take 2$[1..]如何在haskell工作?
我们知道,$操作符绑定最松散的,并且也关联到右边,这意味着应该首先计算[1..,因此,它不应该运行到无限循环中吗?为什么它会停下来呢?哈斯克尔很懒,take 2$[1..]如何在haskell工作?,haskell,Haskell,我们知道,$操作符绑定最松散的,并且也关联到右边,这意味着应该首先计算[1..,因此,它不应该运行到无限循环中吗?为什么它会停下来呢?哈斯克尔很懒,($)不会改变这一点。($)操作符一点也不神奇,它是一个完全普通的Haskell函数†: 由于Haskell是惰性的,参数在传递给函数之前不会进行计算,($)也不例外。因此,take 2$[1..]等同于(take 2)[1..],这当然等同于take 2[1..]。没有进行额外的评估 现在,事实证明,($)的一个严格版本被调用,它在应用函数之前对其
($)
不会改变这一点。($)
操作符一点也不神奇,它是一个完全普通的Haskell函数†:
由于Haskell是惰性的,参数在传递给函数之前不会进行计算,($)
也不例外。因此,take 2$[1..]
等同于(take 2)[1..]
,这当然等同于take 2[1..]
。没有进行额外的评估
现在,事实证明,($)
的一个严格版本被调用,它在应用函数之前对其参数进行求值。它也可以定义为普通的Haskell函数,但它必须使用神奇的seq
函数作为其定义的一部分:
($!) :: (a -> b) -> a -> b
f $! x = x `seq` f x
然而,即使也要2美元![1..]
将产生[1,2]
,而不是分歧。为什么?嗯,$代码>只将其参数计算为WHNF,而不是标准形式,并且WHNF可以被认为是“浅”的计算。它计算第一个cons对,但仅此而已。通过使用GHCi中的:sprint
命令可以看到这一点:
ghci> let xs = [1..] :: [Int]
ghci> xs `seq` ()
()
ghci> :sprint xs
xs = 1 : _
要递归地强制一个值,您需要使用,顾名思义,它对一个值进行深入的求值。它提供了一个更“强大”的($)
,名为,类似于($!)
,但使用而不是seq
。因此,拿2美元!![1..]
事实上会产生分歧
†这在GHC中并不完全正确,因为。但是,所有这些都与此无关,更简单的定义同样适用。由于函数应用程序具有最高优先级,您的表达式被解析为(取2)$[1..]
。这意味着您首先得到一个函数(取2)
,然后将其应用于参数[1..]
然而,这一切都无关紧要,因为Haskell是一种懒惰的语言。你可以反过来写,得到完全相同的结果:
> [1..] & take 2
[1.2]
(&)
是($)
运算符的反向版本
即使[1..]
在这里是第一位的,在需要它的内容之前也不会对它进行评估。为了补充其他答案,让我补充一点,您混淆了评估顺序(或评估策略)和优先级。这是一种常见的误解
举例说明,考虑表达式
f 0 * g 0 + h 0
优先级告诉我们乘法必须在加法之前进行。然而,这并不意味着必须在h0
之前对f0
和g0
进行评估!编译器可以选择先计算h0
,然后是g0
,然后是f0
,最后是乘法,然后是加法
这不仅适用于Haskell,甚至适用于命令式语言,如C,它不指定求值顺序,并且允许函数产生副作用
除此之外,您还必须了解,在Haskell中“评估”某个内容大致意味着评估它,直到它的第一个构造函数出现(WHNF)。因此,计算[1..]
大约会导致1:[2..]
必须计算尾部的位置。如果计算[1..]
会导致无限循环,那么在表达式中根本没有办法使用[1..]
:只能选择放弃它而不计算它,或者永远循环。这里的$
是一条红鲱鱼take 2$[1..]
与take 2[1..]
完全相同。$
只影响what的参数;它对评估事物的时间没有任何影响
(例如:
print 2 + 2 ==> (print 2) + 2 {- Doesn't work. -}
print $ 2 + 2 ==> print (2 + 2) {- Works. -}
美元会影响print
是+
的参数,还是反过来。美元本身不会评估任何东西。)
这里的“最顶端”函数是take
,因此我们首先对其进行评估。take
的定义可以这样写:
take 0 xs = xs
take n xs =
case xs of
x : xs' -> x : take (n-1) xs'
[] -> []
假设长度不为零,首先要做的事情是……
的情况xs,这意味着必须对xs
(在本例中为[1..]
)进行评估,以确定它是:
还是[]
。这样做,我们发现(在恒定时间内)xs=1:[2..]
,因此第一种情况下的备选方案适用
你可以这样写出来
take 2 [1..]
take 2 (1 : [2..])
1 : take (2-1) [2..]
1 : take 1 [2..]
1 : take 1 (2 : [3..])
1 : 2 : take (1-1) [3..]
1 : 2 : take 0 [3..]
1 : 2 : []
(我仍然认为很遗憾,没有人能想出一个工具来自动生成这样的跟踪……它可能需要几个人,并且可能非常适合调试……)
take 2 [1..]
take 2 (1 : [2..])
1 : take (2-1) [2..]
1 : take 1 [2..]
1 : take 1 (2 : [3..])
1 : 2 : take (1-1) [3..]
1 : 2 : take 0 [3..]
1 : 2 : []