on
Machines
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.
- Github Repo of my example code.
- Hackage Page for Machines
- Github for Machines
- Slide deck by Rúnar Óli Bjarnason
- glguy’s 2048 clone
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
Thanks
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.