web-dev-qa-db-ja.com

関数型プログラミングにおけるポリモーフィズムの達成

私は現在、オブジェクト指向言語から関数型言語への移行を楽しんでいます。それは新鮮な空気の息吹であり、私は以前よりもはるかに生産的になっています。

ただし、-OOPには、FP側で満足のいく答えがまだ見られないという側面があります。それは、ポリモーフィズム。つまり、特定の関数に渡されるときにまったく異なる方法で処理する必要があるデータ項目の大規模なコレクションがあります。議論のために、多態的な動作を潜在的に指数関数的に駆動する複数の要因があるとしましょう。多くの異なる動作の組み合わせ。

In OOPこれは、ポリモーフィズムを使用して比較的適切に処理できます。構成+継承またはプロトタイプベースのアプローチのいずれかを使用します。

FP私は次の間に少し立ち往生しています:

  • 各データ項目の値を分岐することによって多態的な動作を効果的に実装する純粋関数を記述または構成することは、巨大な条件付きを組み立てたり、仮想メソッドテーブルをシミュレートしたりするような感覚です。
  • プロトタイプのように純粋データ構造内に関数を配置する-これは機能しているように見えますが、データとは別に純粋関数を定義するという考えにも違反していませんか?

この種の状況で推奨される機能的アプローチは何ですか?他に良い選択肢はありますか?

52
mikera

プロトタイプのように純粋データ構造内に関数を配置する-これは機能しているように見えますが、データとは別に純粋関数を定義するという考えにも違反していませんか?

仮想メソッドディスパッチが問題にアプローチしたい方法である場合、これは完全に合理的なアプローチです。関数をデータから分離することに関しては、それはそもそも明らかに非機能的な概念です。関数型プログラミングの基本原理は、関数はデータであるということだと思います。そして、仮想関数をシミュレートしているというあなたの感覚については、それはまったくシミュレーションではないと私は主張します。 IS仮想関数テーブルであり、それはまったく問題ありません。

言語にOOPサポートが組み込まれていないからといって、同じ設計原則を適用することが合理的でないという意味ではありません。つまり、他の機械よりも多くの機械を作成する必要があるということです。使用している言語の自然な精神と戦っているため、言語には組み込みがあります。現代の型付き機能言語は、ポリモーフィズムを非常に深くサポートしていますが、ポリモーフィズムへのアプローチは大きく異なります。

OOPのポリモーフィズムは、ロジックの「存在記号」によく似ています。ポリモーフィズムの値には実行時型がいくつかありますが、それが何であるかはわかりません。多くの機能プログラミング言語では、ポリモーフィズムは「ユニバーサル数量化」のようなもの-ポリモーフィック値は、ユーザーが望む任意の互換性のあるタイプにインスタンス化できます。これらはまったく同じコインの両面です(特に、関数を表示しているかどうかに応じて場所を入れ替えます。 「内部」または「外部」)が、「コインを公平にする」言語を設計する場合、特にサブタイピングや高種類のポリモーフィズム(ポリモーフィズムよりもポリモーフィズム)などの他の言語機能が存在する場合は、非常に難しいことがわかります。タイプ)。

それが役立つ場合は、関数型言語のポリモーフィズムをC#やJavaの「ジェネリック」に非常によく似たものと考えることをお勧めします。これは、MLやHaskellなどのポリモーフィズムが好むタイプだからです。

22
mokus

ええと、Haskellではいつでも型クラスを作って一種のポリモーフィズムを実現することができます。基本的には、さまざまなタイプに対して処理される関数を定義しています。例は、クラスEqおよびShowです。

_data Foo = Bar | Baz

instance Show Foo where
    show Bar = 'bar'
    show Baz = 'baz'

main = putStrLn $ show Bar
_

関数show :: (Show a) => a -> Stringは、型クラスShowをインスタンス化するすべてのデータ型に対して定義されています。コンパイラは、タイプに応じて、適切な関数を見つけます。

これにより、次のような関数をより一般的に定義できます。

_compare a b = a < b
_

型クラスOrdの任意のタイプで機能します。これはOOPとまったく同じではありませんが、次のような型クラスを継承することもできます。

_class (Show a) => Combinator a where
    combine :: a -> a -> String
_

実際の関数を定義するのはインスタンス次第です。仮想関数と同様に、タイプのみを定義します。

これは完全ではなく、私が知る限り、多くのFP言語は型クラスを備えていません。OCamlは備えていません。それをOOPそして、Schemeにはタイプがありませんが、Haskellでは、制限内で一種のポリモーフィズムを実現するための強力な方法です。

さらに進んで、2010標準の新しい拡張により、型族などが許可されます。

これが少しお役に立てば幸いです。

13
Lanbo

誰が言ったの

データとは別に純粋関数を定義する

ベストプラクティスは何ですか?

ポリモーフィックオブジェクトが必要な場合は、オブジェクトが必要です。関数型言語では、オブジェクトは、「純粋データ」のセットとそのデータを操作する「純粋関数」のセットを結合することによって構築できます。これは、クラスの概念がなくても機能します。この意味で、クラスは、関連する「純粋関数」の同じセットでオブジェクトを構築するコードの一部に他なりません。

また、ポリモーフィックオブジェクトは、オブジェクトのこれらの関数の一部を同じ署名を持つ異なる関数に置き換えることによって構築されます。

関数型言語(Schemeなど)でオブジェクトを実装する方法について詳しく知りたい場合は、この本を参照してください。

Abelson/Sussman:「コンピュータプログラムの構造と解釈」

11
Doc Brown

マイク、あなたの両方のアプローチは完全に受け入れられます、そして、Doc Brownが言うように、それぞれの賛否両論はSICPの第2章で議論されます。 1つ目は、どこかに大きなタイプのテーブルがあり、それを維持する必要があるという問題があります。 2番目のis従来のシングルディスパッチポリモーフィズム/仮想関数テーブル。

スキームにシステムが組み込まれていない理由は、問題に対して間違ったオブジェクトシステムを使用すると、あらゆる種類の問題が発生するためです。言語設計者の場合、どちらを選択しますか?単一のディスパッチ単一の継承は、「多形性の動作を駆動する複数の要因、したがって指数関数的に多くの異なる動作の組み合わせ」をうまく処理しません。

概要を説明すると、オブジェクトを作成する方法はたくさんあります。SICPで説明されている言語であるスキームは、必要なツールキットを作成するための基本的なツールキットを提供します。

実際のスキームプログラムでは、オブジェクトシステムを手動で構築してから、関連する定型文をマクロで非表示にします。

Clojureには、実際にはマルチメソッドが組み込まれたビルド済みのオブジェクト/ディスパッチシステムがあり、従来のアプローチに対する利点の1つは、すべての引数のタイプでディスパッチできることです。階層システムを使用して、継承のような機能を提供することもできますが、私はこれを使用したことがないので、それを取る必要がありますcum grano salis

ただし、言語設計者が選択したオブジェクトスキームとは異なるものが必要な場合は、適切なものを1つ(または複数)作成できます。

それは事実上あなたが上で提案していることです。

必要なものを作成し、すべてを機能させ、マクロで詳細を非表示にします。

FPとOOの間の議論は、データの抽象化が悪いかどうかではなく、データの抽象化システムが関心の分離をすべて詰め込む場所であるかどうかについてです。プログラム。

「プログラミング言語では、新しいデータ型を定義できるはずだと思います。プログラムが新しいデータ型の定義だけで構成されるべきだとは思いません。」

http://www.haskell.org/haskellwiki/OOP_vs_type_classes#Everything_is_an_object.3F いくつかの解決策についてうまく説明しています。

1
thSoft