Возможно строительство монады

В настоящее время я борюсь с новым элементом Haskell: монадами. Поэтому я познакомился с этим на примере создания оператора (>>=), который выполняет функцию для типа Maybe (принимая его фактическое целочисленное значение в качестве аргумента), только если оно не равно Nothing, и в противном случае возвращает Nothing:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x

Однако я не совсем уверен, как это работает при следующем его использовании:

eval (Val n) = Just n
eval (Div x y) = eval x >>= (\n ->
    eval y >>= (\m ->
        safediv n m))

Мне кажется, что оператор (>>=) просто принимает одно значение Maybe и функцию, которая его возвращает, однако в этом примере кода использования кажется, что он принимает 2 раза значение Maybe и один раз функцию. Однако мне сказали, что он оценивает x, помещает результат в n, затем оценивает y, помещает результат в y, а затем выполняет функцию safediv на обоих. Хотя я не понимаю, какую роль здесь играет оператор (>>=); Как это работает?


person user2999349    schedule 07.10.2014    source источник


Ответы (3)


Вы можете прочитать это так:

eval (Div x y) = eval x >>= (\n ->
    eval y >>= (\m ->
        safediv n m))

когда хочешь сделать eval (Div x y)тогда

  • первый eval x:
    • if was Just n (using the first >>=)
    • then take the n and have a look at eval y (using the first >>=)
      • if the last is Just m (second >>=)
      • then take the m and do a (second >>=)
      • savediv n m to return it's result - you still have the n from your closure!.

в любом другом случае возвращается Nothing

Итак, (>>=) просто помогает вам деконструировать.

Может быть, проще читать и понимать в форме do:

eval (Val n) = Just n
eval (Div x y) = do
    n <- eval x
    m <- eval y
    safediv n m

это просто синтаксический сахар вокруг (>>=)

давайте разберемся с кейсами:

1. eval x = Nothing and eval y = Nothing:
eval x >>= (...) = Nothing >>= (...) = Nothing
2. eval x = Nothing and eval y = Just n:

что то же самое:

eval x >>= (...) = Nothing >>= (...) = Nothing
3. eval x = Just n and eval y = Nothing:
eval x >>= (\n -> eval y >>= (...))
= Just n >>= (\n -> eval y >>= (...)) 
= Just n >>= (\n -> Nothing)
= Nothing
4. eval x = Just n and eval y = Just m:
eval x >>= (\n -> Just m >>= (...))
= Just n >>= (\n -> Just m >>= (...)) 
= Just n >>= (\n -> Just m >>= (\m -> safediv n m))
= (first >>= for Just) = Just m >>= (\n -> safediv n m)
= (second >>= for Just) = safediv n m
person Random Dev    schedule 07.10.2014
comment
Я тоже так думал, но я не понимаю, как это работает с функцией (›› =), которая используется дважды, сначала (как я это читал) для значения с другим значением, а затем снова в результате этого и функция. Однако оператор принимает только значение и функцию? - person user2999349; 07.10.2014
comment
@ user2999349 он просто использовал, чтобы сначала получить n от Just n, а затем второй раз, чтобы получить m от Just m - person Random Dev; 07.10.2014
comment
@ user2999349 Это два отдельных приложения >>=. У внутреннего есть аргументы eval y и (\m -> safediv n m), а у внешнего - в качестве аргументов eval x и (\n -> eval y >>= (\m -> safediv n m)) - person bheklilr; 07.10.2014

Давайте займемся поиском элементов, чтобы проиллюстрировать, как это работает. Если у нас есть

eval (Div (Val 5) (Div (Val 0) (Val 1)))

Тогда мы можем разбить это на

eval (Div (Val 5) (Div (Val 0) (Val 1)))
    = eval (Val 5) >>=
        (\n ->
            eval (Div (Val 0) (Val 1)) >>=
                (\m ->
                    safediv n m
                )
        )

