Jonathan
Jonathan

Reputation: 11321

How can I refactor these IO operations in Haskell?

I just made a script that takes some values from a TSV file and formats them differently. The script looks like this:

{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}

import qualified Data.Text as T
import qualified Data.Text.IO as TIO

tsvToPat tsv = T.unlines $ map (makePat . (T.replace "-" " ") . head . (T.splitOn "\t")) (T.lines tsv)

main :: IO ()
main = do
  pantone <- TIO.readFile "../data/maps/pantone/pantone.tsv"
  xkcd <- TIO.readFile "../data/maps/xkcd/rgb.txt"
  jaffer <- TIO.readFile "../data/maps/jaffer/master.tsv"
  TIO.putStr $ tsvToPat pantone
  TIO.putStr $ tsvToPat xkcd
  TIO.putStr $ tsvToPat jaffer


makePat :: T.Text -> T.Text
makePat pat = T.concat [ "{\"label\":\"COLOR\",\"pattern\":[{\"lower\":\""
                       , pat
                       , "\"}]}"
                       ]

but I'm struggling with thinking about how to refactor everything in the main function. What I want to do is something like:

maps :: [FilePath]
maps = [ "../data/maps/pantone/pantone.tsv"
       , "../data/maps/xkcd/rgb.txt"
       , "../data/maps/jaffer/master.tsv"
       ]

main = map (TIO.putStr . tsvToPat . TIO.readFile) maps

Which won't work, because I'm guessing it's mixing IO operations with pure functions. Is there some magic monadic bind operator I should be using here?

Upvotes: 2

Views: 77

Answers (2)

K. A. Buhr
K. A. Buhr

Reputation: 50819

The monadic binding operator you're looking for is <=< from Control.Monad. It acts like function composition for monad operators. You would also need to upgrade your map to a monadic mapping function mapM_. For example, ignoring the tsvToPat bit, you could write:

main = mapM_ (TIO.putStr <=< TIO.readFile) maps

It can be a little tricky to figure out how to squeeze a pure function like tsvToPat in there. One way is to make it monadic by writing return . tsvToPat, so you get:

main' :: IO ()
main' = mapM_ (TIO.putStr <=< return . tsvToPat <=< TIO.readFile) maps

Note that the precedence of . here is higher than <=<.

Even though that version makes everything pretty clear, it can actually be simplified to:

main :: IO ()
main = mapM_ (TIO.putStr . tsvToPat <=< TIO.readFile) maps

Upvotes: 6

Actions in do blocks desugar to calls to >>=, and there's a flipped version of that called =<<. To run a list of IO actions, use mapM_. Put those concepts together and you get this:

main = mapM_ (\x -> TIO.putStr . tsvToPat =<< TIO.readFile x) maps

Upvotes: 4

Related Questions