Simple Haskell scripting
It's been quite some time since Martín Escardó told me about the somehow forgotten Haskell function
interact :: (String -> String) -> IO ()
What it does is that it takes the function String -> String
and simply throws the entire program input into it and whatever it outputs produces as the program output. For example, the following Haskell program prints back the first 10 characters of its input.
main :: IO ()
main = interact (take 10)
This becomes really useful when chained with the lines :: String -> [String]
and unlines :: [String] -> String
functions. Then writing Haskell scripts that deal with text data, with entries split by lines, is just simple. The usual Haskell script then looks something like this.
main :: IO ()
main = interact pipe
where
pipe = unlines . map linepipe . lines
linepipe :: String -> String
linepipe = ... -- a function that handles a single line of input
There are quite a few Haskell scripting libraries out there and they get quite a bit of attention. However, I haven't seen many articles praising the simplicity and power of the interact
+lines
+unlines
pattern.
As a bonus, here is one real-world example. Let's say we want to convert the CSV data of this form
Alice;travelling, maths;https://alice.crypto
Bob;espionage;
Jonáš;;
...
into an html of this form
<ul>
<li><a href="https://alice.crypto">Alice</a> (travelling, maths)</li>
<li>Bob (espionage)</li>
<li>Jonáš</li>
...
</ul>
The following is an easy script I wrote to do just that with interact
. I decided to use the Data.Text.Lazy
version of interact
because I needed to deal with unicode characters properly. The added benefit of this choice is that the script can handle inputs that don't fit into the memory.
#!/usr/bin/env runhaskell
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as T
main :: IO ()
main = do
putStrLn "<ul>"
T.interact pipe
putStrLn "</ul>"
where
pipe :: T.Text -> T.Text
pipe = T.unlines . map linepipe . T.lines
linepipe :: T.Text -> T.Text
linepipe line =
"<li>" <> name <> hobbies <> "</li>"
where
(a:b:c:xs) = T.split (== ';') line
name | c == "" = a
| otherwise = "<a href=\"" <> c <> "\">" <> a <> "</a>"
hobbies | b /= "" = " (" <> b <> ")"
| otherwise = ""
To run it, simply type cat data.csv | ./script.sh
. This should work provided that script.sh
is executable and the package text
is installed.
Responses (?)
JavaScript needs to be enabled to show responses. (Although, it is not necessary for posting them.)