在Haskell中哪一个被认为是更好的XOR

nia*_*hoo 7 optimization haskell

我想知道Haskell中最标准的方法是什么.

第一个明确指出我们需要两个参数(大多数时候).

第二个涉及id第二个子句中的函数call(),因此效率较低,因为在第一个实现中我们可以简单地返回第二个参数.

所以我倾向于认为第一个更好,应该是一个选择:更容易阅读和弄清楚它做什么[1],以及函数调用保存.

但我是Haskell的新手,也许编译器会优化这个额外的调用.

xor :: Bool -> Bool -> Bool

xor True x = not x
xor False x = x

xor True = not
xor False = id
Run Code Online (Sandbox Code Playgroud)

此外,我想知道我是否可以用False那里的通配符替换它们.

那么,Haskell的好习惯是什么呢?也许另一个实现?

[1]我们在那里省略它是一个众所周知的函数,让我们想象它是一个非平凡的函数.

谢谢

Tox*_*ris 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)


Dan*_*her 12

当然,这取决于编译器和传递给编译器的选项.

对于这个特定的例子,如果你编译没有优化,GHC会在你编写代码时生成代码,所以第二个版本包含对idresp 的调用.到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
    }
Run Code Online (Sandbox Code Playgroud)

(调用仍然在生成的程序集中,但核心更具可读性,所以我只发布它).

但是通过优化,两个函数都编译到同一个核心(从而到同一个机器代码),

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
        }
    }
Run Code Online (Sandbox Code Playgroud)

GHC eta扩展了第二个版本并内联调用idnot,您获得了纯粹的模式匹配.

第二个等式是使用False还是通配符在任一版本中都没有区别,无论是否有优化.

也许编译器会优化这个额外的调用.

如果你要求它进行优化,在这种简单的情况下,GHC将消除额外的呼叫.

让我们想象它是一个非平凡的功能.

这是一个可能的问题.如果代码非常简单,编译器可能无法消除通过定义函数而引入的所有调用,而不是提供所有参数.GHC相当擅长这样做和内联调用,所以你需要相当多的非平凡性来使GHC无法在编译代码时消除对它所知道的简单函数的调用(它当然不会内联对函数的内联调用)不知道编译相关模块时的实现.

如果是关键代码,请始终检查编译器生成的代码,对于GHC,相关标志是-ddump-simpl在优化后生成核心,并-ddump-asm获取生成的程序集.