【问题标题】:Scala's style val in HaskellScala 在 Haskell 中的风格验证
【发布时间】:2013-09-30 05:36:28
【问题描述】:

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

def function = 1 + 2 + 3

或一个只评估一次的val

val val1 = 1 + 2 + 3

即使我多次调用它。

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

function = 1 + 2 + 3

我可以在 Haskell 中定义类似 Scala 的 val 吗?

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

更新 2:

function 的值将被计算多少次?

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

【问题讨论】:

  • 谁说函数在haskell中一次又一次地被评估??
  • 这不是“一遍又一遍”评估的
  • 这个问题是基于对 Haskell 行为的一些误解。
  • 更新 2 的答案是:“至少一次。”您似乎对 Haskell 的一个特别不寻常的怪癖感到困惑:根据设计,没有办法告诉某事被评估了多少次,并且尝试考虑 Haskell 代码并不是特别有成效方式。在像 Scala 这样的语言中,了解与 function 关联的表达式被评估了多少次可能很重要,例如因为它打印了一些东西;在 Haskell 中,无法判断,所以假设编译器会尝试以最有效的方式评估它并考虑其他事情。
  • @jacobm 我认为区分 Haskell 规范和典型实现是有意义的。正如您所说,Haskell 规范几乎什么也没说,这意味着“至少一次”是您能说的最好的。但是如果你开始谈论一个特定的实现——例如GHC——那么你可以多说一点,在这种情况下,你也可以说“最多一次”。

标签: scala haskell


【解决方案1】:

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

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

【讨论】:

  • 这是一个常数。常量只是一个 0 元纯函数。
  • 不,在 Haskell 中,函数只有一个参数。如果它没有参数,它就不是一个函数。
  • @JörgWMittag 是的,我同意,我可以称它为 0 元函数,特别是当它是自然泛化为 0 元的 n 元函数序列的一部分时-性。 (我有点反对这里的流行观点。)但是将其称为没有任何限定符(如 0-ary)并且不清楚作为此类序列的一部分的函数是非常奇怪的。
  • @Alex 据我所知,您的更新不会改变我的答案。
【解决方案2】:

你当然可以写:

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 版本完全相同的类似物。

【讨论】:

    【解决方案3】:

    编辑:忽略这个答案。它仅在您未经优化进行编译时才有效。以我的 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 在 cmets 中所说,let 表达式的内容可能会被不友好的编译器提升,这会导致不必要的共享

    【讨论】:

    • fib () = 0 : 1 : zipWith (+) fib (tail fib) 坏了。也许你的意思是fib () = 0 : 1 : zipWith (+) (fib ()) (tail (fib ()))?但这并不记得。 fib () = let x = 0 : 1 : zipWith (+) x (tail x) in x 可能是您想要的,但是 let 表达式的内容可能会被不友好的编译器提升,这会导致不必要的共享。
    • @PhilipJF:Doh。我不应该在仍在醒来的过程中编写代码 :) 感谢您的关注。
    • 你测试过这个吗?我怀疑你会发现,启用优化后,GHC 对你来说太聪明了:x 会为你浮出到顶层,调用新的fib () !! 100000 会像旧的一样占用内存fib !! 100000 做到了。另请参阅this question about how to properly prevent CAFs from sharing memory
    • @DanielWagner:我测试了它,它的表现当然如你所说。谢谢你带我第一次看Core,很受启发。
    • val () = 1+2+3。我怎么称呼它?我不能只说 print val,它不起作用。你能看看我的update2吗?
    猜你喜欢
    • 2017-05-05
    • 2010-10-28
    • 1970-01-01
    • 2011-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多