Recently, nor and I went with her family to Bletchley Park. Nor’s grandmother used to be a radio operator at what we think was one of the Y stations, near Leighton Buzzard. She spent her time transmitting and receiving coded messages in morse code — in 5 character blocks of apparently random characters.
Anyhow, that got nor and me thinking about morse code and in particular how one could write a simple morse translation program. So, I hacked one together in Haskell before work this morning. I learned that Haskell’s GetOpt implementation is a massive faff. And that interact rocks. Well I already knew that. But now I know it more. Here is the code.module Main where
import qualified Data.Map as M (Map, toList, fromList, lookup)
import System.Console.GetOpt
import Data.Maybe (fromMaybe)
import Data.Char (toLower)
import System.Environment
data Options = Options { optUnmorse :: Bool } deriving Show
type MorseString = String
defaultOptions :: Options
defaultOptions = Options { optUnmorse = False }
options :: [OptDescr (Options -> Options)]
options = [ Option ['u'] ["unmorse"] (NoArg (\ opts -> opts { optUnmorse = True })) "Translate from morse back to string" ]
myOpts :: [String] -> IO (Options, [String])
myOpts argv =
case getOpt Permute options argv of
(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where
header = "Usage: morse [-u(nmorse)] < file"
letters :: (M.Map Char MorseString)
letters = M.fromList [
('a', ".-")
, ('b', "-...")
, ('c', "-.-.")
, ('d', "-..")
, ('e', ".")
, ('f', "..-.")
, ('g', "--.")
, ('h', "....")
, ('i', "..")
, ('j', ".---")
, ('k', "-.-")
, ('l', ".-..")
, ('m', "--")
, ('n', "-.")
, ('o', "---")
, ('p', ".--.")
, ('q', "--.-")
, ('r', ".-.")
, ('s', "...")
, ('t', "-")
, ('u', "..-")
, ('v', "...-")
, ('w', ".--")
, ('x', "-..-")
, ('y', "-.--")
, ('z', "--..")
, ('1', ".----")
, ('2', "..---")
, ('3', "...--")
, ('4', "....-")
, ('5', ".....")
, ('6', "-....")
, ('7', "--...")
, ('8', "---..")
, ('9', "----.")
, ('0', "-----")
, (' ', "_")
]
-- ' yeh, i found a bug in my syntax highlighter!
invertLetters = M.fromList $ map (\(x,y) -> (y,x)) $ M.toList letters
charToMorse :: Char -> MorseString
charToMorse x =
case M.lookup (toLower x) letters of
Just x' -> x' ++ " "
Nothing -> ""
stringToMorse :: String -> MorseString
stringToMorse xs =
m ++ "\n"
where
m = concatMap charToMorse xs
morseToChar :: MorseString -> Char
morseToChar x =
case M.lookup x invertLetters of
Just x' -> x'
Nothing -> '?'
morseToString :: MorseString -> String
morseToString xs =
s ++ "\n"
where
s = map morseToChar $ words xs
main :: IO ()
main =
do (os, _) <- getArgs >>= myOpts
if optUnmorse os
then interact morseToString
else interact stringToMorse
Aside from the ugly GetOpt stuff, the main interesting thing here is inverting our map of characters to morse strings. My version of morse represents a pause between words as an underscore, a dot as a ., and a dash as a -, which seemed reasonable. I ignore any characters that don’t have a morse code representation (accroding to the chart on wikipedia). I compile with ghc in the normal fashion $ ghc -O2 morse.hs
[1 of 1] Compiling Main ( morse.hs, morse.o )
Linking morse ...
/>
Now I can call my morse executabe, type my messages in, terminated with a ctrl-d, and get the morse equivalent. $ ./morse
hello world
.... . .-.. .-.. --- _ .-- --- .-. .-.. -..
If I want to un-morse something into its letter-ish equivalent, I call morse with -u.$ echo .... . .-.. .-.. --- _ .-.. . .-- .. ... | ./morse -u
hello lewis
I can pipe files to the morse program — here I turn the source code of the program into morse code. $ ./morse < morse.hs
-- --- -.. ..- .-.. . _ .- .. -. _ .-- .... . .-. . .. -- .--. --- .-. - _ --.- ..- .- .-.. .. ..-. .. . -.. _ .- - .- .- .--. _ .- ... _ _ .- .--. _ - --- .. ... - _ ..-. .-. --- -- .. ... - _ .-.. --- --- -.- ..- .--. .. -- .--. --- .-. - _ -.-- ... - . -- --- -. ... --- .-.. . . - .--. - .. -- .--. --- .-. - _ .- - .- .- -.-- -... . _ _ ..-. .-. --- -- .- -.-- -... . _ .. -- .--. --- .-. - _ -.-- ... - . -- -. ...- .. .-. --- -. -- . -. - -.. .- - .- _ .--. - .. --- -. ... _ _ .--. - .. --- -. ... _ _ --- .--. - -. -- --- .-. ... . _ _ --- --- .-.. _ _ -.. . .-. .. ...- .. -. --. _ .... --- .-- - -.-- .--. . _ --- .-. ... . - .-. .. -. --. _ _ - .-. .. -. --. -.. . ..-. .- ..- .-.. - .--. - .. --- -. ... _ _ .--. - .. --- -. ... -.. . ..-. .- ..- .-.. - .--. - .. --- -. ... _ _ .--. - .. --- -. ... _ _ --- .--. - -. -- --- .-. ... . _ _ .- .-.. ... . _ --- .--. - .. --- -. ... _ _ .--. - . ... -.-. .-. _ .--. - .. --- -. ... _ _ .--. - .. --- -. ... --- .--. - .. --- -. ... _ _ _ .--. - .. --- -. _ ..- _ ..- -. -- --- .-. ... . _ --- .-. --. _ _ --- .--. - ... _ _ --- .--. - ... _ _ --- .--. - -. -- --- .-. ... . _ _ .-. ..- . _ _ .-. .- -. ... .-.. .- - . _ ..-. .-. --- -- _ -- --- .-. ... . _ -... .- -.-. -.- _ - --- _ ... - .-. .. -. --. _ -- -.-- .--. - ... _ _ - .-. .. -. --. _ _ _ .--. - .. --- -. ... _ - .-. .. -. --. -- -.-- .--. - ... _ .- .-. --. ...- _ _ _ -.-. .- ... . _ --. . - .--. - _ . .-. -- ..- - . _ --- .--. - .. --- -. ... _ .- .-. --. ...- _ --- ..-. _ _ _ _ --- -. _ _ _ _ .-. . - ..- .-. -. _ ..-. --- .-.. -.. .-.. _ ..-. .-.. .. .--. _ .. -.. _ -.. . ..-. .- ..- .-.. - .--. - .. --- -. ... _ --- _ -. _ _ _ _ . .-. .-. ... _ _ .. --- .-. .-. --- .-. _ ..- ... . .-. .-. .-. --- .-. _ -.-. --- -. -.-. .- - _ . .-. .-. ... _ _ ..- ... .- --. . -. ..-. --- _ .... . .- -.. . .-. _ --- .--. - .. --- -. ... _ _ .-- .... . .-. . _ _ _ _ _ .... . .- -.. . .-. _ _ ... .- --. . _ -- --- .-. ... . _ ..- -. -- --- .-. ... . _ _ ..-. .. .-.. . .-.. . - - . .-. ... _ _ .- .--. _ .... .- .-. _ --- .-. ... . - .-. .. -. --. .-.. . - - . .-. ... _ _ ..-. .-. --- -- .. ... - _ _ _ _ _ _ _ .- _ _ _ _ _ _ -... _ _ _ _ _ _ -.-. _ _ _ _ _ _ -.. _ _ _ _ _ _ . _ _ _ _ _ _ ..-. _ _ _ _ _ _ --. _ _ _ _ _ _ .... _ _ _ _ _ _ .. _ _ _ _ _ _ .--- _ _ _ _ _ _ -.- _ _ _ _ _ _ .-.. _ _ _ _ _ _ -- _ _ _ _ _ _ -. _ _ _ _ _ _ --- _ _ _ _ _ _ .--. _ _ _ _ _ _ --.- _ _ _ _ _ _ .-. _ _ _ _ _ _ ... _ _ _ _ _ _ - _ _ _ _ _ _ ..- _ _ _ _ _ _ ...- _ _ _ _ _ _ .-- _ _ _ _ _ _ -..- _ _ _ _ _ _ -.-- _ _ _ _ _ _ --.. _ _ _ _ _ _ .---- _ _ _ _ _ _ ..--- _ _ _ _ _ _ ...-- _ _ _ _ _ _ ....- _ _ _ _ _ _ ..... _ _ _ _ _ _ -.... _ _ _ _ _ _ --... _ _ _ _ _ _ ---.. _ _ _ _ _ _ ----. _ _ _ _ _ _ ----- _ _ _ _ _ _ _ _ _ _ _ _ .. -. ...- . .-. - . - - . .-. ... _ _ ..-. .-. --- -- .. ... - _ _ -- .- .--. _ -..- -.-- _ _ -.-- -..- _ _ - --- .. ... - _ .-.. . - - . .-. ... -.-. .... .- .-. --- --- .-. ... . _ _ .... .- .-. _ _ --- .-. ... . - .-. .. -. --. -.-. .... .- .-. --- --- .-. ... . _ -..- _ _ _ _ _ _ -.-. .- ... . _ .-.. --- --- -.- ..- .--. _ -..- _ .-.. . - - . .-. ... _ --- ..-. _ _ _ _ _ _ ..- ... - _ -..- _ _ -..- _ _ _ _ _ _ _ _ _ --- - .... .. -. --. _ _ ... - .-. .. -. --. --- --- .-. ... . _ _ - .-. .. -. --. _ _ --- .-. ... . - .-. .. -. --. ... - .-. .. -. --. --- --- .-. ... . _ -..- ... _ _ _ _ _ _ -- _ _ -. _ _ .-- .... . .-. . _ _ _ _ _ -- _ _ -.-. --- -. -.-. .- - .- .--. _ -.-. .... .- .-. --- --- .-. ... . _ -..- ... -- --- .-. ... . --- .... .- .-. _ _ --- .-. ... . - .-. .. -. --. _ _ .... .- .-. -- --- .-. ... . --- .... .- .-. _ -..- _ _ _ _ _ _ _ -.-. .- ... . _ .-.. --- --- -.- ..- .--. _ -..- _ .. -. ...- . .-. - . - - . .-. ... _ --- ..-. _ _ _ _ _ _ ..- ... - _ -..- _ _ -..- _ _ _ _ _ _ --- - .... .. -. --. _ _ -- --- .-. ... . --- - .-. .. -. --. _ _ --- .-. ... . - .-. .. -. --. _ _ - .-. .. -. --. -- --- .-. ... . --- - .-. .. -. --. _ -..- ... _ _ _ _ _ _ ... _ _ -. _ _ .-- .... . .-. . _ _ _ _ ... _ _ -- .- .--. _ -- --- .-. ... . --- .... .- .-. _ _ .-- --- .-. -.. ... _ -..- ... -- .- .. -. _ _ _ -- .- .. -. _ _ _ _ _ _ -.. --- _ --- ... _ _ _ --. . - .-. --. ... _ _ -- -.-- .--. - ... _ _ _ _ _ _ _ _ .. ..-. _ --- .--. - -. -- --- .-. ... . _ --- ... _ _ _ _ _ _ _ _ _ _ - .... . -. _ .. -. - . .-. .- -.-. - _ -- --- .-. ... . --- - .-. .. -. --. _ _ _ _ _ _ _ _ _ _ _ . .-.. ... . _ .. -. - . .-. .- -.-. - _ ... - .-. .. -. --. --- --- .-. ... .
And naturally I can pipe the output of morse to morse -u to get the original string. Which is sort of pointless but fun. $ echo hello world | ./morse | ./morse -u
hello world
The next step is to make it beep, of course. But Euterpea (haskore’s successor) looks to be overkill for such a trivial task as making two different sorts of beep.
Incidentally, if you search "morse something" on DuckDuckGo it returns a morse version of "something". Nifty.
Update I’ve added audio support now. You can read about it at More haskell morse code. This time with audio!