Уточнение монады чтения

Я пытаюсь понять монаду читателя, но, кажется, не могу понять, что делает bind (>>=) в этой монаде.

Вот реализация, которую я анализирую:

newtype Reader e a = Reader { runReader :: (e -> a) }

instance Monad (Reader e) where 
    return a         = Reader $ \e -> a 
    (Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e
  1. Мой первый вопрос: почему Reader частично применяется в левой части привязки? (Reader r) вместо (Reader r a).
  2. Что происходит в этой части определения: (f (r e)), какова ее цель?

Большое спасибо за помощь.


person oskar132    schedule 21.01.2018    source источник


Ответы (3)


Мой первый вопрос: почему Reader частично применяется в левой части привязки? (Reader r) вместо (Reader r a).

Это не так. Это использование Reader полностью насыщено, как и должно быть. Тем не менее, я могу понять ваше замешательство... помните, что в Haskell типы и значения находятся в разных пространствах имен, и определение типа данных с помощью data или newtype приводит к тому, что новые имена становятся доступными в обоих пространствах имен. Например, рассмотрим следующее объявление:

data Foo = Bar | Baz

Это определение связывает три имени: Foo, Bar и Baz. Однако часть слева от знака равенства связана в пространстве имен типов, поскольку Foo — это тип, а конструкторы справа связаны в пространстве имен значений, поскольку Bar и Baz по сути являются значениями.

Все эти вещи также имеют типы, которые могут быть полезны для визуализации. Foo имеет тип kind, который по сути является «типом объекта уровня типа», а Bar и Baz оба имеют тип. Эти типы можно записать следующим образом:

Foo :: *
Bar :: Foo
Baz :: Foo

…где * — тип типов.

Теперь рассмотрим немного более сложное определение:

data Foo a = Bar Integer String | Baz a

Опять же, это определение связывает три имени: Foo, Bar и Baz. И снова Foo находится в пространстве имен типов, а Bar и Baz — в пространстве имен значений. Однако их типы более сложны:

Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a

Здесь Foo — это конструктор типа, поэтому, по сути, это функция уровня типа, которая принимает тип (*) в качестве аргумента. Между тем, Bar и Baz — это функции уровня значений, которые принимают различные значения в качестве аргументов.

Теперь вернемся к определению Reader. На мгновение избегая синтаксиса записи, мы можем переформулировать его следующим образом:

newtype Reader r a = Reader (r -> a)

Это связывает одно имя в пространстве имен типов и одно имя в пространстве имен значений, но сбивает с толку то, что они оба называются Reader! Однако это полностью разрешено в Haskell, поскольку пространства имен разделены. В этом случае каждый Reader также имеет вид/тип:

Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a

Обратите внимание, что уровень типов Reader принимает два аргумента, а уровень значений Reader принимает только один. Когда вы сопоставляете шаблон со значением, вы используете конструктор уровня значения (поскольку вы деконструируете значение, созданное с помощью того же конструктора), и значение содержит только одно значение (как и должно быть, поскольку Reader является a newtype), поэтому шаблон связывает только одну переменную.


Что происходит в этой части определения: (f (r e)), какова его цель?

Reader — это, по сути, механизм для составления множества функций, которые принимают один и тот же аргумент. Это способ избежать необходимости передавать значение повсюду, так как различные экземпляры будут выполнять сантехнику автоматически.

Чтобы понять определение >>= для Reader, давайте специализируем тип от >>= до Reader:

(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b

Для ясности мы также можем расширить Reader r a до r -> a, просто чтобы лучше понять, что на самом деле означает этот тип:

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)

Давайте также назовем аргументы здесь для целей обсуждения:

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
(>>=)    f           g             =  ...

Давайте задумаемся об этом на мгновение. Нам даны две функции, f и g, и ожидается, что мы создадим функцию, которая производит значение типа b из значения типа a. У нас есть только один способ создать b — вызвать g. Но чтобы позвонить g, у нас должен быть a, а у нас есть только один способ получить a: позвонить f! Мы можем вызвать f, так как ему нужен только r, который у нас уже есть, поэтому мы можем начать связывать функции вместе, чтобы создать нужный нам b.

Это немного сбивает с толку, поэтому может помочь визуально увидеть этот поток значений:

          +------------+
          | input :: r |
          +------------+
             |       |
             v       |
+--------------+     |
| f input :: a |     |
+--------------+     |
       |             |
       v             v
  +------------------------+
  | g (f input) input :: b |
  +------------------------+

В Haskell это выглядит так:

f >>= g = \input -> g (f input) input

… или, немного переименовав вещи, чтобы они соответствовали определению в вашем вопросе:

r >>= f = \e -> f (r e) e

Теперь нам нужно снова ввести некоторую обертку и распаковку, поскольку реальное определение относится к типу Reader, а не напрямую к (->). Это означает, что нам нужно добавить некоторые варианты использования обертки Reader и развертки runReader, но в остальном определение такое же:

Reader r >>= f = Reader (\e -> runReader (f (r e)) e)

На этом этапе вы можете проверить свою интуицию: Reader — это способ передать значение между множеством функций, и здесь мы составляем две функции, r и f. Следовательно, нам нужно передать значение дважды, что мы и делаем: в приведенном выше определении есть два варианта использования e.

person Alexis King    schedule 21.01.2018

Чтобы ответить на вашу функцию, Monad является классом над типами вида * -> *.

  • [Int] :: * = список целых чисел
  • [] :: * -> * = список
  • Reader e a :: * = считыватель со средой e, что приводит к
  • Reader e :: * -> * = считыватель с окружением e
  • Reader :: * -> * -> * = читатель

Мы ошибаемся, когда говорим, что Reader имеет экземпляр монады, когда мы имеем в виду, что Reader в любой среде имеет экземпляр монады.

(Аналогично, Writer w — это Monad, когда w — это Monoid, а Writer — это не Monad).


Чтобы ответить на ваш второй вопрос, проще думать о Reader e a как о функции e -> a.

Функция имеет такое же определение монады, но без newtype обёрток и распаковщиков. Подумайте Reader = (->):

instance Monad ((->) e) where
    return x = \_ -> x -- alternatively, return = const !
    r >>= f  = \e -> f (r e) e

Тем не менее, определение join, вероятно, является наиболее проницательным:

join :: Reader e (Reader e a) -> Reader e a

Но с голой функцией:

join :: (e -> e -> a) -> (e -> a)
join f e = f e e

И если мы напишем это для Reader, мы должны добавить runReader и Reader в нужных местах (и перенести привязку переменной к лямбде на RHS):

join f = Reader $ \e -> runReader (runReader f e) e
person phadej    schedule 21.01.2018

  1. Частичного применения нет. Reader – это определение newtype, которое "обертывает" ровно одно значение (runReader), которое является функцией типа e -> a. Таким образом, Reader r просто соответствует шаблону функции из оболочки Reader. r привязан к функции типа e -> a.

  2. f — это функция, как и r. r e вызывает функцию r со значением e, а затем вызывается f с результатом вызова этой функции.

person Mark Seemann    schedule 21.01.2018