Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 奇怪的GHCi惰性评估_Haskell_Lazy Evaluation_Ghci - Fatal编程技术网

Haskell 奇怪的GHCi惰性评估

Haskell 奇怪的GHCi惰性评估,haskell,lazy-evaluation,ghci,Haskell,Lazy Evaluation,Ghci,我为ghci中的偶数和奇数定义了两个相互递归的列表,如下所示: > let evens = 0:map (+1) odds; odds = map (+1) evens 然后我使用:sp > :sp evens evens = _ > :sp odds odds = _ > take 5 evens [0,2,4,6,8] > :sp evens evens = 0 : 2 : 4 : 6 : 8 : _ :sp odds odds = _ 请注意,赔率thun

我为ghci中的偶数和奇数定义了两个相互递归的列表,如下所示:

> let evens = 0:map (+1) odds; odds = map (+1) evens
然后我使用
:sp

> :sp evens
evens = _
> :sp odds
odds = _
> take 5 evens
[0,2,4,6,8]
> :sp evens
evens = 0 : 2 : 4 : 6 : 8 : _
:sp odds
odds = _
请注意,
赔率
thunk是如何计算的,尽管
evens
已计算到第5个元素。我能想出一个直观的解释<必须明确调用代码>赔率才能进行评估:

> take 5 odds
[1,3,5,7,9]
>:sp odds
odds = 1 : 3 : 5 : 7 : 9 : _
但是,现在当我这样做时:

> take 10 evens
[0,2,4,6,8,10,12,14,16,18]
> :sp evens
evens = 0 : 2 : 4 : 6 : 8 : 10 : 12 : 14 : 16 : 18 : _
> :sp odds
odds = 1 : 3 : 5 : 7 : 9 : 11 : 13 : 15 : 17 : _

请注意,无论何时评估
evens
,现在都是如何评估
赔率的?为什么第一次未评估
赔率,第二次及所有后续评估均未评估
赔率?发生了什么事

这与GHC如何编译相互递归的绑定有关(绑定是否具有显式类型签名是有区别的)

让我们编写以下简单的程序,它暴露了相同的问题,但消除了对整数重载或单态限制可能扮演的角色的所有怀疑:

module MutRec where

ft = False : map not tf
tf = map not ft
将其加载到GHCi(我使用的是7.6.3)会产生:

让我们看看这个模块的核心代码

$ ghc -O0 MutRec -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling MutRec           ( MutRec.hs, MutRec.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 28, types: 42, coercions: 0}

Rec {
ft1_rkA
ft1_rkA = : False a_rkC

tf1_rkB
tf1_rkB = map not ft1_rkA

a_rkC
a_rkC = map not tf1_rkB
end Rec }

ds_rkD
ds_rkD = (ft1_rkA, tf1_rkB)

ft
ft = case ds_rkD of _ { (ft2_Xkp, tf2_Xkr) -> ft2_Xkp }

tf
tf = case ds_rkD of _ { (ft2_Xkq, tf2_Xks) -> tf2_Xks }
这就解释了一切。相互递归的定义最终会出现在一个
Rec
块中,直接相互引用。但随后GHC正在构建一对
ds_rkD
,并从该对中重新提取组件。这是一个额外的间接过程。它解释了为什么在GHCi中对
ft
进行部分评估后,
tf
的顶部仍然会显示为“砰”的一声,即使下面已经进行了评估。事实上,我们可以验证,仅对
tf
进行最小的评估就足以公开以下内容:

*MutRec> take 5 ft
[False,False,False,False,False]
*MutRec> :sp ft
ft = False : False : False : False : False : _
*MutRec> :sp tf
tf = _
Prelude MutRec> seq tf ()
()
Prelude MutRec> :sp tf
tf = True : True : True : True : _
如果我们将显式类型符号添加到
ft
tf
或启用优化,则元组构造不会发生:

$ ghc -O MutRec -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling MutRec           ( MutRec.hs, MutRec.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 12, types: 11, coercions: 0}

