Essentially I need a bot. The bot needs to talk to a device that speaks SMASH CLP, and my initial thoughts were to write some Haskell code around the useful function interact, or rather, a close relative of interact.
Interact has the signature
interact :: (String -> String) -> IO ()
This just means that it takes a function which takes a String as input and produces a String as output, and the result of interact is an IO action that returns ().
What this gives me is the ability to process input strings, to produce output strings, which get read in and written out by the IO action that is the result of interact. It interleaves the pure functional processing around the input to create output.
The way it works is by calling the function getContents, which lazily reads all the input from stdin and processes it through the provided (String -> String) function, to produce output.
At first glance, this is not seemingly a very advantageous function, except when you take into account all the ways you can set up the IO before calling interact to get some nice behaviors.
For example, if one sets the handles for stdin and stdout to "LineBuffer" based buffers before calling interact, you can get linewise input by doing the following inside the (String -> String) processing function.
lineId = unlines . lines
The function lineId breaks up the lazy input string into lines, then re-assembles them by composing the function lines and unlines together.
The type of the function lines is:
lines :: String -> [String]
The type of the function unlines is
unlines :: [String] -> String
One would think that these two functions would just negate each other, and they do when combined as "unlines . lines" returning the original input to lines, however, if I add further behaviors to the function composition expression that operate on the [String] result of lines before running unlines I have the opportunity to work on each member of the list.
This gives me guaranteed linewise input evaluation of commands, and the ability to interpret line-oriented protocols, strings or what have you.
Now think of how useful interact can be?
Now, interact by itself is only dealing with stdin and stdout. Perhaps I want to open a connection to a remote service, or even a stream of in and out data. It is not terribly hard to write a relative to interact that can handle those sources of data.
hInteract :: Handle -> Handle -> (String -> String) -> IO ()
hInteract inp out f = hPutStr out . f =<< hGetContents inp
This function can take a handle for input, a handle for output, and a function of String -> String, and uses them to lazilly read input from the input handle, pushing data through the String -> String function, to finally output the result string to the output handle.
All of the interesting parts of this function are still contained in f.
The part that's kind of exciting now is that I don't have to limit myself to just line based input if I consider using functions like words and unwords.
I think I'm nearly ready to write an almost IO free, pure version of my Expect-like code bot in Haskell.