I gave a short talk at the local Haskell meetup yesterday about the library “Machines” by the ever-so-famous Edward Kmett.

This is a quick roundup of what I learned, and the resources I ran across.

Counting Words

The initial task I gave myself to learn was to read an input line, and report how many words were in that line.

That consisted of 3 machines wired together in a pipeline. I only had to write a custom function for the worker in the middle. And even that was a one-liner.

The auto function (and it’s autoM friend) seem like the easiest way to create a simple mapper type machine that takes some input, does a bit of work, and spits out output.

eachLineCount :: IO ()
eachLineCount = runT_ $ repeatedly (yield =<< liftIO getLine)
                     ~> countWords
                     ~> autoM print

countWords :: Process String Int
countWords = auto (length . splitOn " ")

Teeing two inputs together

The other big thing I tackled was the Tee type. It lets you read from one of two incoming streams of data, explicitly. For example, logically you can say: “Give me the next value off the left stream”

There’s another type of multi-input machine I didn’t dive into called Wye that allows for a blind await in the consuming end, and the left pipe will be read until its empty, and then the right pipe will be read (as opposed to explicitly asking for Left or Right on a Tee)

Actually building the Tee was relatively simple once I figured out the tee function. I have a commented out version at the bottom of the next snippet that manually assembled the Tee using addL and capR. It is equivalent to the much shorter tee version.

compareLineCounts :: String -> IO ()
compareLineCounts fixedString =
  runT_ $ tee (repeated fixedString ~> countWords) (ioInput ~> countWords) mergeInput
       ~> compareWords
       ~> autoM putStrLn

ioInput :: (MonadIO m) => SourceT m String
ioInput = repeatedly $ do
                        liftIO $ putStrLn "Enter your new line to compare: "
                        x <- liftIO getLine
                        yield x

mergeInput :: Tee a a (a,a)
mergeInput = repeatedly $ do
              x <- awaits L
              y <- awaits R
              yield (x, y)

compareWords :: (Ord a) => Process (a, a) String
compareWords = repeatedly $ do (x,y) <- await
                               yield $ case compare x y of
                                        GT -> "Greater Than"
                                        LT -> "Less Than"
                                        EQ -> "Equal To"

-- compareLineCounts :: String -> IO ()
-- compareLineCounts fixedString =
--   runT_ $ (capR (repeated fixedString ~> countWords) $
--            addL (ioInput              ~> countWords)
--            mergeInput)
--        ~> compareWords
--        ~> autoM putStrLn


Many thanks are in order to @kmett, @yoeight, @glguy, @cartazio and everybody else I asked questions of, all of whom helped me immensely on IRC.