在haskell中添加函数定义的LHS参数的语义是什么?

时间:2016-04-26 13:52:40

标签: haskell functional-programming lambda-calculus

我是haskell的初学者并试图理解Let vs Where wiki page。最后有一个例子,在函数定义x的左侧添加参数fib会改变语义。

fib1 =
   let fib' 0 = 0
       fib' 1 = 1
       fib' n = fib1 (n - 1) + fib1 (n - 2)
   in  (map fib' [0 ..] !!)

fib2 x =
   let fib' 0 = 0
       fib' 1 = 1
       fib' n = fib2 (n - 1) + fib2 (n - 2)
   in  map fib' [0 ..] !! x

维基页面指出“在第二种情况下[fib2],为每个参数x”重新定义了fib'。我正在寻找一个初学者友好的解释为什么会发生这种情况,一般来说,是否有更多这样隐藏的副作用?

维基页面还有一个指向eta reduction的解释的链接,其中指出表达式及其eta减少是等效的。那么,如果fib2fib1的eta抽象,为什么它们不等同?

1 个答案:

答案 0 :(得分:5)

您的最后一点可能是最重要的一点 - fib1fib2 确实是等效的,编译器可以完全自由地将一个转换为另一个。由于这种转换对指称语义没有影响,只有操作语义,如果它“改进”程序的操作语义,它通常被称为优化。麻烦的是,很难说出“改进”的真正含义。

维基页面说明

  

编译器无法知道您是否打算这样做 - 而它   增加时间复杂度可能会降低空间复杂性。

这是事实 - 一般来说,这种“优化”可能不是优化,具体取决于您如何衡量绩效。因此,一般情况下,编译器不会执行此优化,如果他们愿意,请将这样做的负担留给程序员,除非它确实确定它确实会提高代码的性能。

差异是一种称为共享的特征。在fib1中,fib'中的map fib'来电将在fib1功能内fib'的不同来电之间共享。这意味着只要计算函数,就必须保留整个列表map fib' [0..n]。在某些情况下,这可能会对性能造成不利影响,但在这种情况下,它会为您节省很多递归函数调用。在fib2中,由于参数位于map fib'之外,因此每个let都是单独计算的。考虑一下这个程序,即使没有优化也相当于fib1

fib3 :: Int -> Integer 
fib3 =
   let fib' 0 = 0
       fib' 1 = 1
       fib' n = fib1 (n - 1) + fib1 (n - 2)
    in \x -> map fib' [0 ..] !! x

\x -> ..的展示位置至关重要 - 此处为let fib' = .. in \x -> map fib' ..,但fib2\x -> let fib' = .. in map fib' ..

  

因此它不会将定义从x的绑定中浮出来。

这显然取决于您使用的特定编译器以及编译程序的方式!我假设这个wiki页面是在编译器不够聪明而无法弄清楚这个特定示例的时候编写的,但是对于GHC 7.10.3和-O2,编译器实际上生成了相同的这两个程序的代码。

如果您在没有优化或解释的情况下进行编译,则性能差异将变得明显。这不是由于单态性限制 - 即使你给函数提供单态类型也存在:

fib1 :: Int -> Integer 
fib1 = ..

fib2 :: Int -> Integer 
fib2 x = ..

差别很明显:

>:set +s
>fib2 32
2178309
(10.86 secs, 6,063,137,456 bytes)
>fib1 32
2178309
(0.00 secs, 0 bytes)