Learning Haskell: Caesar cipher

In my quest to learn Haskell I’ve been reading through Learn You A Haskell, a great book on understanding Haskell.

While reading about function signatures and recursion yesterday it came to my mind that a Caesar cipher should be quite easy to implement in Haskell – and surprise, it is!

shiftLetter :: Char -> Int -> Char
shiftLetter ' ' _ = ' '
shiftLetter letter 0 = letter
shiftLetter letter offset = shiftLetter (succ letter) (offset - 1)

encode :: [Char] -> Int -> [Char]
encode input offset = [ shiftLetter x offset | x <- input ]

The syntax looks super weird, especially if you’ve never seen Haskell, but it actually makes sense.

First we have function called shiftLetter which takes a Char and Int as parameters, and returns a Char. In Haskell this is defined like so

shiftLetter :: Char -> Int -> Char

Then right below is the actual function body – or bodies in our case! In Haskell, multiple function bodies can be defined depending on input variables.

  • The first line matches the case we get a space as input in which case a space is returned.
  • The second matches the case where we receive an offset of 0, in which case the letter is returned.
  • The third is the recursive function which takes a letter and offset, and calls the shiftLetter function with the next letter (succ letter) and offset - 1.
  • succ is a Haskell build-in function which returns the successor of something. Since Haskell is aware of how the alphabet looks, we can do succ 'A' to get 'B'!

This function runs recursively until we reach the case where offset = 0 and then we just return the letter.

To illustrate this more, take the following example which shows each execution step

shiftLetter 'A' 2

# how the function is called
shiftLetter (succ 'A') (2 - 1) -> shiftLetter B 1
shiftLetter (succ 'B') (1 - 1) -> shiftLetter C 0

-> return C

With a function to shift single letter all we need is to wrap it in some form of recursion. In Haskell Strings are just lists of Chars, so we can use list comprehension function on strings!

encode :: [Char] -> Int -> [Char]
encode input offset = [ shiftLetter x offset | x <- input ]

The weird looking function body is a list comprehension with a syntax of [ function | list-input ] – we basically do the following:

  • take every single char out of [Char] and assign it to x (x <- input)
  • calculate the cipher (shiftLetter x offset) for each letter
  • return the ciphered text as a [Char], which is just a String in Haskell.

I was surprised that the code worked exactly like I wanted on first try. I thought about how it could be implemented, wrote it down, executed it and it just worked. I’ve rarely had this in any other language!

Sadly, I do not have any use case to try Haskell somewhere yet, but I’m enjoying learning it a LOT. I’ll now think about how to implement a decode function.

Update

I implemented the decode function with some slight changes to the signatures.

shiftLetter :: Char -> Int -> Bool -> Char

The Bool is used to determine if we want to encode (True) or decode (False).

The body can than match the two possible values and decide if we want to shift the letter using succ or pred – the only difference between encoding and decoding.

shiftLetter :: Char -> Int -> Bool -> Char
shiftLetter ' ' _ _ = ' '
shiftLetter letter 0 _ = letter
shiftLetter letter offset True = shiftLetter (succ letter) (offset - 1) True
shiftLetter letter offset False = shiftLetter (pred letter) (offset - 1) False

Update 2

I extracted the code into a module and published it on my GitHub! https://github.com/KevinGimbel/caesar-cipher

I’ve also slightly updated it to avoid going out of bounds of the English alphabet. Instead of using succ and pred directly, they’re now wrapped in functions to handle the beginning and end of the alphabet.

-- get next letter, if we reach Z the next is A
succLetter :: Char -> Char
succLetter 'z' = 'a'
succLetter 'Z' = 'A'
succLetter x = succ x

-- get prev letter, if we reach A the previous is Z again
predLetter :: Char -> Char
predLetter 'a' = 'z'
predLetter 'A' = 'Z'
predLetter x = pred x

Where other languages would require some ifs and elses, Haskell can “just” work with parameter matching of the functions.

succLetter z will return “a”, succLetter f will return “g”, and so on. The same applies to predLetter which will return “z” when “a” is given.

Links