My issue with both your ciphers is that they don't preserve whitespace. Even for a lower-case only string, the following property does not hold: unCaesar n (caesar xs) == xs Indeed, only the following property holds: let xs' = unwords (words xs) in unCaesar n (caesar xs') == xs' But that might be your design, so let us ignore that for now. Instead, let us have a look at your first cipher. # Caesar The nice property about Caesar is that you can encrypt the same way you decrypt. If you move a character `n` characters forward, how many characters do you need to move it to get back the original one? `26 - n`. With that in mind, we can heavily reduce the size of `unCaesar`: unCaesar :: Int -> String -> String unCaesar n = caesar (26 - n) We could also use `caesar (-n)` since we're using `mod`, but that's not important. It would fail if we used `rem`, though. Now that we've reused `unCaesar`, let us have a look at `caesar`: caesar :: Int -> String -> String caesar n s = unwords $ map (map chr) coded where coded = map (map helper) $ words s helper = (+) base . flip mod 26 . (+) n . flip (-) base . ord . toLower base = ord 'a' Point-free programming isn't always best practices. It's clever, but you really don't want to fix something like that in the middle of the night. Compare your code to caesar :: Int -> String -> String caesar n s = unwords $ map (map chr) coded where coded = map (map helper) $ words s helper c = (ord (toLower c) - base + n) `mod` 26 + base base = ord 'a' It is even _shorter_. It is easier to read than the point-free version, too. No `flip`. But if we want to preserve whitespace, it is still not optimal. Since we're handling a single character in `helper` either way, let us just keep spaces and handle all other characters: caesar :: Int -> String -> String caesar n = map helper where helper ' ' = ' ' helper c = chr $ (ord (toLower c) - base + n) `mod` 26 + base base = ord 'a' We've lost all applications of `unwords` and `words`, and instead of the `map (map …)` we only have a single `map`. Being able to advance a lower-case ASCII character seems somewhat important, so let us refactor that: caesar :: Int -> String -> String caesar n = map helper where helper ' ' = ' ' helper c = asciiAdvance n c asciiAdvance :: Int -> Char -> Char asciiAdvance n c = chr $ (ord (toLower c) - base + n) `mod` 26 + base where base = ord 'a' We will revisit Caesar later. # Vigenère First of all, let us apply the point-free to non-pointfree conversion and use pattern-matching in `assign`: vigenere :: String -> String -> String vigenere k s = unwords $ map (map chr) coded where coded = zipWith (zipWith helper) (words s) (words $ assign s 0) helper x y = base + mod (diff (toLower x) + diff (toLower y)) 26 base = ord 'a' diff c = ord c - base assign "" _ = "" assign (x:xs) i | x == ' ' = ' ' : assign xs i | otherwise = (k !! i) : assign xs (mod (i + 1) (length k)) Hm. `assign` just cycles through `k` and skips spaces. We can implement it without `length` and `!!` if we hand it two lists: our secret, and our `cycle`d key: vigenere k s = unwords $ map (map chr) coded where coded = zipWith (zipWith helper) (words s) (words $ assign s (cylce k)) … assign "" _ = "" assign (x:xs) (y:ys) | x == ' ' = ' ' : assign xs (y:ys) | otherwise = y : assign xs ys But for a second, let us again say that you want to keep the whitespace. How would that look like? vigenere :: String -> String -> String vigenere k s = map chr coded where coded = zipWith helper s (assign s (cycle k) base = ord 'a' diff c = ord c - base helper ' ' ' ' = ' ' helper x y = base + mod (diff (toLower x) + diff (toLower y)) 26 assign "" _ = "" assign (x:xs) i | x == ' ' = ' ' : assign xs i | otherwise = (k !! i) : assign xs (mod (i + 1) (length k)) Hm. `zipWith helper` and `assign` have the same type. Maybe we can fuse them? vigenere :: String -> String -> String vigenere k s = map chr $ helper (cycle k) s where base = ord 'a' diff c = ord c - base helper _ [] = [] helper (y:ys) (x:xs) | x == ' ' = ' ' : helper xs (y:ys) | otherwise = modify x y : helper xs ys modify x y = base + mod (diff (toLower x) + diff (toLower y)) 26 In order to share code between `vigenere` and `unVigenere`, we need one last step. Let us change `modify`'s type to `Char -> Char -> Char`: vigenere :: String -> String -> String vigenere k s = helper (cycle k) s where base = ord 'a' diff c = ord c - base helper _ [] = [] helper (y:ys) (x:xs) | x == ' ' = ' ' : helper (y:ys) xs | otherwise = modify x y : helper ys xs modify x y = chr $ base + mod (diff (toLower x) + diff (toLower y)) 26 If we rewrite `unVigenere` the same way, we'll notice that it looks very similar: unVigenere :: String -> String -> String unVigenere k s = helper (cycle k) s where base = ord 'a' diff c = ord c - base helper _ [] = [] helper (y:ys) (x:xs) | x == ' ' = ' ' : helper (y:ys) xs | otherwise = modify x y : helper ys xs modify x y = chr $ base + mod (diff (toLower x) - diff (toLower y)) 26 Note that I've used the properties of `mod` again, just as in Caesar. Everything is the same. Except for the modification. In `unViginere`, we subtract the key, and in `vigenere` we add it. So let us move that into another function: withKey :: (Char -> Char -> Char) -> String -> String -> String withKey f k s = helper (cycle k) s where helper _ [] = [] helper (y:ys) (x:xs) | x == ' ' = ' ' : helper (y:ys) xs | otherwise = f y x : helper ys xs vigenere :: String -> String -> String vigenere = withKey modify where modify k v = asciiAdvance (ord k - ord 'a') v unVigenere :: String -> String -> String unVigenere = withKey modify where modify k v = asciiAdvance (26 - (ord k - ord 'a')) v And we're done.