【问题标题】:How does the ask function know the enrivonment to return in the Reader monad?ask 函数如何知道 Reader monad 中返回的环境?
【发布时间】:2021-05-17 04:02:26
【问题描述】:

我正在阅读器 monad 上阅读此示例:https://blog.ssanj.net/posts/2014-09-23-A-Simple-Reader-Monad-Example.html,其中包含以下代码:

import Control.Monad.Reader

tom :: Reader String String
tom = do
    env <- ask -- gives you the environment which in this case is a String
    return (env ++ " This is Tom.")

我明白它的作用,但我不明白ask 是如何返回任何东西的。

在 Haskell 中,函数可以作用于许多不同的类型。 ask 是一个没有输入并返回 monad 环境 m r 的函数。我试图了解当我这样做时会发生什么

(runReader tom) "Who is this?"

不知何故,runReader 会调用tom,但tom 中的ask 怎么可能返回带有文本Who is this?env

【问题讨论】:

  • ask 不是没有输入的函数;这是一个没有输入的Reader r r action,它是一个newtype 包装器,用于one 输入的函数(与所有函数一样),类型为r -&gt; r。事实上,runReader 等价于标识函数id @(r -&gt; a) :: (r -&gt; a) -&gt; (r -&gt; a),因为它只是解开这个包装器以便您可以应用该函数,而ask 也是id @r :: r -&gt; r,因为它只是直接返回“环境”参数。 return/pure 等价于const,用于忽略环境,do 变为&gt;&gt;=,将环境传递给每个动作。
  • 对了,你可以写runReader tom "Who is this?"。一般来说,f a b 只是 Haskell 中 (f a) b 的语法糖。

标签: haskell


【解决方案1】:

Reader monad 中 do 块的每一行都会在后台发送一份环境副本,就好像它已作为额外参数传入一样(并且由于 Haskell 没有可变数据,每行都得到相同的复制)。 ask 函数将其副本也作为值返回。

这将有助于将ask 类型中的通用m 替换为具体类型:ask::Reader r rask 是一个单子动作,其值(第二个 r)与环境(第一个 r)相同。为了使ask 有用,必须存在Reader r 上下文(这就是do 块的用途)。在这种情况下,r 总是坐在那里等待被使用。

想一想如何在没有 Monad 的情况下编写这个示例:

tom :: String -> String
tom env = env ++ " This is Tom."

jerry :: String -> String
jerry env = env ++ " This is Tom."

tomAndJerry :: String -> String
tomAndJerry env = 
  let
    t = tom env
    j = jerry env
   in (t ++"\n"++j)

注意env 是如何手动传递给每个函数的? (顺便说一下,如果您将Reader raskrunReader 的定义替换到示例中,这正是您得到的结果。)Reader monad 简单地为我们完成了收集和传递环境的过程,它很好,因为这是一个我们容易搞砸的过程(至少我是这样)。

当您有一个配置文件时,Reader Monads 非常方便,您在启动时读取一次,然后在程序的其余部分中进行引用。在命令式语言中,您可以将其设为全局 const 变量。在 Haskell 中,您可以尝试将该值传递给需要它的每个函数,但是将其转储到 Reader monad 更容易且不易出错(并且结果完全相同,只是没有所有类型的输入。)类型系统标记依赖于配置文件的所有函数的附带好处,因此当配置格式更改时,编译器可以指出所有可能需要更新的地方。

