2

I understand (somewhat) monads and understand that the operator <- will extract the value from the monad.

But how does it work with different types?

Typically, I have seen it being used to extract strings from IO monad. But in the example code below am not able to see why it fails in main 3rd line, complaining that it is expecting a type of IO int? How is the compiler infering that an IO int is needed?

Also what does it (<-) do in the multWithLog method?

import Control.Monad.Trans.Writer.Lazy

main = do
   putStrLn $ show $ logNumber 3 -- prints WriterT (Identity (3,["Got Number: 3"]))
   putStrLn $ show $ multWithLog -- prints WriterT (Identity (3,["Got Number: 3"]))
    _ <- logNumber 3 -- fails with Couldn't match type ‘WriterT [String] Data.Functor.Identity.Identity’ with ‘IO’
                    -- Expected type: IO Int
                    -- Actual type: Writer [String] Int
   putStrLn "test"


logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got Number: " ++ show x])

multWithLog :: Writer [String] Int
multWithLog = do
  a <- logNumber 3
  --b <- logNumber 5
  return a
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
peeyush singh
  • 1,337
  • 1
  • 12
  • 23
  • all the values to the right of `<-` in the same `do` block must be of type `Monad m => m _`, for the same `m`. – Will Ness Sep 04 '18 at 13:10
  • Thanks all, I guess where I was tripping again and again was forgetting that the bind takes a method which will take a normal value (from within monad m) and then it needs to wrap it back (after processing) in the same type of monad m. In my mind I sort of forgot <- is a syntactic shortcut for bind and was thinking as if <- is more of a haskell operator which "extracts" some value (as per the context of monad) and leaves me free to do what I want with this normal value (which is not correct, since I am bound to call a method which has the constraint to return the result in wrapped in moand m. – peeyush singh Sep 05 '18 at 02:31
  • the point here is that you are extracting them from the *same* monad each time, while in the *same* `do` block. so trying to extract from different monads in the same `do` block doesn't work. *that* was your problem here, not what you wrote above. also, it's not always *you* that "wraps" the value; sometimes - like you have in your code, too - you are using a primitive of a given monad, like `putStrLn :: String -> IO ()`, that returns a monadic action value all by itself. this "wrapping" thing is not a good metaphor. – Will Ness Sep 05 '18 at 07:05

3 Answers3

5

Every statement in a do block must be from the same monadic type.

In

multWithLog = do
  a <- logNumber 3
  return a

we have logNumber 3 :: Writer [String] Int and return a :: (Monad m) => m Int (which is polymorphic), so the whole thing typechecks as Writer [String] Int (with m = Writer [String], which is a monad).

In

main = do
   putStrLn $ show $ logNumber 3
   putStrLn $ show $ multWithLog
    _ <- logNumber 3
   putStrLn "test"

we have putStrLn ... :: IO () and logNumber 3 :: Writer [String] Int. This is a type error because Writer [String] is not the same as IO.

The underlying reason is that do blocks are just syntactic sugar for calls to >>= and >>. E.g. your main really means

main =
   (putStrLn $ show $ logNumber 3) >>
   (putStrLn $ show $ multWithLog) >>
   logNumber 3 >>= \_ ->
   putStrLn "test"

with

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

which requires the type m to remain the same throughout.

melpomene
  • 84,125
  • 8
  • 85
  • 148
3

Be careful with wording such as

extract the value from the monad

A monad doesn't contain 'a' value. For example, Maybe contains zero or one value. List ([]) contains multiple values. See this answer for more details.

In the list case, for example, the <- operator extracts each of the list values, one at a time.

When you use do notation, all values extracted must belong to the same Monad. In the OP, the compiler infers that the Monad in question is IO, since putStrLn returns IO () values.

logNumber, on the other hand, returns Writer [String] Int values, and while that's also a Monad instance, it's not the same as IO. Therefore, the code doesn't type check.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • @WillNess I don't object to the word *extract*; I warn again the articles *the* and *a*. – Mark Seemann Sep 04 '18 at 13:11
  • I'm referring to the different sentence, this one: "all values extracted must belong to the same Monad" – Will Ness Sep 04 '18 at 13:12
  • @WillNess Ah, would it be more correct to say that *all values extracted must originate from the same Monad?* Or something like that... – Mark Seemann Sep 04 '18 at 13:14
  • maybe "all Monadic action values (those to the right of `<-`, or on a line of their own) must belong to the same Monad". – Will Ness Sep 04 '18 at 13:37
1

Kepping things simple. These are two facts

  1. Writer [String] is actually monad, so Writer [String] Int can be seen as m Int
  2. Every action in a do block should happen within the same monad.

In the main function the compiler is reasoning as follows:

  1. I am working within the IO monad since putStrLn ... is of type IO ()
  2. Let me compute _ <- logNumber 3. Since I am in the IO monad, logNumber 3 should be IO WhatEver
  3. logNumber 3 is actually a monadic value of type m Int
  4. Wait! m is the Writer [String] monad, not the IO monad
  5. Print an error saying that Writer [String] Int is incorrect an It should be IO Int

So that is where IO Int comes from. I am just triying to be pedagogic here. Check @melpomene's answer for a complete explanation

Hope it helps.

lsmor
  • 4,698
  • 17
  • 38