简体   繁体   中英

How does the operator <- in haskell behave?

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

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 >> . Eg 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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM