コンビネーターとは何ですか?また、それらはプログラミングプロジェクトにどのように適用されますか?(実用的な説明)


51

コンビネーターとは何ですか?

を探しています:

  • 実用的な説明
  • それらの使用例
  • コンビネーターがコードの品質/汎用性を改善する方法の例

私は探していません:

  • 作業の完了に役立たないコンビネータの説明(Yコンビネータなど)

コンビネータは「副詞」に似ており、関数を取り込んで他の関数を返す関数です。変数の間に必要ないため、コードの重複を削除するのに役立ちます。便利なものには、twice(f)= \ x-> f(f(x))、flip(op)-> \ xy-> y op x、(。)のように(fg)x = f(g(x ))、($)は、($ 5)<$> [(+1)、(* 2)] = [6、10]のように、マップ(挿入記号で<$>と呼ばれる)に役立ちます。Lispではカレーを使用できます/ Python / JavaScriptの部分的なアプリケーション、およびuncurryは、Haskellのレコード(タプル)を必要とする関数に使用できます。x |> f = faの場合、x |>(長さ&&&合計)|>通貨(/)は平均です。
aoeu256

回答:


51

実用的な観点から、コンビネーターは、興味深い高度な方法でロジックの断片をまとめることができるプログラミング構造の一種です。通常、それらを使用するのは、実行可能コードを(歴史的な理由から)ラムダ関数またはラムダ式と呼ばれることが多いオブジェクトにパックできるかどうかによって異なりますが、使用する距離は異なります。

(有用な)コンビネーターの簡単な例は、パラメーターなしで2つのラムダ関数を取り、それらを順番に実行する新しいラムダ関数を作成するものです。実際のコンビネータは、次のような一般的な擬似コードに見えます。

func in_sequence(first, second):
  lambda ():
    first()
    second()

これをコンビネータにする重要なことは、2行目の匿名関数(ラムダ関数)です。あなたが電話するとき

a = in_sequence(f, g)

結果のオブジェクトaは、最初にf()を実行してからg()を実行した結果ではありませんが、後で呼び出してf()およびg()を順番に実行できるオブジェクトです。

a() // a is a callable object, i.e. a function without parameters

同様に、2つのコードブロックを並行して実行するコンビネーターを使用できます。

func in_parallel(first, second):
  lambda ():
    t1 = start_thread(first)
    t2 = start_thread(second)
    wait(t1)
    wait(t2)

そして再び、

a = in_parallel(f, g)
a()

クールなことは、「in_parallel」と「in_sequence」は両方とも同じタイプ/シグネチャを持つコンビネーターです。つまり、どちらも2つのパラメーターなしの関数オブジェクトを取り、新しいオブジェクトを返します。実際に次のように書くことができます

a = in_sequence(in_parallel(f, g), in_parallel(h, i))

期待どおりに機能します。

基本的に、コンビネータを使用すると、プログラムの制御フローを(特に)手続き的で柔軟な方法で構築できます。たとえば、in_parallel(..)コンビネータを使用してプログラムで並列処理を実行する場合、それに関連するデバッグをin_parallelコンビネータ自体の実装に追加できます。後で、プログラムに並列処理関連のバグがあると思われる場合は、実際にin_parallelを再実装できます。

in_parallel(first, second):
  in_sequence(first, second)

そして、1回のストロークで、すべての平行セクションが順次セクションに変換されました!

コンビネータは、正しく使用すると非常に便利です。

ただし、Yコンビネータは実際には必要ありません。これは、自己再帰関数を作成できるコンビネーターであり、Yコンビネーターを使用せずに現代言語で簡単に作成できます。


9

Y-combinatorを「作業の完了に役立つ」ものとしてブランド化するのは間違っています。多くの場合に非常に便利だと感じました。最も明らかなケースは、組み込みのインタープリター言語をすばやくブートストラップする必要がある場合です。あなたはプリミティブの最小限のセットを提供する場合、すなわちsequenceselectcallconstそしてclosure allocation、それはすでに完了し、任意の複雑な言語を構築するのに十分です。再帰の特別なサポートは必要ありません-固定小数点コンビネーターを介して追加できます。そうしないと、はるかに複雑なプリミティブが必要になります。

コンビネーターのもう1つの明らかなケースは難読化です。SKI計算に変換されたコードは、実際には判読できません。アルゴリズムの実装を本当に難読化する必要がある場合は、コンビネーターの使用を検討してください。以下に例を示します。

そして、もちろん、コンビネーターは関数型言語を実装するための重要なツールです。最も簡単なアプローチ(上記の例のように)は、SKIまたは同等の計算を介して行われます。スーパーコンビネータは、他のいくつかの実装で使用されます。この本はそれについて詳しく説明しています。

これは冗談ですが、多くの難解なプログラミング手法と理論がそこにカバーされているので、非常に注意深く読む価値のある冗談です。


1
@MattFenwick、単純な組み込みのインタープリターをドロップする必要がしばしば発生します。たとえば、私の場合は、通信プロトコルを拡張するために設計しなければならなかった言語でした。単純なIPCでは十分ではなかったため、プロトコルは実行可能でなければなりませんでした。
SKロジック

@MattFenwick、あなたの質問に関して:APLまたはJでいくつかのコードを書くことができます。そこではコンビネータが不可欠なので、それらを適切に適用する方法のアイデアを得るでしょう。また、ポイントフリースタイルの読書は役立つかもしれません:en.wikipedia.org/wiki/Tacit_programming
SK-logic

7

少し掘り下げて、StackOverflowの質問を見つけました。「コンビネーター」(数学者ではない人)の良い説明です。これはこの質問と密接な関係があります。 回答の1つはReginald Braithwaiteのブログ、Homoiconicを指していました。これは、コード内のコンビネーターのいくつかの有用な例にリンクしています(たとえば RubyのObject#tapメソッドによって実装されるKコンビネーター。

Combinatory LogicWikipediaページには、コンビネーターがよりグローバルに説明されています。


これは私の質問の2番目の箇条書きに対処します。例に感謝します!

1
この投稿にはいくつかの良いリンクがありますが、実際には質問に直接回答していません。回答はそれ自体で完成し、必要に応じてリンクを参照として使用する必要があります。
アーロンノート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.