【讨论】:

    【解决方案2】:

    我认为您基本上对Reader 的定义及其使用方式感到困惑。

    让我们快速构建我们自己的:

    newtype MyReader env a = MyReader (env -> a)
    

    没错,Reader 可以只是一个 函数,它接受当前环境并返回一些 a - 这里在 newtype 包装器中,因此我们可以在其上定义一些实例而无需太多扩展

    runReader 非常简单——它只是将给定的环境应用到我们包装的函数中:

    runReader :: MyReader env a -> env -> a
    runReader (MyReader f) = f
    

    这可能应该回答你的大部分问题 - runReader 在你的例子中不会调用 tom - 你将它作为参数提供,所以 tom 调用包装函数;)


    但是让我们完成我们的 Monad

    所以让我们先把它变成一个仿函数 - GHC 现在可以推导出它,但我想理解手工操作会更好:

    instance Functor (MyReader env) where
      fmap f (MyReader rdr) = MyReader (f . rdr)
    

    如果您仔细观察,您会发现这确实只是函数组合 - 所以映射的阅读器将采用 env,将包装的函数应用到它,然后在 f 到这个结果。

    应用是下一个:

    instance Applicative (MyReader env) where
      pure a = MyReader (\_ -> a)
      (MyReader f_a_to_b) <*> (MyReader f_a) = MyReader (\env -> f_a_to_b env (f_a env))
    

    这应该很容易 - pure a 是一个忽略环境并无论如何返回给pure 的值的阅读器。

    而你 &lt;*&gt; 两个读者通过为两者提供环境(我希望命名有所帮助 - 第一个参数是 MyReader env (a -&gt; b) 第二个是 MyReader env a,结果应该是 MyReader env b

    现在我们可以定义 monad 实例了:

    instance Monad (MyReader env) where
      (MyReader f_a) >>= f_a_MyR_b = 
        MyReader (\env -> let a = f_a env in runReader (f_a_MyR_b a) env)
    

    所以这(作为应用程序)将提供给结果MyReader 的环境传递给两个部分。这里使用runReader 让它更简单一些(不想再次解包)。


    现在对于ask,这似乎有点特别:它将传递的环境 正确地进入 monad。您可能可以从它的类型中弄清楚:ask :: MyReader env env try! 但这里是:

    ask :: MyReader env env
    ask = MyReader (\env -> env)
    

    回到你的问题:

    问怎么能返回任何东西?

    只有当你runReader它时才会这样做:

    举个简单的例子:

    tom :: Reader String String
    tom = do
        env <- ask -- gives you the environment which in this case is a String
        return (env ++ " This is Tom.")
    

    它不适合:

    tom = ask >>= (\a -> pure (a ++ " This is Tom."))
    

    所以如果你这样做 runReader tom "Who is this" 并遵循上面的所有定义,你最终会得到

    runReader tom "Who is this"
      = let a = (\env -> env) "Who is this" in  (\env -> a ++ " This is Tom.") "Who is this"
      = let a = "Who is this" in a ++ " This is Tom."
      = "Who is this This is Tom".
    

    不要自己定义Functor/Applicative

    {-# LANGUAGE DeriveFunctor #-}
    
    import Control.Monad (ap)
    
    newtype MyReader env a = 
      MyReader { runReader :: env -> a }
      deriving Functor
    
    instance Applicative (MyReader env) where
      pure = return
      (<*>) = ap
    
    instance Monad (MyReader env) where
      return a = MyReader (const a)
      (MyReader mr_a) >>= f_a_mr_b = 
        MyReader (\env -> let a = mr_a env in runReader (f_a_mr_b a) env)
    
    ask :: MyReader env env
    ask = MyReader id
    

    【讨论】:

    • 为什么在 monad 之前列出 Applicative 和 Functor 实例?我猜,monad 实例不依赖于它们。在您回答后,我去阅读有关应用程序和仿函数的信息,然后回到这里,仍然不明白您为什么这样做
    • @GuerlandoOCs 如果您查看Monad 类的定义,您会看到class Applicative m =&gt; Monad m,这意味着Monad 的实例需要 是@987654359 的实例@ - 与Applicative/Functor 相同 - 这就是我这样做的原因。例如,Monad 中的 return 被定义为 Applicative 中的 return = pure(您可以覆盖它,但没有任何意义)
    • 当然每个Monad总是Applicative/Functor,我可以写下return/&gt;&gt;=的定义并推导出所有内容来自那些,但 IMO 比这样做更复杂,而且信息量也可能更少。
    • @GuerlandoOCs 我添加了一个部分来演示如何在不手动指定ApplicativeFunctor 的情况下实现MyReader - 使用{-# LANGUAGE GeneralizedNewtypeDeriving #-},您甚至可以去掉Applicative 并执行@ 987654376@ 但比(&lt;*&gt;) = appure = return 不那么明显。如果你愿意,你可以玩这个 - 注释掉 derivingApplicative 实例,看看会发生什么
    猜你喜欢
    • 2022-01-25
    • 1970-01-01
    • 2015-09-08
    • 2019-11-30
    • 1970-01-01
    • 2023-04-05
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多