功能语言中的部分评估和函数内联之间有什么区别?

时间:2014-11-18 13:57:43

标签: haskell optimization functional-programming compiler-optimization inlining

我知道:

  1. 函数内联是用函数定义替换函数调用。
  2. 部分评估是在编译时评估程序的已知(静态)部分。
  3. 在命令式语言(如C)中,两者之间存在差异,其中运算符与函数不同。但是,在Haskell这样的函数式语言中,两者之间是否存在差异?运算符也是函数?

    两者之间的唯一区别是功能内联是否可以在程序的选择性部分上执行,而对整个程序进行部分评估(即 vs )?

    两种优化技术之间的语义差异是什么?

2 个答案:

答案 0 :(得分:6)

之间存在差异
  • 评估编译器(甚至预处理器)已知的一组给定运算符和函数上的常量表达式,这些运算符和函数发生在编译时。例如。编译器将print(2*2)编译为print(4)。这似乎并不局限于运算符表达式,因为您似乎暗示(例如print(sqrt(2.0)
  • 部分评估,这是一个更广泛的概念。编译器可以意识到print(myfunc(2))可以转换为print(c),其中c是调用myfunc(2)的结果。然后可以(在" 专业化时间")调用myfunc(2)来确定c。当然,如果myfunc有副作用,例如擦除自己的硬盘而不是程序用户,则会出现严重错误。因此编译器需要某种注释或属性来知道何时允许/期望(例如C ++ 11' s constexpr

内联是一个不相关的概念。内联函数调用意味着用被调用函数的主体替换调用。这个机构没有评估。

  

在命令式语言(如C)中,两者之间存在差异,其中运算符与函数不同。但是,在Haskell这样的函数式语言中,两者之间是否存在差异?运算符也是函数?

这种独特性(运算符与函数)纯粹是语法,与内联和部分评估之间的差异无关:

函数调用和带运算符的表达式都可以用C进行内联和编译时评估。编译时评估仅限于一组固定的运算符和函数(主要是运算符,但这是历史事故)的表达式

这两个概念都有意义,在Haskell中是不同的。

  • 内联:ghc{-# INLINE f #-},其中f无法递归,原因很明显,
  • 部分评估:这通常推广到Supercompilation,其中不仅基本类型的表达式被转换,甚至函数,例如,将map f (map g xs)转换为map (f . g) xs)。它也可以(但不必)进行内联。 Template Haskell是另一种在编译时(明确地)评估程序的一部分的方法。

因此,您的标题问题的答案是:内联评估和部分评估之间的区别与函数和运算符之间的差异无关,在函数式语言中与C语言的区别大致相同。由于副作用(参见上面的擦除硬盘),C中的评估可能更难以实现

答案 1 :(得分:0)

考虑以下代码:

map f xs = case xs of [] -> []; (x:xs') -> f x : map f xs'
f x y = if x % 2 == 0 then y + x / 2 else y + x

main = map (f 3) [1..100]

部分评估f 3后:

f3 y = if 3 % 2 == 0 then y + 3 / 2 else y + 3
main = map f3 [1..100]

然后在不断折叠3 % 2 == 0之后:

f3 y = y + 3
main = map f3 [1..100]

现在让我们考虑内联f

注意:f中的f 3不会被内联,因为它不是一个完整的应用程序,但是我们可以调整f的定义以使其成为现实,有关更多详细信息,请参见the ghc doc here

f x = \y -> if x % 2 == 0 then y + x / 2 else y + x

main = map (\y -> if 3 % 2 == 0 then y + 3 / 2 else y + 3) [1..100]

然后在不断折叠3 % 2 == 0之后:

main = map (\y -> y + 3) [1..100]

上面的示例表明,内联评估和部分评估都可以提供额外的优化机会,但是它们仍然有很大的不同:

  1. 特殊版本(上例中的f 3)出现多次时,内联将产生很多重复的代码。
  2. 当第一个参数不是静态常量时部分评估仍然有效,但是内联将不起作用。

对于第二点,请考虑以下示例:

main = [f x y | x <- [1..10], y <- [1..100]]

在这种情况下,部分评估可以为f x动态生成10个专用版本,但是内联却无能为力。