哈斯克尔的读者莫纳德。读者在哪里作为参数传递?

Reader Monad in Haskell. Where is the reader passed as argument?

提问人:Mafaldo Reliedo 提问时间:10/4/2023 更新时间:10/4/2023 访问量:125

问:

在此 monad 阅读器示例中:

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.")

jerry :: Reader String String
jerry = do
  env <- ask
  return (env ++ " This is Jerry.")

tomAndJerry :: Reader String String
tomAndJerry = do
    t <- tom
    j <- jerry
    return (t ++ "\n" ++ j)

runJerryRun :: String
runJerryRun = (runReader tomAndJerry) "Who is this?"

我们可以将 ˋdoˋ 符号读作

tomAndJerry = tom >>= \t -> ...

最终你会得到一个字符串。好吧,所以,基本上ˋ(runReader tomAndJerry)“这是谁?ˋ 是一个读者,所以 ˋaskˋ 给出 ˋ这是谁?但是,ˋtomˋ 没有参数名称。询问如何能够运行?

我可以把 ˋtom :: Reader String Stringˋ 想象成类似 C++ 泛型的东西,其中 ˋReaderˋ 在编译时绑定到某个静态类,当你调用 ask 时,它总是会产生“这是谁?因为据我所知,ˋaskˋ 是接受 ˋReader String Stringˋ 的东西,它没有明确地将其作为参数在任何地方。

我知道它以某种方式获得了上下文,但我对它在语言本身中是如何发生的感到好奇。

我没有尝试任何东西,因为它本身不是问题

哈斯克尔 单子

评论

0赞 willeM_ Van Onsem 10/4/2023
“但是,ˋtomˋ 没有参数名称。问如何能够运行?我不是真的在关注吗?
5赞 David Young 10/4/2023
像C++泛型一样思考它的部分实际上比实际发生的事情要复杂得多。 只是一个 type 值的包装器。Reader String StringString -> String
0赞 Mafaldo Reliedo 10/4/2023
@DavidYoung,但参数 Reader String String 没有名称,因此未使用。在C++中,Reader实现了静态询问,返回“这是怎么回事?”,这是有道理的
1赞 Louis Wasserman 10/4/2023
为什么参数必须有一个名称才能使用?在 Haskell 中,通常可以创建几乎任何函数,而无需命名任何参数
1赞 David Young 10/4/2023
尝试实现具有这些类型的这三个函数的自己的版本可能会有所帮助(为它们提供新名称,以便它们不会与现有名称冲突)。然后,您可以将问题中的代码转换为使用这些新定义函数的代码。这就是让它发挥作用所需要的一切。不涉及其他语言功能。只是你提到的符号脱糖,然后只是一些常规的、普通的旧函数。do

答:

2赞 willeM_ Van Onsem 10/4/2023 #1

因为据我所知,它需要一些东西,但它并没有明确地把它作为论据。askReader String String

Haskell中关于单子的一个常见误解是,单子是立即运行的动作。它们不是,尤其是对于 a 来说,这有点误导,a 不是带有状态的东西,a 描述了 的变化,然后你可以将这些变化链接在一起,这本质上就是这样的单子所做的。StateState IntState IntState

出于同样的原因,不是从环境中读取的东西。它基本上是一个“过程”对象,例如在 C# 中是一个委托,然后您可以使用正确的参数调用它。Reader String String

在这种特定情况下,一个对象只能变成使用正确参数“运行”的东西,在这种情况下是初始环境,runReader :: Reader r a -> r -> a 这样做,从而保证你传递一个 .Readerr

例如,一个简单的例子是 ,其中定义为:StateState

newtype State s a = State (s -> (s, a))

因此,a 基本上是一个函数,它将状态映射到具有(可能)不同状态和返回值的 2 元组。States

现在我们可以将两种状态组合在一起:

combine :: State s a1 -> State s a2 -> State s a2
combine (State f1) (State f2) = State (\s -> let (s', _) = f1 s in f2 s')

因此,我们在这里构造一个新对象,首先通过让第一个函数运行将状态映射到新状态,然后将该新状态传递给第二个函数。States'

请注意,我们的 combine 函数(本质上是函数)不会运行两者中的任何一个,它只是构造一个将要运行的函数。>>

现在,如果我们想运行该对象,我们需要一个初始状态,因此将如下所示:StaterunState

runState :: State s a -> s -> a
runState (State f) s = let (_, a) = f s in a

因此,我们可以首先组合各种对象,这些对象是函数,它们将状态作为参数并返回(可能)不同的状态。我们将其组合成一个函数,该函数采用初始状态,并在每次将状态传递给下一个子步骤,然后我们可以调用具有初始状态的函数来获取结果。State

所以这里 a 看起来像:get

get :: State s s
get = State (\s -> (s, s))

因此,我们将状态作为新状态和返回值返回,但这只有在我们被赋予一个状态时才有效,而这个对象本身没有 .sStateState

9赞 Daniel Wagner 10/4/2023 #2

实际上,类型事物不是类型的值,而是类型函数,它接受环境并产生类型值。看看如果我在你的代码中做这个简单的替换,你有多少问题会消失:Reader r aar -> ara

-- skip all imports, so we can use our own ask implementation

ask :: String -> String
ask s = s

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

jerry :: String -> String
jerry = do
  env <- ask
  return (env ++ " This is Jerry.")

tomAndJerry :: String -> String
tomAndJerry = do
    t <- tom
    j <- jerry
    return (t ++ "\n" ++ j)

它的环境从哪里来?嗯,这是一个函数,它以环境为参数,就是这样!与 :它将其环境作为参数传递给它。asktom

好吧,你可能会说,但看起来不像一个接受参数的函数。毕竟,标志的左边没有争论!tom = do {- ... -}=

你是对的。对于传统的命令式程序员来说,它看起来不像是一个函数。然而,确实如此。不用担心。。。随着时间的流逝,你会习惯的!举一个非常愚蠢的例子:

f = (+)

虽然这看起来不像一个函数(它在符号的左侧没有参数),但它是一个函数。它与原样的功能完全相同。非常相似的定义 或 ,看起来更像是一个函数,含义几乎完全相同。一个稍微不那么愚蠢的例子:=(+)f x y = x + yf x y = (+) x y

g = if somethingComplicated then (+) else (*)

函数只是可以传递、存储在变量中、作为复杂计算结果返回的对象等。他们并不总是需要命名他们的参数才能成为一个函数!

现在让我们稍微浏览一下您的帖子。

好的,所以,基本上是一个读者,这样给.(runReader tomAndJerry) "Who is this?"askWho is this?

我不喜欢这种措辞。只是一个读者;更完整的短语是 ,而不是读者。tomAndJerry(runReader tomAndJerry) "Who is this?"String

但是,没有参数名称。如何能够运行?tomask

没有任何命名的参数,但仍然有一个参数。

我可以认为像C++泛型这样的东西,在编译时绑定到某个静态类,当你调用它时总是产生它?tom :: Reader String StringReader"Who is this?"

当然不是。 不是一个你可以传递参数的东西;它本身就已经是一个动作了。你不能打电话.你可以把它想象成 .你也可以认为这样的东西可以在实施过程中使用,如果你愿意(但没有义务)。askReaderReaderasktomtom :: Reader String Stringtom :: String -> Stringaskask :: String -> Stringtom

因为据我所知,它需要一些东西,但它并没有明确地把它作为论据。askReader String String

不。请参阅上一点。 是一个,它不需要一个。askReader String String