Charlie Harvey

Let’s make morse code with Haskell

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.

y station layout from da gov.uk

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!


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.