Search code examples
haskellmonadsdo-notation

Understanding Haskell (<-) syntactic sugar


I'm trying to parse a CSV file using Cassava. I want a function that returns Nothing if parse was unsuccessful and Just (V.Vector (String, String, String)) otherwise.

I'm using the code below:

  {-# LANGUAGE ScopedTypeVariables #-}
module Lib
    ( someFunc
    ) where

import qualified Data.ByteString.Lazy as BL
import Data.Csv
import qualified Data.Vector as V

type Dataset = (String, String, String)
someFunc :: Maybe (V.Vector Dataset)
someFunc = do
    csvData <- BL.readFile "TAEE3.SA.csv"
    case decode HasHeader csvData :: Either String (V.Vector (String, String, String)) of
      Left a -> Nothing
      Right v -> Just v

The error is:

  • Couldn't match type ‘IO’ with ‘Maybe’
      Expected type: Maybe BL.ByteString
        Actual type: IO BL.ByteString
    • In a stmt of a 'do' block: csvData <- BL.readFile "TAEE3.SA.csv"
      In the expression:
        do csvData <- BL.readFile "TAEE3.SA.csv"
           case  decode HasHeader csvData ::
                   Either String (V.Vector (String, String, String))
           of
             Left a -> Nothing
             Right v -> Just v
      In an equation for ‘someFunc’:
          someFunc
            = do csvData <- BL.readFile "TAEE3.SA.csv"
                 case  decode HasHeader csvData ::
                         Either String (V.Vector (String, String, String))
                 of
                   Left a -> Nothing
                   Right v -> Just v
   |
14 |     csvData <- BL.readFile "TAEE3.SA.csv"
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^

It's like the <- function is not working at all. Isn't it supposed to return an a in IO a monad?


Solution

  • While <- does give you the a in IO a, it doesn't do it by taking it out of the IO monad. In general, it's impossible to take a value out of monads. What it actually does is put the rest of the do block in the monad too. To account for this, you need to make your function return its result in IO, and then add a return to wrap your final Maybe back in IO:

      {-# LANGUAGE ScopedTypeVariables #-}
    module Lib
        ( someFunc
        ) where
    
    import qualified Data.ByteString.Lazy as BL
    import Data.Csv
    import qualified Data.Vector as V
    
    type Dataset = (String, String, String)
    someFunc :: IO (Maybe (V.Vector Dataset))
    someFunc = do
        csvData <- BL.readFile "TAEE3.SA.csv"
        return $ case decode HasHeader csvData :: Either String (V.Vector (String, String, String)) of
          Left a -> Nothing
          Right v -> Just v