J Fritsch
J Fritsch

Reputation: 3358

"No instance for (Monad ..." in if-then-else and guards

I have the following function:

appendMsg :: String -> (String, Integer) -> Map.Map (String, Integer) [String] -> Map.Map (String, Integer) [String]
appendMsg  a (b,c) m = do
      let Just l = length . concat <$> Map.lookup (b,c) m
          l' = l + length a
      --guard $ l' < 1400
      --return $ Map.adjust (++ [a]) (b, c) m
      if l' < 1400 then let m2 = Map.adjust (++ [a]) (b, c) m in return m2 else return (m)

In case l' < 1400 the value m2 should be created and returned, in case l' > 1400 I eventually want to call a second function but for now it is sufficient to return nothing or m in this case.

I started with guards and was immediately running in the error

No instance for (Monad (Map.Map (String, Integer)))
      arising from a use of `return'

Then I tried if-then-else, had to fix some misunderstanding and eventually ended up with the very same error.

I want to know how to fix this. I do understand what a Monad is or that Data in Haskell is immutable. However, after reading two books on Haskell I feel still quite far away from being in a position to code something useful. There is always one more Monad ... :D .

Upvotes: 0

Views: 1089

Answers (2)

Ben
Ben

Reputation: 71545

This looks very much like you're confused about what return does. Unlike in many imperative languages, return is not the way you return a value from a function.

In Haskell you define a function by saying something like f a b c = some_expression. The right hand side is the expression that the function "returns". There's no need for a "return statement" as you would use in imperative languages, and in fact it wouldn't make sense. In imperative languages where a function is a sequence of statements that are executed, it fits naturally to determine what result the function evaluates to with a return statement which terminates the function and passes a value back up. In Haskell, the right hand side of your function is a single expression, and it doesn't really make sense to suddenly put a statement in the middle of that expression that somehow terminates evaluation of the expression and returns something else instead.

In fact, return is itself a function, rather than a statement. It was perhaps poorly named, given that it isn't what imperative programmers learning Haskell would expect. return foo is how you wrap foo up into a monadic value; it doesn't have any effect on flow control, but just evaluates to foo in the context of whatever monad you're using. It doesn't look like you're using monads at all (certainly the type for your function is wrong if you are).

It's also worth noting that if/then/else isn't a statement in Haskall either; the whole if/then/else block is an expression, which evaluates to either the then expression or the else expression, depending on the value of the condition.

So take away the do, which just provides convenient syntax for working with monads. Then you don't need to return anything, just have m2 as your then branch (with the let binding, or since you only use m2 once you could just replace your whole then expression with Map.adjust (++ [a]) (b, c) m), and m in your else branch. You then wrap that in a let ... in ... block to get your bindings of l and l'. And the whole let ... in ... construct is also an expression, so it can just be the right hand side of your function definition as-is. No do, no return.

Upvotes: 4

Ingo
Ingo

Reputation: 36349

You state that the result of your function is

Map.Map (String, Integer) [String]

and, through the use of return, state that it is also monadic. Hence the compiler thinks there should be an Monad instance for Map (String, Integer).

But I am quite sure this will compile if you drop return and do and write in before the if. Or at least it will give you more meaningful error messages.

Upvotes: 4

Related Questions