Here is my solution for an exercise, which requires translation of strings to key presses on a phone keypad (for example, to get 'b' the digit '2' needs to be pressed twice).

It's taken from the book Haskell from first principles, and originally appeared in 1HaskellADay. I'd love to get feedback.

module Phone where
import Data.Char
import Data.String.Utils
import Data.List

data DaPhone = DaPhone [String]
convo :: [String]
convo =
["Wanna play 20 questions",
"Ya",
"U 1st haha",
"Lol ok. Have u ever tasted alcohol lol",
"Lol ya",
"Wow ur cool haha. Ur turn",
"Ok. Do u think I am pretty Lol",
"Lol ya",
"Haha thanks just making sure rofl ur turn"]

-- validButtons = "1234567890*#"
type Digit = Char
-- Valid presses: 1 and up
type Presses = Int

reverseTaps :: DaPhone -> Char -> [(Digit, Presses)]
reverseTaps (DaPhone keys) c = if isUpper c then ('*', 1) : looks else looks
where looks = look keys (toLower c)
-- assuming the default phone definition
-- 'a' -> [('2', 1)]
-- 'A' -> [('*', 1), ('2', 1)]

look :: [String] -> Char -> [(Digit, Presses)]
look keys c = look' keys c 0

look' :: [String] -> Char -> Int -> [(Digit, Presses)]
look' [] _ _ = []
look' (x:xs) c n = if ind /= (-1) then [(head $show n, ind + 1)] else look' xs c (n + 1) where ind = maybe (-1) id$ elemIndex c x

cellPhonesDead :: DaPhone -> String -> [(Digit, Presses)]
cellPhonesDead = (concat .) . map . reverseTaps

-- count total presses
fingerTaps :: [(Digit, Presses)] -> Presses
fingerTaps = sum . map snd

mostPopularLetter :: String -> Char
mostPopularLetter = head . longest . group . sort

coolestLtr :: [String] -> Char
coolestLtr = mostPopularLetter . filter isAlpha . concat

coolestWord :: [String] -> String
coolestWord = head . longest . group . sort . words . join " "

longest :: [[a]] -> [a]
longest = maximumBy (\x y -> compare (length x) (length y))

phone :: DaPhone
phone = DaPhone keymap

keymap :: [String]
keymap =

[
" 0",
"1",
"abc2",
"def3",
"ghi4",
"jkl5",
"mno6",
"pqrs7",
"tuv8",
"wxyz9"
]


In real code, I would consider this to be too much naming for this little code. Most of this would probably be used only once and could thus be inlined. Since the exercise required implementations for all these, it's okay.

(concat .) . map is foldMap.

\x y -> compare (length x) (length y) is comparing length.

Did you try compiling this? ind /= Nothing means that ind + 1 shouldn't work, and elemIndex takes the Char before the String.

look keys c = case asum $zipWith (fmap . (,)) [0..]$ map (elemIndex c) keys of
Nothing -> []
Just (n, ind) -> [(head \$ show n, ind + 1)]

• Thanks, fixed look. I originally used my own function for index, which returned -1, thats why there was a confusion. Commented Sep 5, 2016 at 9:33

I am wondering whether DaPhone could have a better data structure, because I think your passing number around makes code complex. Such as:

data DaPhone = DaPhone [(Char, String)]

reverseTaps :: DaPhone -> Char -> [(Digit, Presses)]
reverseTaps phone c =
if isUpper c
then [('*', 1), look phone (toLower c)]
else [look phone c]

look :: DaPhone -> Char -> (Digit, Presses)
look (DaPhone ((digit, press):tl)) c =
case elemIndex c press of
Just idx -> (digit, idx + 1)
Nothing -> look (DaPhone tl) c

cellPhonesDead :: DaPhone -> String -> [(Digit, Presses)]
cellPhonesDead phone = foldMap (reverseTaps phone)


What I assume:

• look will not fail.
• DaPhone will store like ('2', 'abc2') instead of ('2', 'abc').
• That's not valid Haskell code, since either data or newtype is missing.
– Zeta
Commented Sep 14, 2016 at 6:18
• sure. I just modify the core functions. you can find the data and newtype in original code. Because those definitions are fixed in this problem. @Zeta Commented Sep 14, 2016 at 6:36
• ah yes, i did miss the data. i added it. thanks Commented Sep 14, 2016 at 6:38