理解 Reader monad

Understanding the Reader monad

我正在阅读 PureScript by Example 并阅读了介绍 Reader monad 的部分。这个例子是这样的:

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

它似乎凭空创造了一个 Reader

当我阅读有关 State monad 的内容时,它与 get 函数具有相同的概念。以及文字说明:

状态被实现为隐藏在 State monad 的数据构造函数中的函数参数,因此没有显式引用可传递。

我猜这是关键,Reader 也发生了同样的事情,但我不明白它是如何工作的...

当上面的例子是运行通过runReader时,怎么突然出现了提供的值作为ask的结果? ask 的 Haskell 文档说:检索 monad 环境。 但我的困惑是 从哪里 ?在我看来,一个值被传递给 runReader,被存储在 某个地方 ,然后要获取它 - 你调用 ask...感觉。

虽然示例是 PureScript,但我猜任何 Haskell 识字的人也能回答,因此 Haskell 标签。

我目前没有 PureScript 环境,所以我会尝试从 Haskell 的角度回答,希望对您有所帮助。

一个Reader实际上只是一个函数周围的'wrapper',所以当你得到一个Reader r r时,你实际上只得到一个reader 从 rr;换句话说,函数 r -> r.

可以凭空召唤函数,因为,如果你是柏拉图主义者,我想它们总是存在的......

当您使用 do 表示法时,您是 'inside the monad',因此上下文 r 是隐含的。换句话说,您调用一个 returns 值为 r 的函数,当您使用 <- 箭头时,您只需获取该上下文。

您可以通过执行一些替换来说服自己它有效。先看createUser的签名。让我们"unroll"定义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

脱糖为

ask >>= \permissions -> 
  if hasPermission "admin" permissions
  then map Just newUser
  else pure Nothing

要了解它的作用,您必须查找 ask>>=pureReaderT 的定义。让我们进行另一轮替换:

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 包装的函数,它通过您的表达式将一个值("environment")串联起来。 runReader 展开函数并使用提供的参数调用它:

runReader :: forall r a. Reader r a -> r -> a
runReader (ReaderT f) r = f r

部分函数类型 (->) r 是一个函子,即 r->a 是任何类型 a 的容器(大小为 2 的 List a 等同于函数 Bool->a) 。而且,它也是一个monad

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

它满足 MonadReader 类型 class 并且被称为简单的 reader monad 并且可能被赋予类型同义词 Reader r.

ask returns 这个 monad (->) r) r 应用于同一类型 r,然后我们可以绑定它。

更好地理解偏函数类型(->) r