Rec {
ft1
ft1 = map not tf

ft
ft = : False ft1

tf
tf = map not ft
end Rec }
现在,GHCi将表现得更加自然


编辑 我已经查看了GHC来源,试图找出差异的原因 行为。这似乎是多态绑定的类型推断工作方式的副作用

如果绑定是多态的,但没有类型签名,那么它的递归用法是 单态的。这是Hindley Milner中GHC也实施的限制。如果你愿意 多态递归,您需要一个额外的类型签名

为了在核心语言中忠实地对此进行建模,脱糖者制作了 每个未注递归函数。此单态版本用于递归 调用时,通用版本用于外部调用。你甚至可以在很短的时间内看到这一点 函数,例如
rep
(这是
repeat
的重新实现)。去糖化的核

rep x = x : rep x

外部的
rep
是多态的,因此在开始时类型抽象
\(@a\u aeM)->
。内部
rep_aeJ
是单态的,用于递归调用

如果向
rep

rep :: a -> [a]
rep x = x : rep x
然后对多态版本进行递归调用,生成的核心就变成 更简单:

您可以看到类型参数
@a_b
是如何在开始时拾取并重新应用的 在每次递归调用中执行
rep

我们看到的用于相互递归绑定的元组结构只是一个简单的例子 这一原则的概括。你们建立了相互作用的内部单态版本 递归函数,然后在元组中泛化它们,并提取多态 来自元组的版本

所有这些都独立于绑定是否是多态的。 它们是递归的就足够了。我认为GHC的这种行为是完全错误的
正确且正常,特别是因为优化考虑了性能影响。

无法重现这一点。你的操作系统是什么,ghc版本是什么?我可以用ghc 7.6.3在Win7上复制它。我也可以做
拿5个赔率
,得到
的相同行为:sp evens
,这真的没有意义,因为
evens
中有文字
0
。我有一个猜测:
evens
赔率
是“某种”多态的,直到你第一次明确地观察它们。然后它们被神奇地单形化为
[Integer]
,从那时起,一切都是可见的。e、 g.尝试将
evens,赔率::[Integer]
添加到您的
中,让
查看差异。@bheklillr当文字“看起来像函数”时,不会提前计算它。比较
让x=[1,2,3,4]
让y()=[1,2,3,4]
。如果我们做
:sp x
我们得到
x=[1,2,3,4]
,但是
:sp y()
y=\ucode>。因此,在这种情况下,
evens
中的文字
0
不会得到早期计算,因为它显然不是一个常数。@DanielWagner
evens
赔率
的默认类型签名是
[Integer]
,至少在ghc 7.6.3之前是这样。这是推断的类型,不必显式地提及它。为什么提到类型会有什么不同?我认为这只是意味着类型INFENCER或
:sp
本身存在缺陷。但添加显式类型签名后行为改变的事实是不合理的,不是吗?@is7s行为会改变吗?结果不是这样。GHC被“允许”使用任何它喜欢的、不会改变结果的评估策略<代码>:sp
正在窥探通常不会“公开”的实施细节。不过,我也很想知道元组构造在GHC中实现了什么,但这是一个额外的间接层次,因此对性能有很大影响。我决不会认为添加已正确推断的类型会对性能产生任何影响@我已经编辑了我的答案。我同意本的观点,GHC可以做它所做的事情。抱怨一个未经优化的Haskell项目的潜在性能下降是没有道理的。会的
rep
rep =
  \ (@ a_aeM) ->
    letrec {
      rep_aeJ
      rep_aeJ =
        \ (x_aeH :: a_aeM) -> : @ a_aeM x_aeH (rep_aeJ x_aeH); } in
    rep_aeJ
rep :: a -> [a]
rep x = x : rep x
Rec {
rep
rep = \ (@ a_b) (x_aeH :: a_b) -> : @ a_b x_aeH (rep @ a_b x_aeH)
end Rec }