为啥以下 Haskell 代码是不确定的?
Posted
技术标签:
【中文标题】为啥以下 Haskell 代码是不确定的?【英文标题】:Why is the following Haskell code non-deterministic?为什么以下 Haskell 代码是不确定的? 【发布时间】:2015-04-27 03:47:50 【问题描述】:我一直在向 Learn You A Haskell 和 just came across the following statement 学习 Haskell:
执行
(+) <$> [1,2] <*> [4,5,6]
会导致不确定性 计算x + y
其中x
采用来自[1,2]
和y
的每个值 来自[4,5,6]
的每个值。
我想我不明白什么是不确定的。只是结果的顺序还是计算的顺序不保证每次都一样?
【问题讨论】:
也许这会有所帮助:***.com/questions/20638893/… 简而言之,有两种非确定性计算:一种是随机选择结果;另一种是随机选择结果。在其他情况下,所有可能的答案都是由计算生成的,然后您可以稍后决定是要随机选择其中一个还是继续分析所有答案。 如果您使用 do 表示法根据列表 monad 编写计算,则声明具有与非确定性有限自动机的转换相同的非确定性语法。我喜欢将val <- list :: [a]
视为声明a
类型的正式值val
,或将val
声明为[a]
中的正式a
值,称为list
。
【参考方案1】:
本书使用的“非确定性计算”与您不同。
您将“非确定性计算”视为“不能完全确定其输出的程序”。这种不确定性在使用多个并行执行线程时很常见;有许多可能的输出,你得到哪一个是由运行时事情发生的精确顺序任意决定的。
您从 LYAH 引用的段落是在谈论将列表视为“非确定性计算”的模型,在某种意义上是逻辑编程范式的意思(如果您曾经使用 Prolog 进行过很多编程语言,你可能对此很熟悉)。从这个意义上说,非确定性程序有多个(或零个!)输出因为它们被专门编程为这样做,而不是因为它们没有完全指定它们的输出应该是什么。
如果“非确定性代码”只是具有“t 类型的零个或多个输出”的代码,这听起来很像一个返回 t 列表的函数。列表 Applicative(以及 Functor 和 Monad)实例只是说明如何将这些“非确定性值”相互结合以及与纯函数结合的明显方式。例如,Functor 实例表示,如果您可以将函数应用于 A 以获得 B,那么您还可以将该函数映射到“非确定性 A”以获取“非确定性 B”(通过将未映射的函数应用于“非确定性 A”的每个可能值)。
(+) <$> [1,2] <*> [4,5,6]
这样看是“非确定性加法”的一个例子。您将一个可能是 1 或 2 的数字与另一个可能是 4、5 或 6 的数字相加;结果可能是 5、6、7、6、7 或 8(有些可能性会重复,因为生成它们的方法不止一种)。
【讨论】:
【参考方案2】:在这种情况下,不确定性的不是 Haskell执行的计算,而是表示的计算。当被视为 monad(或应用函子)时,列表表示非确定性计算:就像 Maybe a
是对可能失败的 a
的计算,或者 IO a
是对完成某些操作的 a
的计算I/O,[a]
是a
的非确定性计算。因此,在这种解释下,列表[1,2]
表示非确定性返回1
或2
的计算,[4,5,6]
也类似。或者再次打个比方:在 Haskell 中计算 Nothing
成功,即使该值代表失败;在 Haskell 中计算 [1,2]
是确定性的(而且很无聊),但该值编码了一种非确定性形式。
因此,(+) <$> [1,2] <*> [4,5,6]
不确定地计算 x + y
。同样,这不是写在代码中的内容——那是代码表示的内容。代码本身确定性地计算非确定性计算的表示!
它的工作方式是 <$>
和 <*>
函数在应用函子内提升计算,因此 sn-p 表示要在列表应用函子内计算 (+)
,这意味着它不确定地计算 (+)
:
[1,2]
表示可以返回 1
或 2
的计算。调用它的结果x
。
[4,5,6]
表示可以返回任何4
、5
或6
的计算。调用它的结果y
。
因此,将这些计算的结果加在一起(计算x + y
)可以计算出x
和y
的任何可能值的总和。
这就是引用的意思,只是用了更多和不同的词:-)
事实上,(+) <$> [1,2] <*> [4,5,6]
完全等同于[x + y | x <- [1,2], y <- [4,5,6]]
,其中“不确定性”是x
和y
各自迭代各自的列表。最后,这就是不确定性的全部含义!
至于您是如何理解这一点的:请记住,Haskell 代码保证其结果具有确定性,这要归功于 Haskell 的纯函数性质。然而,计算顺序不会影响这一点,因此只要函数不会过早失败(例如,const () undefined
必须评估为()
),计算顺序就不会受到限制。我们只能通过将其表示为效果来获得不确定性;列表是其中的一种编码(IO
可以是另一种编码,用于一种非常不同的不确定性)。
【讨论】:
“事实上,(+) <$> [1,2] <*> [4,5,6]
完全等价于 [x + y | x <- [1,2], y <- [4,5,6]]
”啊啊啊啊突然我更深层次地理解了 monad 理解。
@kqr:我想过提到这一点 :-) 但在难得的简洁时刻,我决定我的回答已经足够长了!【参考方案3】:
在列表 monad 中,我喜欢将[1, 2]
视为表示一组可能的选择:1 或 2。当我们对这样的集合进行操作时,我们会产生一组可能的结果。什么是“1 或 2”加 4?自然是“5 或 6”。在 Haskell 中,我们可以将该问题表述为 (+ 4) <$> [1, 2]
并得到预期的答案 [5, 6]
。
list monad 代表了不确定性,因为它让我们讨论可能选择的整个树,而无需实际承诺任何这些选择。那么“1 或 2”加上“4、5 或 6”是什么?好吧,那可能是:
1 + 4 = 5 或 + 5 = 6 或 + 6 = 7 或 2 + 4 = 6 或 + 5 = 7 或 + 6 = 8我们可以在 Haskell 中用 list monad 对问题进行编码(作为所有解决方案的详尽计算,按顺序):
do
x <- [1, 2] -- if x is 1 or 2
y <- [4, 5, 6] -- and y is 4, 5, or 6
return (x + y) -- then what are the possible values of x + y?
或者使用列表应用程序(做同样的事情):
(+) <$> [1, 2] <*> [4, 5, 6]
答案当然是[5, 6, 7, 6, 7, 8]
。
如果有帮助,您还可以将列表单子或列表推导视为执行一种笛卡尔积。
另一种编码方式是立即为每个选择启动一个独立的并发计算,生成没有任何固有顺序的最终结果。
【讨论】:
以上是关于为啥以下 Haskell 代码是不确定的?的主要内容,如果未能解决你的问题,请参考以下文章