Правильное использование записи do - PullRequest
4 голосов
/ 01 ноября 2011

Я пытаюсь понять Монаду, и у меня есть следующий код

f a b c d =
   do one <- a + b
      two <- c * d
      three <- one + two
      return three

Вышеуказанные компиляции

но выдается ошибка, когда я

*Main> f 1 2 3 4

:1:1:
    No instances for (Num (a0 -> t0), Monad ((->) a0), Monad ((->) t0))
      arising from a use of `f'
    Possible fix:
      add instance declarations for
      (Num (a0 -> t0), Monad ((->) a0), Monad ((->) t0))
    In the expression: f 1 2 3 4
    In an equation for `it': it = f 1 2 3 4

:1:9:
    No instance for (Num (a0 -> a0 -> t0))
      arising from the literal `4'
    Possible fix:
      add an instance declaration for (Num (a0 -> a0 -> t0))
    In the fourth argument of `f', namely `4'
    In the expression: f 1 2 3 4
    In an equation for `it': it = f 1 2 3 4

Я думаю, я был бы на шаг ближе к пониманию Monad, если бы я знал, почему вышеприведенный код не работает на
f 1 2 3 4

Ответы [ 5 ]

8 голосов
/ 01 ноября 2011

Я собираюсь не согласиться со всеми и сказать, что то, что вы делаете, почти наверняка просто не имеет отношения к монадам .Вы, вероятно, просто хотите использовать вместо этого какой-нибудь скучный старый код:

f a b c d = three where
    one = a + b
    two = c * d
    three = one + two

или более кратко:

f a b c d = a + b + c * d
8 голосов
/ 01 ноября 2011

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

Первое, что нужно знать, это то, что нотация синтаксического сахара для обычных вызовов функций (>>= и >>).Таким образом, это поможет увидеть, что ваш код тоже не подходит.

Давайте попробуем что-нибудь попроще

 f a b =
   do one <- a + b
      return one

Это та же проблема, что и ваш код, но проще.Чтобы понять, почему это не работает, мы спрашиваем: что это на самом деле означает?Что ж, мы можем переписать символ <-, используя >>=

 f a b = (a + b) >>= \x -> return x

(это не самое простое представление, но проясняет суть)

Если вы проверяете следующее в GHCi

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

, то есть функция >>= принимает: аргумент типа m из a и функцию от a до m из b и возвращает m из b.

А что в этом коде?

(a + b)

Будет числом.Как насчет другой половины

 \x -> return x

Берет объект типа a и возвращает объект типа m a для любого a

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

Один хороший способ примириться с монадами - это взглянуть на некоторые конкретные примеры.

Монада Maybe выражает вычисления, которые могут потерпеть неудачу

 instance Monad Maybe where
      return = Just
      (>>=) (Just a) f = f a
      (>>=) Nothing _ = Nothing

Это позволяет вам говорить вещи с шаблоном типа

 f args = do x <- functionThatMightFail args
             y <- anotherfunctionThatMightFail x
             return y

или с тем же кодом более просто

f args = do x <- functionThatMightFail args
            anotherfunctionThatMightFail x

или, возможно,

f args = functionThatMightFail args >>= anotherfunctionThatMightFail

С другой стороны, монада List отражает идею выполнения одной и той же функции для каждого элемента списка, а затем объединяет результаты вместе.Простых примеров предостаточно:

f = do x <- [1,2,3,4]
       [1..x]

Если вы понимаете эти примеры, поиграйте с монадой State.Это поможет вам получить более общее представление о том, что «монады являются моделями вычислений».Я бы тогда проверил Parsec, и, конечно, IO

3 голосов
/ 01 ноября 2011

Я думаю, вы не понимаете, для чего используется <-. Этот оператор «распаковывает» данные из монады; монада IO в этом случае. Например, если вы позвоните readLn, вы получите результат типа IO a. Если вы хотите использовать a внутри монады, вы можете поместить input <- readLn в конструкцию do, и это связывает значение a с input. Однако значение a + b не находится внутри монады ввода-вывода или какой-либо другой монады. В вашем примере использования a + b просто имеет тип Int. Поэтому, если вы хотите объявить переменную, равную этому, вы используете оператор let: let one = a + b. То же самое относится и к другим вашим заявлениям. Таким образом, вы хотели бы переписать эту функцию как:

f a b c d = do
    let one = a + b
    let two = c * d
    let three = one + two
    return three

Дополнительное примечание: добавление сигнатур типов часто помогает отлаживать подобные вещи. Если я добавлю эту сигнатуру типа в исходную функцию: f :: Int -> Int -> Int -> Int -> IO Int, я получу гораздо более полезную ошибку, которая говорит, что ожидаемый тип был IO t0, но фактический тип был Int в выражении one <- a + b. Это должно помочь вам понять, что <- ожидает монадическое значение в качестве правого аргумента, но вместо этого он получил Int.

1 голос
/ 01 ноября 2011

Если вы пытаетесь использовать монаду Maybe для обработки ошибок, вот рабочий пример:

module Bbb where

import Data.Maybe

f a b c d =
   do one <- return $ a + b
      two <- return $ c * d
      three <- return $ one + two
      return three

main = print $ fromJust $ f 1 2 3 4

Предложение import Data.Maybe предназначено для fromJust, то есть просто

fromJust (Just x) = x

Если вы просто пытаетесь интерпретировать <- как назначения, не надо:)

1 голос
/ 01 ноября 2011

Какую монаду вы пытаетесь использовать?

Самое близкое совпадение с тем, что вы пытаетесь сделать, - это монада функции ((->) a) (отсюда и ошибки GHC), но поскольку вы предоставляете оба аргументана каждом этапе это не работает.

Не пытайтесь понять монады в целом, просто разбивая код на блоки do: понять, как использовать конкретную монаду (например, узнать, как использовать IO, узнатькак использовать библиотеку монадического анализа и т. д.), а затем понять общность, которая у них есть, и как работает абстракция монады.

Каждая монада имеет свои уникальные характеристики и метод работы (потому что, если они неt, тогда какой смысл иметь более одного?).

Мне особенно нравится подход Real World Haskell к этому: различные монады вводятся повсюду с помощью вспомогательных комбинаторов, чтобы помочь управлять шаблоном, а затем в Глава 7 все это собрано вместе, и класс типов Monad официально представлен.

...