斯卡拉在哈斯克尔的风格

时间:2013-09-30 05:36:29

标签: scala haskell

在Scala中,我可以表达一个每次引用它时都要评估的函数:

def function = 1 + 2 + 3

或仅评估一次的val

val val1 = 1 + 2 + 3
即使我多次打电话也是如此。

在Haskell中,我当然可以定义一个函数(当我调用它时,它会反复评估):

function = 1 + 2 + 3

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

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

UPDATE2:

评估function的价值的次数是多少次?

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

3 个答案:

答案 0 :(得分:19)

Haskell没有指定很多方法来控制次数或命令事物的评估。也就是说,GHC最多评估一次CAFs(除非它们是类型多态)。请注意,对于单一性限制(默认值),function = 1 + 2 + 3function定义为单态CAF,因此function最多只评估一次。

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

答案 1 :(得分: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 + 3绑定到x,并且何时或甚至是否来评估表达式的问题不是由let表达式。

由于Haskell绑定表单无法决定与其关联的表达式所依据的策略,因此无法编写Scala版本的精确模拟。

答案 2 :(得分:2)

编辑:忽略这个答案。它只适用于没有优化的编译。以我的斐波那契实现为例,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仍然会被记忆并占用记忆。


以下原始答案:

正如Daniel Wagner在另一个答案中所说的那样,这个定义

val = 1+2+3

只评估一次,除非它是多态的。如果你想在每次引用它时进行评估,你可以做

val () = 1+2+3

这样你必须将参数()赋予val,但它不会保存计算值。如果val的计算值占用大量内存,但很容易计算,并且在需要时逐步消耗,这可能是一件好事。例如,您可以列出斐波那契数字:

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

如果您现在fib !! 100000,则需要计算所有前100,000个斐波纳契数。值fib引用此列表,因此不能进行垃圾回收,但会在内存中挂起。为了解决这个问题,你可以做到

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

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

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