Читатель Монады разъяснения

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

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

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))какова его цель?

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

3 ответа

Решение

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

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

data Foo = Bar | Baz

Это определение связывает три имени, Foo, Bar, а также Baz, Тем не менее, часть в левой части знака равенства связана в пространстве имен типа, так как Foo является типом, и конструкторы с правой стороны связаны в пространстве имен значения, так как Bar а также Baz по сути ценности.

Все эти вещи также имеют типы, которые могут быть полезны для визуализации. Foo имеет вид, который по сути является "типом вещи уровня типа", и 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 это 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 в приведенном выше определении.

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

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

Другие вопросы по тегам