Понимание монады читателя
Я читаю Purescript на примере и попал в часть, представляющую монаду Reader. Пример выглядит так:
createUser :: Reader Permissions (Maybe User)
createUser = do
permissions <- ask
if hasPermission "admin" permissions
then map Just newUser
else pure Nothing
Запутанная часть для меня это ask
функция. Подпись:
ask :: forall r. Reader r r
Похоже, что он создает читателя из воздуха
Когда я читал о State
монада, он имел ту же концепцию с его get
функция. И текст объяснил:
состояние реализовано как аргумент функции, скрытый конструктором данных монады State, поэтому нет явной ссылки для передачи.
Я предполагаю, что это ключ, и то же самое происходит здесь с Reader, но я не понимаю, как это работает...
Когда приведенный выше пример запускается через runReader
Каким образом предоставленное значение внезапно появляется в результате ask
? Документы на Haskell для ask
говорят: извлекает среду монады. Но мое замешательство откуда? Как я вижу, значение передается runReader
, хранится где-то, а чтобы получить - звоните ask
... но это не имеет смысла.
В то время как пример Purescript, я предполагаю, что любой грамотный человек на Haskell также сможет ответить, отсюда и тэг Haskell.
3 ответа
В настоящее время у меня нет среды PureScript, поэтому я постараюсь ответить с точки зрения Haskell и надеюсь, что это поможет.
Reader - это всего лишь "обертка" вокруг функции, поэтому, когда вы получаете Reader r r
Вы действительно получаете только читателя из r
в r
; другими словами, функция r -> r
,
Вы можете вызывать функции из воздуха, потому что, если вы платонист, я полагаю, они всегда существуют...
Когда вы используете do
нотация, вы "внутри монады", поэтому контекст r
неявно. Другими словами, вы вызываете функцию, которая возвращает r
значение, и когда вы используете <-
стрелка, вы просто получите этот контекст.
Вы можете убедить себя, что это работает, выполнив несколько замен. Сначала посмотрите на подпись createUser
, Давайте "развернем" определение Reader
:
createUser :: Reader Permissions (Maybe User)
{- definition of Reader -}
createUser :: ReaderT Permissions Identity (Maybe User)
ReaderT
Тип имеет только один конструктор данных: ReaderT (r -> m a)
, что значит createUser
это термин, который оценивает значение типа ReaderT (Permissions -> Identity (Maybe User))
, Как видите, это просто функция, помеченная ReaderT
, Он не должен создавать что-либо из воздуха, но получит значение типа Permissions
когда эта функция вызывается.
Теперь давайте посмотрим на линию, с которой у вас проблемы. Вы знаете, что do
обозначение просто синтаксический сахар, а выражение:
do permissions <- ask
if hasPermission "admin" permissions
then map Just newUser
else pure Nothing
Desugars для
ask >>= \permissions ->
if hasPermission "admin" permissions
then map Just newUser
else pure Nothing
Чтобы понять, что это делает, вам придется поискать определение ask
, >>=
а также pure
за ReaderT
, Давайте выполним еще один раунд замен:
ask >>= \permissions -> ...
{- definition of ask for ReaderT -}
ReaderT pure >>= \permissions -> ...
{- definition of >>= for ReaderT -}
ReaderT \r ->
pure r >>= \a -> case (\permissions -> ...) a of ReaderT f -> f r
{- function application -}
ReaderT \r ->
pure r >>= \a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r
{- definition of pure for Identity -}
ReaderT \r ->
Identity r >>= \a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r
{- definition of >>= for Identity -}
ReaderT \r ->
(\a ->
case (if hasPermission "admin" a
then map Just newUser
else pure Nothing) of ReaderT f -> f r) r
{- function application -}
ReaderT \r ->
case (if hasPermission "admin" r
then map Just newUser
else pure Nothing) of ReaderT f -> f r
Как вы видете, createUser
это явно просто функция, обернутая ReaderT
это пропускает значение ("окружение") через ваши выражения. runReader
разворачивает функцию и вызывает ее с указанным аргументом:
runReader :: forall r a. Reader r a -> r -> a
runReader (ReaderT f) r = f r
Частичный тип функции является функтором, т.е.
r->a
является контейнером для любого типа
a
(а
List a
размера два эквивалентна функции
Bool->a
). Более того, это еще и монада
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
Он удовлетворяет
MonadReader
класс типа и называется простой монадой чтения, и ему может быть присвоен синоним типа
Reader r
.
ask
возвращает эту монаду
(->) r) r
применяется к тому же типу
r
, который мы затем можем связать.
Понимание частичного типа функции
(->) r
лучше.