【问题标题】:Understanding the Reader monad理解 Reader monad
【发布时间】:2018-03-26 01:52:26
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • Reader a ba -&gt; b 的包装。所以,ask :: forall a. Reader a a 最终只是一个 forall a. a -&gt; a 类型的值,并带有一个包装器。

标签: haskell monads purescript state-monad reader-monad


【解决方案1】:

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

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

ReaderT 类型只有一个数据构造函数:ReaderT (r -&gt; m a),这意味着 createUser 是一个计算为 ReaderT (Permissions -&gt; 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

要了解这是做什么的,您必须查找 ReaderTask&gt;&gt;=pure 的定义。让我们进行另一轮替换:

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

【讨论】:

    【解决方案2】:

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

    Reader 实际上只是一个函数的“包装器”,所以当您获得Reader r r 时,您实际上只能获得从rr 的阅读器;换句话说,一个函数r -&gt; r

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

    当您使用do 表示法时,您处于“单子内部”,因此上下文r 是隐含的。换句话说,您调用一个返回 r 值的函数,当您使用 &lt;- 箭头时,您只需获取该上下文。

    【讨论】:

    • 赞成。凭空召唤这样一个r -&gt; r 功能的方法可能毫无价值,就是说出咒语id。 (由于参数化,id唯一这样的函数。)
    • 好的,所以在我的例子中,Reader 是 Permissions -> Maybe User 的包装器。当我运行runReader createUser permissions 时,createUser 的主体中的ask 如何知道返回与我传递给runReader 相同的permissions?我确信这个问题完全是荒谬的,因为我认为这一切都是错误的......但是请尝试帮助我解开我的大脑。
    • @kaqqao Reader Permissions (Maybe User) 类型只是Permissions -&gt; (Maybe User) 的“包装器”,所以你的整个createUser 'value' 确实是一个函数(但函数是值,所以这很酷)。当您调用runReader 时,您不仅必须传递createUser,还必须传递r 的值——在本例中为Permissions 值。 runReader 然后使用您传递的 Permissions 值调用包装函数。 HTH。
    • @MarkSeemann 啊,我明白了。 runReader 在调用其中的函数之前有效地在 Reader 实例上设置状态/环境。 (不确定我的术语有多好)它终于有意义了。非常感谢!
    • @ftor 我不认为Reader monad 的意义在于伪装任何东西。 Reader monad 在数学上是存在的,所以它也可能存在于 Haskell/TypeScript API 中。这使您能够将阅读器(即纯函数)与所有在 monad 上运行的函数一起使用。 (例如mapMsequence 等等...)。动机不是隐藏任何东西,而是为您提供选项进行构图。
    猜你喜欢
    • 2021-08-06
    • 2014-11-14
    • 2018-09-04
    • 2017-08-16
    • 1970-01-01
    • 2017-06-22
    • 2021-07-18
    • 1970-01-01
    • 2023-04-05
    相关资源
    最近更新 更多