Here's a program I wrote to convert numbers into English words using Haskell.
import Data.Char
import Data.List
type WordNum = String
ones :: (Integral a, Show a) => a -> WordNum
ones 1 = "one"
ones 2 = "two"
ones 3 = "three"
ones 4 = "four"
ones 5 = "five"
ones 6 = "six"
ones 7 = "seven"
ones 8 = "eight"
ones 9 = "nine"
ones n = error (show n ++ " is not a one-digit value")
teens :: (Integral a, Show a) => a -> WordNum
teens 10 = "ten"
teens 11 = "eleven"
teens 12 = "twelve"
teens 13 = "thirteen"
teens 14 = "fourteen"
teens 15 = "fifteen"
teens 16 = "sixteen"
teens 17 = "seventeen"
teens 18 = "eighteen"
teens 19 = "nineteen"
teens n = error (show n ++ " is not a teen")
tens :: (Integral a, Show a) => a -> WordNum
tens 1 = "ten"
tens 2 = "twenty"
tens 3 = "thirty"
tens 4 = "forty"
tens 5 = "fifty"
tens 6 = "sixty"
tens 7 = "seventy"
tens 8 = "eighty"
tens 9 = "ninety"
tens n = error (show n ++ " is not a tens place value")
groups :: [WordNum]
groups = ["", " thousand", " million", " billion", " trillion"]
groupToWord :: (Integral a, Show a) => a -> String
groupToWord n
| n == 0 = ""
| n < 10 = ones n
| n < 20 = teens n
| n < 100 = tens (n `div` 10) ++ ' ' : (groupToWord $ n `mod` 10)
| n < 1000 = ones (n `div` 100) ++ " hundred " ++ (groupToWord $ n `mod` 100)
| otherwise = error (show n ++ " is not a 3-digit group")
-- Splits a number into groups in reverse order
splitNum :: (Integral a, Show a) => a -> [a]
splitNum n
| n <= 999 = [n]
| otherwise = (n `mod` 1000) : splitNum (n `div` 1000)
numToWord :: (Integral a, Show a) => a -> String
numToWord n
| n == 0 = "zero"
| n >= 10^15 = error "Doesn't support numbers bigger than trillions"
| otherwise = concat $ intersperse ", " [w ++ g | (w,g) <- reverse (zip wordGroups groups)]
where
wordGroups = toWordGroups $ splitNum n
toWordGroups :: (Integral a, Show a) => [a] -> [WordNum]
toWordGroups (g:gs) = groupToWord g : toWordGroups gs
toWordGroups _ = []
It seems like there's a lot of redundancy, and I'm also not very happy with how I had to join the lists. Also, is there a way to do more consing and less appending? I was hoping to cons the groups back together after they were made words so I wouldn't have to reverse the zipped list afterward. Also, as always, I'd really appreciate general comments on improvements to style and best practice.