3

在 Scala 中,我可以表达一个函数,每次引用它时都会对其进行评估:

def function = 1 + 2 + 3

val仅评估一次的 a

val val1 = 1 + 2 + 3

即使我多次调用它。

当然,在 Haskell 中,我可以定义一个函数(同样,每当我调用它时,它都会一遍又一遍地求值):

function = 1 + 2 + 3

我可以val在 Haskell 中定义 Scala 的模拟吗?

更新:问题不在于惰性评估。

更新2:

的值function将被评估多少次?

function = 1 + 2 + 3
main = do
        print function
        print function
        print function
4

3 回答 3

19

Haskell 没有指定很多方法来控制评估的次数或顺序。也就是说,GHC 最多评估一次CAF(除非它们是类型类多态的)。请注意,使用单态限制(默认),function = 1 + 2 + 3定义function为单态 CAF,因此function最多评估一次。

您还会发现很多人反对将此函数称为函数,因为除非您做了一些非常奇特的事情,否则它不是一个函数。(其类型中没有箭头。)

于 2013-09-30T05:54:08.203 回答
7

你当然可以写:

let x = 1 + 2 + 3 in ...

代替

let myFunc () = 1 + 2 + 3 in ...

但是在 Haskell 中,考虑对其中任何一个进行评估的频率并没有真正的意义,除了如果计算需要它们,则至少对具有两者的主体进行评估,如果不需要,则永远不会。

Haskell 和 Scala 之间的主要区别在于,在 Scala 或任何其他严格的语言中,绑定形式val x = 1 + 2 + 3告诉 Scala 评估表达式1 + 2 + 3并将其结果绑定到变量。另一方面,在 Haskell 中,let x = 1 + 2 + 3将计算绑定1 + 2 + 3x何时甚至是否评估表达式的问题根本不是由let表达式决定的。

由于 Haskell 绑定表单无法决定评估与它们关联的表达式的策略,因此无法编写与 Scala 版本完全类似的方法。

于 2013-09-30T06:12:56.480 回答
2

编辑:忽略这个答案。它仅在您未经优化进行编译时才有效。以我的 fibonacci 实现为例,ghc -O0 -ddump-simpl main.hs在 Core 中给出了一个 fib 定义:

Main.fib :: forall a_agB. GHC.Num.Num a_agB => () -> [a_agB]

O2但是使用优化进行编译ghc -O2 -ddump-simpl main.hs会将斐波那契列表实现为顶层的常量值

Main.$wfib :: forall a_agG. GHC.Num.Num a_agG => [a_agG]

然后它被封闭的斐波那契实现调用(它确实需要一个()参数)

Main.fib =
  \ (@ a_agG) (w_stM :: GHC.Num.Num a_agG) (w1_stN :: ()) ->
    case w1_stN of _ { () -> Main.$wfib @ a_agG w_stM }

结果是,虽然Main.fib没有被记忆,Main.$wfib但仍会被记忆并占用内存。


原答案如下:

正如丹尼尔瓦格纳在另一个答案中所说,这样的定义

val = 1+2+3

只会评估一次,除非它是多态的。如果您想在每次引用它时对其进行评估,您可以这样做

val () = 1+2+3

这样,您必须将参数提供()val,但它不会保存计算值。这可能是一件好事,如果 的计算值val占用大量内存,但易于计算,并且在您需要时逐渐消耗。例如,您可以有一个斐波那契数列:

fib = 0 : 1 : zipWith (+) fib (tail fib)

如果您现在这样做fib !! 100000,您将需要计算所有前 100000 个斐波那契数。该值fib引用此列表,因此它不能被垃圾收集,但会在内存中徘徊。为了解决这个问题,你可以这样做

fib () = let x = 0 : 1 : zipWith (+) x (tail x) in x

由于fib现在是(常量)函数而不是常量值,因此不会保存其值,从而释放内存。

编辑:正如 Philip JF 在评论中所说,let 表达式的内容可能会被不友好的编译器提升,这会导致不必要的共享

于 2013-09-30T07:09:41.130 回答