Каково предыдущее значение вычисления monadi c в контексте функции? - PullRequest
0 голосов
/ 08 февраля 2020

Я борюсь с экземпляром функции монад (а кто нет?). Монады могут зависеть от значения предыдущего вычисления монади c. Что это точно означает для монады функции?

Когда мы смотрим на голые реализации аппликативных / монадных экземпляров

ap   f g x = f x (g x)
bind f g x = f (g x) x

, кажется, что результат g x составляет этот предыдущий ценность. Однако недавно я наткнулся на следующий пример:

mainA = (\x y z -> (x,y,z)) <$> (2/) <*> (+1) <*> (\x -> x * x)
mainM = (\x -> (\y -> (\z w -> if w == 0 then (0,y,z) else (x,y,z)) =<< (\x -> x * x)) =<< (+1)) =<< (2/)
mainA 0 -- (Infinity,1.0,0.0)
mainM 0 -- (0.0,1.0,0.0)

Поскольку действие monadi c должно возвращать монаду, у нас есть эта дополнительная лямбда w -> ... в определении mainM. Это дает нам доступ к начальному значению, которое передается в общий расчет. Учитывая bind f g x = f (g x) x, означает ли это, что предыдущее значение для экземпляра функции x не является результатом g x?

Извините за путаницу!

1 Ответ

3 голосов
/ 08 февраля 2020

В этом однострочном написании определение mainM довольно трудно следовать, поэтому я начну с добавления отступа:

mainM =
  (\x ->
    (\y -> 
      (\z w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< (\x -> x * x))
    =<< (+1))
  =<< (2/)

Так как monadi c действие должно вернуть монаду, у нас есть эта дополнительная лямбда w -> ... в определении mainM. Это дает нам доступ к начальному значению, которое передается в общее вычисление.

Это в значительной степени правильно. Может быть немного легче увидеть, что происходит, если мы не напишем эту функцию как функцию двух аргументов (функция двух аргументов - это функция одного аргумента, которая возвращает функцию):

mainM =
  (\x ->
    (\y -> 
      (\z ->
        \w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< (\x -> x * x))
    =<< (+1))
  =<< (2/)

Таким образом, результаты применения (2/), (+1) и (\x -> x * x) к начальному параметру / среде связаны с x, y и z соответственно, и три из них используются для получения конечный результат. Если бы нам хотелось последовательно использовать w для окружающей среды, определение выглядело бы так:

mainM =
  (\x ->
    (\y -> 
      (\z ->
        \w -> if w == 0 then (0,y,z) else (x,y,z))
       =<< \w -> w * w)
    =<< \w -> w + 1)
  =<< \w -> 2 / w

Или с использованием (>>=) вместо (=<<):

mainM =
  (\w -> 2 / w) >>= \x ->
  (\w -> w + 1) >>= \y ->
  (\w -> w * w) >>= \z ->
  \w -> if w == 0 then (0,y,z) else (x,y,z)

Или, используя do-notation:

mainM = do
  x <- \w -> 2 / w
  y <- \w -> w + 1
  z <- \w -> w * w
  \w -> if w == 0 then (0,y,z) else (x,y,z)

В любом случае, обратите внимание, что mainM на самом деле не использует результаты предыдущих вычислений (то есть x, y и * 1035). *) решить, какие вычисления выполнять дальше (то есть какую функцию среды использовать). На самом деле мы можем переписать его, используя только аппликатив:

mainA' = (\w x y z -> if w == 0 then (0,y,z) else (x,y,z))
  <*> (2/) <*> (+1) <*> (\x -> x * x)

Использование результатов предыдущего вычисления для выбора следующего будет выглядеть примерно так:

testM = (\x -> if x == 0 then \_ -> 0 else \w -> w / x) =<< subtract 1
GHCi> testM 0.99
-98.99999999999991
GHCi> testM 1
0.0
GHCi> testM 1.01
100.99999999999991

Still монада функция / считыватель не является хорошей иллюстрацией различий между Applicative и Monad, потому что (<*>) и (=<<) оказываются эквивалентными для нее . Например, с помощью testM мы можем просто извлечь аргумент окружения из выражения if ...

testM' = (\x w -> if x == 0 then 0 else w / x) =<< subtract 1

..., получая, таким образом, что-то, что просто переписать, используя Applicative:

testA = (\w x -> if x == 0 then 0 else w / x) <*> subtract 1
...