我想知道Haskell中最标准的方法是什么。
第一个明确指出我们需要两个参数(大多数时候)。
第二个涉及第二个子句中的函数调用(id
),因此效率较低,因为在第一个实现中我们可以简单地返回第二个参数。
所以我倾向于认为第一个更好,应该是一个选择:更容易阅读和弄清楚它做什么[1],以及函数调用保存。
但我是Haskell的新手,也许编译器会优化这个额外的调用。
xor :: Bool -> Bool -> Bool
xor True x = not x
xor False x = x
xor True = not
xor False = id
此外,我想知道我是否可以用通配符替换False
。
那么,Haskell的好习惯是什么?也许是另一种实施方式?
[1]我们在那里省略它是一个众所周知的功能,让我们想象它是一个非平凡的功能。
由于
答案 0 :(得分:14)
为了便于阅读,我会尝试避免模式匹配,并使用单个方程定义函数,该方程表达要定义的函数的有趣内容。这并不总是可行,但对于这个例子,有很多选择:
xor = (/=)
xor a b = a /= b
xor a b = not (a == b)
xor a b = (a && not b) || (not a && b)
xor a b = (a || b) && not (a && b)
xor a b = odd (fromEnum a + fromEnum b)
答案 1 :(得分:12)
当然这取决于编译器和传递给编译器的选项。
对于这个特定的例子,如果你编译没有优化,GHC会在你编写代码时生成代码,所以第二个版本包含对id
resp的调用。到not
。这比第一个版本效率稍低,第一个版本只包含对not
的调用:
Xors.xor1 :: GHC.Types.Bool -> GHC.Types.Bool -> GHC.Types.Bool
[GblId, Arity=2]
Xors.xor1 =
\ (ds_dkm :: GHC.Types.Bool) (x_aeI :: GHC.Types.Bool) ->
case ds_dkm of _ {
GHC.Types.False -> x_aeI;
GHC.Types.True -> GHC.Classes.not x_aeI
}
Xors.xor2 :: GHC.Types.Bool -> GHC.Types.Bool -> GHC.Types.Bool
[GblId, Arity=1]
Xors.xor2 =
\ (ds_dki :: GHC.Types.Bool) ->
case ds_dki of _ {
GHC.Types.False -> GHC.Base.id @ GHC.Types.Bool;
GHC.Types.True -> GHC.Classes.not
}
(调用仍然在生成的程序集中,但核心更具可读性,所以我只发布它。)
但是通过优化,两个函数都编译到同一个核心(从而编译到同一个机器代码),
Xors.xor2 =
\ (ds_dkf :: GHC.Types.Bool) (eta_B1 :: GHC.Types.Bool) ->
case ds_dkf of _ {
GHC.Types.False -> eta_B1;
GHC.Types.True ->
case eta_B1 of _ {
GHC.Types.False -> GHC.Types.True;
GHC.Types.True -> GHC.Types.False
}
}
GHC eta-扩展了第二个版本并内联了对id
和not
的调用,您获得了纯粹的模式匹配。
第二个等式是否使用False
或通配符在任一版本中都没有区别,无论是否有优化。
也许编译器会优化这个额外的调用。
如果你要求它进行优化,在这种简单的情况下,GHC将消除额外的呼叫。
让我们想象它是一个非平凡的功能。
这是一个可能的问题。如果代码非常简单,编译器可能无法消除通过定义函数而引入的所有调用,而不是提供所有参数。 GHC相当擅长这样做和内联调用,所以你需要相当多的非平凡性来使GHC无法在编译代码时消除对它所知道的简单函数的调用(它当然不会内联对函数的内联调用)不知道编译相关模块时的实现。)
如果是关键代码,请始终检查编译器生成的代码,对于GHC,相关标志为-ddump-simpl
以获得优化后生成的核心,-ddump-asm
获取生成的程序集。
答案 2 :(得分:4)
所以我倾向于认为第一个更好,应该是选择的人:更容易阅读并弄清楚它的作用
我同意可读性。然而,第二个是非常惯用的Haskell,对于有经验的程序员来说更容易阅读:不执行那些微不足道的eta减少是非常可疑的,并且实际上可能会分散注意力。因此,对于优化版本,我宁愿以明确的形式完整地写出来:
True `xor` False = True
False `xor` True = True
_ `xor` _ = False
但是,如果这种替代方案的可读性远远低于最惯用的方法,那么您应该考虑不替换,但添加提示以便编译器仍然可以将其优化为理想版本。正如Daniel Fischer所证明的那样,GHC本身非常聪明,并且经常在没有帮助的情况下正确行事;如果没有,可能有助于添加一些INLINE
和/或RULES
pragma。要弄清楚如何才能获得最佳性能并不容易,但编写快速Haskell98代码也是如此。