Haskell Integer to English

Inspired by a programming interview question I heard about, here's some cute code I wrote for converting (positive) integers to words. It breaks the number into groups of three digits, converts each group into English, and intersperses words like million and billion where necessary. I did the three digit number conversion using a cascading pattern matching system.

I had never thought about this problem much and was surprised by how many little quirks there are.

import Data.List

suffixes = ["" ,"thousand", "million", "billion", "trillion", "quadrillion"]

toDigits :: Int -> [Int]
toDigits 0 = []
toDigits x = toDigits (x `div` 10) ++ [x `mod` 10]

(+++) :: String -> String -> String
"" +++ "" = ""
a +++ "" = a
"" +++ a = a
a +++ b = a ++ " " ++ b

digitGroups ::Int -> [(Int, Int, Int)]
digitGroups n = digitGroups' $ reverse $ toDigits $ n

digitGroups' :: [Int] -> [(Int, Int, Int)]
digitGroups' (a:b:c:ds) = (c,b,a):(digitGroups' ds)
digitGroups' (a:b:[]) = (0,b,a):[]
digitGroups' (a:[]) = (0,0,a):[]
digitGroups' [] = []

groupToWords :: (Int, Int, Int) -> String -> String
groupToWords digits suffix = (toWords3 digits) +++ suffix

toWords :: Int -> String
toWords 0 = "zero"
toWords n = foldl (+++) "" (reverse (zipWith groupToWords  (digitGroups n) suffixes))

toWords3 :: (Int, Int, Int) -> String
toWords3 (0,a,b) = toWords2 (a,b)
toWords3 (a,b,c) = (toWords1 a) +++ "hundred" +++ (toWords2 (b,c))

toWords2 :: (Int, Int) -> String
toWords2 (0,a) = toWords1 a
toWords2 (1,0) = "ten"
toWords2 (1,1) = "eleven"
toWords2 (1,2) = "twelve"
toWords2 (1,3) = "thirteen"
toWords2 (1,5) = "fifteen"
toWords2 (1,8) = "eighteen"
toWords2 (1,a) = (toWords1 a) ++ "teen"
toWords2 (2,a) = "twenty" +++ (toWords1 a) 
toWords2 (3,a) = "thirty" +++ (toWords1 a) 
toWords2 (4,a) = "forty" +++ (toWords1 a) 
toWords2 (5,a) = "fifty" +++ (toWords1 a) 
toWords2 (6,a) = "sixty" +++ (toWords1 a) 
toWords2 (7,a) = "seventy" +++ (toWords1 a) 
toWords2 (8,a) = "eighty" +++ (toWords1 a) 
toWords2 (9,a) = "ninety" +++ (toWords1 a) 

toWords1 :: Int -> String
toWords1 0 = ""
toWords1 1 = "one"
toWords1 2 = "two"
toWords1 3 = "three"
toWords1 4 = "four"
toWords1 5 = "five"
toWords1 6 = "six"
toWords1 7 = "seven"
toWords1 8 = "eight"
toWords1 9 = "nine"

Author | Ben Wiener

Background in physics. Also interested in computing, robotics, hiking, woodworking, and other things.