2013-07-08 5 views
7

ハスケルで最も標準的な方法は何ですか?ハスケルでXORのどちらが良いと思われますか

最初の1つは、2つの引数(ほとんどの場合)が必要であることを明確に示しています。

2番目の句に2番目の句には関数呼び出し(id)が含まれているため、最初の実装では2番目の引数を単に返すことができるため、効率が悪いはずです。

私は最初の方が優れていると思う傾向があります。読みやすく、何をしているのかを把握し、関数呼び出しを保存しておく必要があります。

しかし私はHaskellの初心者です。おそらくこのコンパイラはこの余分な呼び出しを最適化します。

xor :: Bool -> Bool -> Bool 

xor True x = not x 
xor False x = x 

xor True = not 
xor False = id 

また、私はそこにワイルドカードで両方Falseを置き換えることができますかどうかを知りたいと思います。

だから、ハスケルの良い習慣は何ですか?たぶん別の実装ですか?

[1]それはよく知られている機能であり、それが重要ではない機能であると考えてみましょう。

ありがとう

答えて

11

もちろん、コンパイラとコンパイラに渡されるオプションによって異なります。

この特定の例では、最適化なしでコンパイルすると、GHCはコードを作成したので、2番目のバージョンにはid respの呼び出しが含まれています。 〜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 
    } 

(呼び出しが生成されたアセンブリに残っているが、コアは、より読みやすいので、私はそれだけで後):それはわずかに少ない効率的なだけ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のイータ展開秒バージョンとidnotへの呼び出しをインライン化、あなたが得ます純粋なパターンマッチング。

2番目の式でFalseまたはワイルドカードを使用するかどうかによって、どちらのバージョンでも最適化の有無に違いはありません。

多分、この余分な呼び出しを最適化するコンパイラです。

このような簡単なケースでは、最適化をお願いすると、GHCは余分な呼び出しをなくします。

これは重要な機能ではないと考えてみましょう。

これは考えられる問題です。コードが十分に自明でない場合、コンパイラは、すべての引数が提供されていない関数を定義することによって導入されたすべての呼び出しを排除できない場合があります。しかし、GHCは、コードをコンパイルするときに知っている単純な関数への呼び出しを取り除くためにかなりの量の非自明性を必要としています(もちろん、関数にインラインで呼び出すことはできません問題のモジュールをコンパイルするときの実装を知っていない)。

重要なコードの場合は、コンパイラが生成するコードを常にチェックします.GHCの場合、関連するフラグは-ddump-simplで最適化後のコアを取得し、-ddump-asmは生成されたアセンブリを取得します。

+0

Sこれらは同じです...この明確なデモのおかげで(そしてGHCのヒントのために、非常に便利です) – niahoo

4

だから私は、最初は優れていると選択するものでなければならないと考える傾向にある:読みやすくするために、それは私が読みやすさについて同意

を何把握します。しかし、2番目は非常にイディオム的なハスケルであり、経験豊富なプログラマーにとっては読みやすいものです。些細なηの減少を実行しないことはかなり疑わしく、実際に意図から逸脱するかもしれません。だから、最適化されたバージョンのために、私はむしろ、明示的な形で完全にそれを記述します:

True `xor` False = True 
False `xor` True = True 
_  `xor` _  = False 

をしかし、そのような代替はあなたがいないそれを置き換えることを検討すべき最も慣用的なものよりもかなり少ない読み取り可能である場合には、しかし、ヒントを追加するので、コンパイラはそれを最適なバージョンに最適化できます。ダニエル・フィッシャーが示すように、GHCはそれ自体が非常に巧妙であり、しばしば助けなしにそれを正しく得るでしょう。そうでない場合は、INLINEおよび/またはRULESプラグマを追加すると役立ちます。最適なパフォーマンスを得るためにこれを行う方法を理解することは容易ではありませんが、高速のHaskell98コードを書く場合も同じです。

+0

ああ、私は入れ墨のスタイルがブーリアンにも良いと思います。 – niahoo

+1

'False'の結果の場合の代わりに' True'の結果のためのケースを指定すると、これが(さらに)読みやすくなると思います。それは論理的な結合が通常指定される方法です:あなたは真実について話し、他はすべて偽とみなされます。 – Toxaris

+0

@トクサリス:あなたはポイントを持っています。私は 'False' /' 0'/'[]'からすべてのケースを始める傾向があります。なぜなら、それはコードに簡単な列挙型の向きを与えるからです。 – leftaroundabout

13

読みやすくするために、私はパターンマッチングを避け、定義する関数について興味深いものを表現する単一の式で関数を定義しようとします。それは常に可能ではないのですが、この例のために、多くのオプションがあります。

  • 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)
+0

こんにちは、ありがとう、定義4,5,6はより自然なようだが、より多くの操作が必要なので、これを避けるためにパターンマッチングを使用しています。 – niahoo

+0

が1,2,2,3がトップです:) – niahoo

+0

うわー、簡単な解決法ではありませんか?これらのおかげで、私はそれをよりよく理解できるようになりました。 – vikingsteve

関連する問題