[遍历Haskell教科书上不同单子的章节,当作者从解释bind的细节和单子法则转变为实际使用单子时,我一再迷失。突然,出现诸如“在单子上下文中运行函数”或“运行单子”之类的表达式。同样,在库文档和有关monad转换器堆栈的讨论中,我读到一些语句“可以在任何monad中运行”。 “在单子内部运行”到底是什么意思?
我似乎不太明白两件事:
return
,>>=
)和法律的类型类。因此,在monad中“运行”某物可能意味着(a)将其提供为return
的参数,或者(b)使用>>=
对其进行排序。如果monad的类型为m a
,则在a)中something必须为a
类型,以匹配return
函数的类型。在b)情况下,something必须是类型为a -> m b
的函数,以匹配>>=
函数的类型。由此,我不明白如何在任意monad中“运行”某些函数,因为我使用>>=
排序的函数必须全部具有相同的类型签名,并且我使用return
提升的值必须是特定的monad类型参数。run
函数,例如runReader
,runState
等。这些函数不是monad定义的一部分,它们是普通函数,在任何情况下都不是特殊的命令式语句语言的功能核心。那么,他们“运行”什么?我认为,对这些概念有清楚的了解对于理解monad变压器堆栈或类似的结构(对于理解Haskell中的任何实质性库和任何不重要的程序而言似乎是必需的)至关重要。非常感谢您帮助我实现了从简单编写功能代码到真正理解其含义的飞跃。
写书和文章的作者在尝试解释概念时经常使用隐喻和不太精确的语言。目的是使读者对正在发生的事情有一个概念上的直觉。
我相信“运行”功能的概念属于这一类。除了IO
,您说对了,用于编写[]
,Maybe
等功能的功能与其他功能没有什么不同。
我认为,在单子内部运行某些东西的概念来自functors are containers的观察。这种观察也适用于单子,因为所有单子都是函子。 [Bool]
是布尔值的容器,Maybe Int
是(零或一个)数字的容器。您甚至可以将阅读器函子r -> a
视为a
值的容器,因为您可以想象这只是一个很大的查找表。
能够在容器内运行功能非常有用,因为并非所有容器都可以访问其内容。同样,IO
是主要示例,因为它是不透明的容器。
[一个常见问题是:How to return a pure value from a impure method。同样,许多初学者会问:如何获得Maybe
的值?您甚至可能会问:如何从清单中获得价值?概括地说,问题变为:How to get the value out of the monad。
答案是您没有。您“在容器内运行函数”,或者,就像我喜欢的那样,您是inject the behaviour into the monad。您从不离开容器,而是让您的功能在容器的上下文中执行。特别是当涉及到IO
时,这是您与该容器进行交互的唯一方法,因为它不透明(我在这里假装unsafePerformIO
不存在)。
请牢记bind方法(>>=
),尽管'在其内部运行'的函数的类型为a -> m b
,但是您也可以'运行'一个'普通'的函数[ [Mono]内部的a -> b
与fmap
实例相同,因为所有Monad
实例也是Functor
实例。
我使用>> =排序的函数必须都具有相同的类型签名
这仅是正确的。在某些单子语境中,我们可能有表达式
x >>= f >>= g
where
x :: Maybe Int
f :: Int -> Maybe String
g :: String -> Maybe Char
所有这些都必须包含相同的monad(也许),但是请注意,它们并非都具有相同的类型签名。就像普通的函数组成一样,您不需要所有的返回类型都相同,只需要一个函数的输入与其前任的输出匹配即可。
这里是一个简单的类比,“在容器内部运行函数”,带有伪代码:
比方说,您有某种类型的Future [String],它表示一个容器,该容器将在“将来的某个时候”具有字符串:
val tweet: Future[String] = getTweet()
现在,您想访问字符串-但您不会从上下文中删除字符串-“未来”-您只需在容器内使用字符串即可:]]
tweet.map { str => println(str) }
在这些花括号内,您处于“将来”。例如:
。因此,“位于容器内”类似于在阅读源代码时“位于地图的花括号内(平面地图等)”。您正在浸入容器中。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试图访问tweet 容器外部
tweet.map { str => println("Length of tweet string is " + str.length) // <== RIGHT }
尽管是一个非常简单的类比,但我认为在考虑所有单子时,这很有用。在源代码中,“容器内部”在哪里,外部在哪里?在这种情况下,length函数将在将来或“在容器内部”运行。