Vigenere cipher exercise in Haskell This is my implementation using the dreadful !!:

import Data.Char (chr, ord, toUpper)


-- A bit of self documentation help
type Key = String
type Msg = String


key :: Key
key = "TSTING"
msg :: Msg
msg = "I'm not even mad... This is impressive!"


-- | Checks if character is valid for encoding
isValid :: Char -&gt; Bool
isValid c = let cUp = toUpper c :: Char
             in 'A' &lt;= cUp &amp;&amp; cUp &lt;= 'Z'


-- | Given 'key' &amp; 'msg' generate a list of [Maybe Int] indices
-- to map 'msg' from 'key', skipping invalid characters
toIdx :: Key -&gt; Msg -&gt; [Maybe Int]
toIdx k m = map (flip mod keyN &lt;$&gt;) $ toIdx_ 0 m
  where keyN = length k :: Int
        toIdx_ :: Int -&gt; Msg -&gt; [Maybe Int]
        toIdx_ _ "" = []
        toIdx_ acc (c:cs)
          | isValid c = Just acc : toIdx_ (acc + 1) cs
          | otherwise = Nothing : toIdx_ acc cs


-- | Given 'key' &amp; 'msg' generate a list of numbers representing
-- the amount to shift 'msg' characters based on 'key'
toShifts :: Key -&gt; Msg -&gt; [Int]
toShifts k m = map toKey (toIdx k m)
  where kUp = map toUpper k :: Key
        toKey :: Maybe Int -&gt; Int
        toKey Nothing  = 0
        toKey (Just x) = ord (kUp!!x) - ord 'A'


-- | Given 'by' &amp; 'c', shift the Char 'c' by amount 'by'. 'by' can be both
-- positive &amp; negative as well as 0.
shift :: Int -&gt; Char -&gt; Char
shift by c
  | isValid c &amp;&amp; c &gt;= 'a' = shift_ $ ord 'a'
  | isValid c &amp;&amp; c &gt;= 'A' = shift_ $ ord 'A'
  | otherwise = c
  where cONorm    = ord (toUpper c) - ord 'A' :: Int
        azN       = ord 'Z' - ord 'A' :: Int
        shift_ :: Int -&gt; Char
        shift_ aO = chr $ aO + mod (by + cONorm) azN


-- Encode &amp; decode a message using the given key.
vigenere, unVigenere :: Key -&gt; Msg -&gt; Msg
vigenere   k m = zipWith shift (toShifts k m) m
unVigenere k m = zipWith shift (map negate $ toShifts k m) m


I found that the most "annoying" thing when coming from background such as Python is to be able to keep track of things, for example when figuring out how to convert valid characters into usable positions to be then mapped with the key. That thing took me half a day to figure out!

How would you do it? Or is there some "standard" way of working with these sort of things? I'm referring particularly to toIdx &amp; toIdx_, had to use toIdx_ to accumulate the indices with acc in this list of Maybe Int in order to correctly map valid Chars with key.

Of course, I wouldn't have it any other way but have an algorithm which takes any String input and creates an encoded String with preserved upper/lower-case and non-valid (out of ASCII alphabet) Chars.
To get rid of !! here, you can use it earlier and earlier until you never even generate an Int.

-- | Given 'key' &amp; 'msg' generate a list of [Maybe Int] indices
-- to map 'msg' from 'key', skipping invalid characters
toIdx :: Key -&gt; Msg -&gt; [Maybe Char]
toIdx k m = toIdx_ (cycle $ map toUpper k) m
  where toIdx_ :: Key -&gt; Msg -&gt; [Maybe Char]
        toIdx_ _ "" = []
        toIdx_ key@(k:ey) (c:cs)
          | isValid c = Just k : toIdx_ ey cs
          | otherwise = Nothing : toIdx_ key cs


-- | Given 'key' &amp; 'msg' generate a list of numbers representing
-- the amount to shift 'msg' characters based on 'key'
toShifts :: Key -&gt; Msg -&gt; [Int]
toShifts k m = map toKey (toIdx k m)
  where toKey :: Maybe Char -&gt; Int
        toKey Nothing  = 0
        toKey (Just x) = ord x - ord 'A'


Of course, there is hardly a need to separate all these steps.

base :: Char -&gt; Maybe Char
base c
  | 'a' &lt;= c &amp;&amp; c &lt;= 'z' = Just 'a'
  | 'A' &lt;= c &amp;&amp; c &lt;= 'Z' = Just 'A'
  | otherwise = Nothing

vigenere, unVigenere :: String -&gt; String -&gt; String
[vigenere, unVigenere] = (`map` [(+), (-)]) $ \direction k -&gt;
  (.) snd $ (`mapAccumL` cycle k) $ \key@(k:ey) c -&gt; case base c of
    Nothing -&gt; (key, c)
    Just a -&gt; (,) ey $ chr $ ord a +
      mod ((ord c - ord a) `direction` (ord (toUpper k) - ord 'A')) (ord 'Z' - ord 'A')