13

我正在阅读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

看起来好像它凭空创造了一个阅读器

当我阅读有关Statemonad 的内容时,它的get功能具有相同的概念。文字解释道:

state 被实现为一个隐藏在 State monad 的数据构造函数中的函数参数,因此没有明确的引用可以传递。

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

当上面的例子通过 运行时runReader,提供的值是如何突然出现的ask?Haskell 文档ask说:检索 monad 环境。但我的困惑来自哪里?在我看来,一个值被传递给runReader,被存储在某个地方,然后得到它 - 你打电话ask......但这没有任何意义。

虽然这个例子是 PureScript,但我猜任何懂 Haskell 的人也能回答,因此有了 Haskell 标签。

4

2 回答 2

13

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

Reader实际上只是一个函数的“包装器”,所以当你得到 a 时,Reader r r你真的只能从rto得到一个 reader r;换句话说,一个函数r -> r

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

当你使用do符号时,你是“在 monad 中”,所以上下文r是隐含的。换句话说,您调用一个返回r值的函数,当您使用<-箭头时,您只需获取该上下文。

于 2017-10-13T21:37:55.557 回答
3

您可以通过执行一些替换来说服自己它有效。首先看签名createUser。让我们“展开” 的定义Reader

createUser :: Reader Permissions (Maybe User)
{- definition of Reader -}
createUser :: ReaderT Permissions Identity (Maybe User)

ReaderT类型只有一个数据构造函数: ReaderT (r -> m a),这意味着createUser是一个计算值为 type 的术语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>>=forpure的定义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
于 2018-02-24T10:42:54.590 回答