0

I have a function with the following signature:

Config -> IO (Map.Map String String)

that is used to extract a FilePath from the Config, then load that file (contains lines of text), and then parse the lines into keys and values - which is why I need to save them into map, preferably.

So, I have created the following function:

let lns = liftM lines $ readFile $ getSet $ listF c in
    foldM (\m n -> liftM3 Map.insert n n m) (return Map.empty) lns

Now, I would hope that Haskell realizes that I want this to be a IO Map of Strings (for simplicity, I dont parse the lines into keys and vals for now, and just put the whole line in there).

However, I get the following errors:

• Couldn't match type ‘Map.Map String’ with ‘IO’
  Expected type: IO (Map.Map String String)
    Actual type: Map.Map
                   String (Map.Map String (Map.Map String String))
• In the expression:
    foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns
  In the expression:
    let lns = liftM lines $ readFile $ getSet $ listF c
    in foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns
  In an equation for ‘getEpisodeNames’:
      getEpisodeNames c
        | listF c == NotSet = return Map.empty
        | otherwise
        = let lns = liftM lines $ readFile $ getSet $ listF c
          in foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns


• Couldn't match type ‘[Char]’ with ‘Map.Map String String’
  Expected type: Map.Map
                   String (Map.Map String (Map.Map String String))
    Actual type: Map.Map String (Map.Map String String)
• In the expression: liftM3 Map.insert n n m
  In the first argument of ‘foldM’, namely
    ‘(\ m n -> liftM3 Map.insert n n m)’
  In the expression:
    foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns


• Couldn't match type ‘[]’ with ‘Map.Map String’
  Expected type: IO (Map.Map String String)
    Actual type: IO [String]
• In the third argument of ‘foldM’, namely ‘lns’
  In the expression:
    foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns
  In the expression:
    let lns = liftM lines $ readFile $ getSet $ listF c
    in foldM (\ m n -> liftM3 Map.insert n n m) (return Map.empty) lns

From that, it seems that return Map.empty tries to wrap the Map in something else than IO monad, which honestly does not make sense to me.

As Haskell never makes mistakes, it is clear to me that I have messed up somewhere, I just can't find, where (though I am pretty sure it is in the second line, as I have checked the lns for the correct type using :t in ghci). I also tried rewriting the function to >>= notation instead of let ... in ..., but it didn't help. Thanks for any help.

kubci98
  • 368
  • 7
  • 20
  • I think you are using `let` instead of `<-` (in a `do` block). Relevant: https://stackoverflow.com/questions/28624408/equal-vs-left-arrow-symbols-in-haskell – chi Apr 21 '18 at 09:10
  • I am not using a do block, nor the >>= notation, that snippet i put here is actually the whole definition. Also, using the do notation gives exactly the same error (plus, as i said, the `lns` variable is actually typed correctly, it is the foldM result that is wrong). – kubci98 Apr 21 '18 at 09:15

2 Answers2

2

The signature of foldM is

foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a

Let's see what you've got:

return Map.empty :: IO (Map String String)
lns :: IO [String]

That definitely does not match parameters a and [b]. Fixing the former is easy: just use Map.empty. For the latter, you need access to what's inside lns, which is what the bind operator is for:

>>= :: m a -> (a -> m b) -> m b

Hence

lns >>= foldM (…) Map.empty

But since you operation is pure, you might as well just use fmap and foldl

foldl (…) Map.empty <$> lns
Regis Kuckaertz
  • 991
  • 5
  • 14
0

Ok, so I have actually managed to fix the code, though I am still not entirely sure where the problem was, so i would be still grateful if someone could point that out.

So, instead of

let lns = liftM lines $ readFile $ getSet $ listF c in
    foldM (\m n -> liftM3 Map.insert n n m) (return Map.empty) lns

I used

(readFile . getSet $ listF c)
    >>= (\tx -> return $ lines tx)
    >>= (\lns -> return $ foldl (\m n -> Map.insert n n m) Map.empty lns)
kubci98
  • 368
  • 7
  • 20
  • 1
    I think you don't understand yet the difference between `f (g x)` (which is equivalent to `let y=g x in f y`) and `g x >>= f`. In IO, the latter runs the IO action `g x`, takes its result, and passes it to `f`, running the action returned by `f`. The former does not run `g x`, but it passes the whole action to `f`, which must have type `f :: IO something -> ...`. In imperative languages, it's (very roughly) the difference between `f(() => g(x))` (the former) and `f(g(x))` (the latter) -- the former does not run `g(x)`. Perhaps a monad tutorial could help, here. – chi Apr 21 '18 at 10:13