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