Мой первый вопрос: почему 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