Как ведет себя оператор ‹- в haskell?

Я понимаю (несколько) монады и понимаю, что оператор ‹- будет извлекать значение из монады.

Но как это работает с разными типами?

Как правило, я видел, как он используется для извлечения строк из монады IO. Но в приведенном ниже примере кода я не могу понять, почему он не работает в основной 3-й строке, жалуясь, что он ожидает тип ввода-вывода int? Как компилятор делает вывод, что требуется IO int?

И что он (<-) делает в методе multWithLog?

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

person peeyush singh    schedule 04.09.2018    source источник
comment
все значения справа от <- в одном блоке do должны быть типа Monad m => m _ для одного и того же m.   -  person Will Ness    schedule 04.09.2018
comment
Всем спасибо, я догадываюсь, где я снова и снова спотыкался, забывая, что привязка принимает метод, который будет принимать нормальное значение (из монады m), а затем его нужно обернуть обратно (после обработки) в монаду того же типа. м. На мой взгляд, я как бы забыл ‹- это синтаксический ярлык для связывания и думал, что ‹- это скорее оператор haskell, который извлекает какое-то значение (в соответствии с контекстом монады) и дает мне свободу делать то, что я хочу с это нормальное значение (что неверно, так как я обязан вызвать метод, который имеет ограничение на возврат результата, завернутого в moand m.   -  person peeyush singh    schedule 05.09.2018
comment
дело здесь в том, что вы каждый раз извлекаете их из одной и той же монады, находясь в одном и том же do блоке. поэтому попытка извлечения из разных монад в одном блоке do не работает. в этом была ваша проблема, а не в том, что вы написали выше. кроме того, не всегда вы упаковываете значение; иногда - как и в вашем коде - вы используете примитив данной монады, такой как putStrLn :: String -> IO (), который сам по себе возвращает монадическое значение действия. эта обертка не является хорошей метафорой.   -  person Will Ness    schedule 05.09.2018


Ответы (3)


Каждый оператор в блоке do должен относиться к одному и тому же монадическому типу.

In

multWithLog = do
  a <- logNumber 3
  return a

у нас есть logNumber 3 :: Writer [String] Int и return a :: (Monad m) => m Int (полиморфные), поэтому все это проверяется как Writer [String] Intm = Writer [String], которое является монадой).

In

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

у нас есть putStrLn ... :: IO () и logNumber 3 :: Writer [String] Int. Это ошибка типа, потому что Writer [String] не совпадает с IO.

Основная причина в том, что блоки do — это просто синтаксический сахар для вызовов >>= и >>. Например. твой main действительно означает

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

с

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

что требует, чтобы тип m оставался неизменным повсюду.

person melpomene    schedule 04.09.2018

Будьте осторожны с такими формулировками, как

извлечь значение из монады

Монада не содержит значения 'a'. Например, Maybe содержит ноль или одно значение. Список ([]) содержит несколько значений. Подробнее см. в этом ответе.

Например, в случае списка оператор <- извлекает каждое значение списка по одному.

При использовании нотации do все извлеченные значения должны принадлежать одному и тому же Monad. В OP компилятор делает вывод, что рассматриваемое Monad равно IO, поскольку putStrLn возвращает IO () значений.

logNumber, с другой стороны, возвращает Writer [String] Int значений, и хотя это тоже экземпляр Monad, это не то же самое, что IO. Поэтому код не печатает check.

person Mark Seemann    schedule 04.09.2018
comment
@WillNess Я не возражаю против слова извлечь; Предупреждаю еще раз статьи the и a. - person Mark Seemann; 04.09.2018
comment
Я имею в виду другое предложение, вот это: все извлеченные значения должны принадлежать одной и той же монаде. - person Will Ness; 04.09.2018
comment
@WillNess Ах, было бы правильнее сказать, что все извлеченные значения должны происходить из одной и той же монады? Или что-то в этом роде... - person Mark Seemann; 04.09.2018
comment
возможно, все монадические значения действия (те, что справа от <- или в отдельной строке) должны принадлежать одной и той же монаде. - person Will Ness; 04.09.2018

Все просто. Это два факта

  1. Writer [String] на самом деле является монадой, поэтому Writer [String] Int можно рассматривать как m Int
  2. Каждое действие в блоке do должно происходить внутри одной и той же монады.

В функции main компилятор рассуждает следующим образом:

  1. Я работаю внутри монады IO, так как putStrLn ... имеет тип IO ()
  2. Позвольте мне вычислить _ <- logNumber 3. Поскольку я в монаде IO, logNumber 3 должно быть IO WhatEver
  3. logNumber 3 на самом деле является монадическим значением типа m Int.
  4. Ждать! m — это монада Writer [String], а не монада IO
  5. Вывести сообщение об ошибке, говорящее, что Writer [String] Int неверно, должно быть IO Int

Так вот откуда IO Int. Я просто пытаюсь быть педагогом здесь. Проверьте ответ @melpomene для полного объяснения

Надеюсь, поможет.

person lsmor    schedule 04.09.2018