在 monad 中 "run" 是什么意思?

What does it mean to "run" inside a monad?

通读 Haskell 个关于不同 monad 的教科书章节,当作者从解释 bind 的细节和 monad 法则跳到实际使用 monad 时,我反复迷路.突然,"running a function in a monadic context" 或 "to run a monad" 之类的表达式弹出。同样,在库文档和关于 monad 转换器栈的讨论中,我读到一些函数 "can be run in any choice of monad" 的语句。 "running inside a monad" 到底是什么意思?

有两件事我似乎没有说清楚:

  1. Monad 是一种类型 class,具有函数(return>>=)和法则。因此,"run" monad 中的某些东西可能意味着 (a) 将其作为参数提供给 return,或 (b) 使用 >>= 对其进行排序。如果 monad 是 m a 类型,那么在情况 a) 中 something 必须是 a 类型,以匹配 return 的类型功能。如果 b) something 必须是类型 a -> m b 的函数,以匹配 >>= 函数的类型。由此,我不明白我如何 "run" 任意 monad 中的某些函数,因为我使用 >>= 排序的函数必须具有相同的类型签名,并且我使用 [=11 提升的值=] 必须是特定的 monad 类型参数。
  2. 据我所知,函数式语言中没有执行运行计算的概念——有只对某些参数应用函数,并评估函数(用它的值替换它)。然而,许多特定的 monad 带有 run 函数,例如 runReaderrunState 等。这些函数不是 monad 定义的一部分,它们是普通函数,不在语言功能核心之外的任何特殊命令式语句。那么,他们 "run"?

我觉得清楚地理解这些概念是理解 monad 转换器堆栈或类似结构的关键,这些结构似乎是理解 Haskell 中任何实质性库和任何重要程序所必需的。非常感谢您帮助我实现了从简单地编写功能代码到真正理解其含义的飞跃。

撰写书籍和文章的作者在尝试解释概念时经常使用隐喻和不太精确的语言。目的是让 reader 对正在发生的事情有概念上的直觉。

我认为 'running' 函数的概念属于这一类。除了 IO 之外,你说的没错,你用来组合 []Maybe 等的函数与其他函数没有什么特别之处。

我认为 运行 monad 内部的某些东西的概念来自对 functors are containers 的观察。这个观察结果也适用于 monad,因为所有 monad 都是函子。 [Bool] 是布尔值的容器,Maybe Int 是(零或一个)数字的容器。您甚至可以将 reader 仿函数 r -> a 视为 a 值的容器,因为您可以想象它只是一个非常大的查找 table.

能够 'run a function inside a container' 很有用,因为并非所有容器都可以访问其内容。同样,IO 是主要示例,因为它是一个不透明的容器。

一个常见问题是:. Likewise, many beginners ask: How do I get the value of a Maybe? You could even ask: How do I get the value out of a list? Generalised, the question becomes: How to get the value out of the monad

答案是你不知道。你'run the function inside the container',或者我喜欢说的,你inject the behaviour into the monad。您永远不会离开容器,而是让您的函数在容器的上下文中执行。特别是当涉及到 IO 时,这是您可以与该容器交互的唯一方式,因为它在其他方面是不透明的(我在这里假装 unsafePerformIO 不存在)。

请记住,当谈到 bind 方法 (>>=) 时,函数 'running inside of it' 的类型为 a -> m b,您还可以 'run' 'normal' 函数 a -> b 在 monad 中使用 fmap,因为所有 Monad 实例也是 Functor 实例。

the functions I sequence using >>= must all have the same type signature

这有点对。在一些 monadic 上下文中,我们可能有表达式

x >>= f >>= g

哪里

x :: Maybe Int
f :: Int -> Maybe String
g :: String -> Maybe Char

所有这些都必须涉及相同的 monad(可能),但请注意,它们并不都具有相同的类型签名。与普通函数组合一样,您不需要所有 return 类型都相同,只需一个函数的输入与其前一个函数的输出匹配即可。

下面是"run the function inside the container"的简单类比,伪代码:

假设您有一些 Future[String] 类型,它表示将包含字符串 "at some time in the future":

的容器
val tweet: Future[String] = getTweet()

现在您想要访问字符串——但您没有将字符串从上下文中取出——"future"——您只需使用字符串 "inside the container":

tweet.map { str =>
  println(str)
}

在这些花括号中,你是"in the future"。例如:

val tweet: Future[String] = getTweet()

tweet.map { str =>
  println(str)
}

println("Length of tweet string is " + tweet.length) // <== WRONG -- you are not yet in the future

tweet.length 正试图在容器 之外访问推文 。所以 "being inside the container" 类似于阅读源代码时的 "being inside the curly braces of a map (flatmap, etc.)"。您正在浸入容器内。

tweet.map { str =>
  println("Length of tweet string is " + str.length) // <== RIGHT
}

虽然这是一个非常简单的类比,但我发现在一般情况下考虑所有 monad 时这很有用。源码里,哪里是"inside the container",哪里是外面的?这样的话,长度函数以后就是运行,或者"inside the container".