-- eval (Val 5) = Just 5

    = Just 5 >>=
        (\n ->
            eval (Div (Val 0) (Val 1)) >>=
                (\m ->
                    safediv n m
                )
        )

-- Just x >>= f = f x

    = (\n ->
        eval (Div (Val 0) (Val 1)) >>=
            (\m ->
                safediv n m
            )
      ) 5

-- Substitute n = 5, since the 5 is the argument to the `\n ->` lamba

    = eval (Div (Val 0) (Val 1)) >>=
        (\m ->
            safediv 5 m
        )

Теперь нам нужно сделать обходной путь, чтобы вычислить _3 _...

eval (Div (Val 0) (Val 1))
    = eval (Val 0) >>=
        (\n ->
            eval (Val 1) >>=
                (\m ->
                    safediv n m
                )
        )

-- eval (Val 0) = Just 0
-- eval (Val 1) = Just 1

eval (Div (Val 0) (Val 1))
    = Just 0 >>=
        (\n ->
            Just 1 >>=
                (\m ->
                    safediv n m
                )
        )

-- Just x >>= f = f x

eval (Div (Val 0) (Val 1))
    = (\n ->
        (\m ->
            safediv n m
        ) 1
      ) 0

    = (\n -> safediv n 1) 0
    = safediv 0 1
    = Just 0

А теперь вернемся к нашему исходному вызову eval, заменив Just 0 в:

eval (Div (Val 5) (Div (Val 0) (Val 1)))
    = Just 0 >>= (\m -> safediv 5 m)

-- Just x >>= f = f x

eval (Div (Val 5) (Div (Val 0) (Val 1)))
    = safediv 5 0

-- safediv x 0 = Nothing

eval (Div (Val 5) (Div (Val 0) (Val 1)))
    = Nothing
person bheklilr    schedule 07.10.2014

у вас есть

eval (Val n) = Just n

из этого мы заключаем, что eval производит значение Maybe. Второе уравнение, давайте перепишем его как

eval (Div x y) = 
  eval x >>= (\n ->
                    eval y >>= (\m ->
                                      safediv n m ) )

i.e.

eval (Div x y) = 
  eval x >>= g 
             where
             g n =  eval y >>= h 
                               where
                               h m =  safediv n m

Видеть? В каждом >>= приложении задействована только одна функция. Вверху это g. Но g определяет и использует h, поэтому тело h имеет доступ как к своему аргументу m, так и к аргументу g, n.

Если eval x произвел Nothing, то eval x >>= g будет просто Nothing, немедленно, в соответствии с определением >>= для Maybe типов (Nothing >>= _ = Nothing), и попытки eval y выполняться не будут.

Но если это было (Just ...), то его значение просто передается связанной функции (Just x >>= f = f x).

Таким образом, если оба eval производят Just ... значений, safediv n m вызывается внутри области, где доступны оба аргумента n и m. Это вероятно определяется как

safediv :: Num a => a -> a -> Maybe a
safediv n m | m == 0    =  Nothing
            | otherwise =  Just (div n m)    -- or something

и так h :: m -> Maybe m и g :: n -> Maybe n и типы подходят,

-- assuming a missing type of "expressions", `Exp a`,
eval :: Num a => Exp a ->                                       Maybe a    
  -- Num a is assumed throughout, below
  eval (Div x y) =                                           -- Maybe a
  -- Maybe a >>= a ->                                           Maybe a
      eval x >>= g 
                 where
  --               a ->                                         Maybe a
  --                   Maybe a >>= a ->                         Maybe a 
                 g n =  eval y >>= h
                                   where
  --                                 a ->                       Maybe a
                                   h m =  safediv    n    m  -- Maybe a
  --                                      safediv :: a -> a ->  Maybe a

согласно типу привязки для монады Maybe,

(>>=) :: Maybe a -> 
              (a -> Maybe b) -> 
         Maybe            b
person Will Ness    schedule 07.10.2014