为啥以下 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] 表示非确定性返回12 的计算,[4,5,6] 也类似。或者再次打个比方:在 Haskell 中计算 Nothing 成功,即使该值代表失败;在 Haskell 中计算 [1,2] 是确定性的(而且很无聊),但该值编码了一种非确定性形式。

因此,(+) <$> [1,2] <*> [4,5,6] 不确定地计算 x + y。同样,这不是写在代码中的内容——那是代码表示的内容。代码本身确定性地计算非确定性计算的表示!

它的工作方式是 <$><*> 函数在应用函子内提升计算,因此 sn-p 表示要在列表应用函子内计算 (+),这意味着它不确定地计算 (+)

[1,2] 表示可以返回 12 的计算。调用它的结果x[4,5,6] 表示可以返回任何456 的计算。调用它的结果y。 因此,将这些计算的结果加在一起(计算x + y)可以计算出xy 的任何可能值的总和。

这就是引用的意思,只是用了更多和不同的词:-)

事实上,(+) <$> [1,2] <*> [4,5,6] 完全等同于[x + y | x <- [1,2], y <- [4,5,6]],其中“不确定性”是xy 各自迭代各自的列表。最后,这就是不确定性的全部含义!

至于您是如何理解这一点的:请记住,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 代码是不确定的?的主要内容,如果未能解决你的问题,请参考以下文章

工具rest:Haskell的REST开源框架

为啥这个 opencl 代码是不确定的?

Haskell趣学指南

FFI 可以处理数组吗?如果是这样,怎么做?

为啥 Haskell ghc 不工作,但 runghc 工作良好?

为啥我的 Haskell 函数参数必须是 Bool 类型?