代数データ型はどのような問題を解決しますか?


18

公正な警告、私は関数型プログラミングは初めてなので、多くの悪い仮定を抱くかもしれません。

私は代数型について学んでいます。多くの関数型言語にはそれらが含まれているようで、パターンマッチングと併用するとかなり便利です。ただし、実際にどのような問題を解決しますか?次のように、C#で一見(一種の)代数型を実装できます。

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

しかし、これはオブジェクト指向プログラミングのろくでなしであることにほとんどの人が同意すると思うので、絶対にやるべきではありません。それで、関数型プログラミングは、このスタイルのプログラミングをあまり目立たないようにする、より簡潔な構文を追加するだけですか?他に何が欠けていますか?


6
関数型プログラミングは、オブジェクト指向プログラミングとは異なるパラダイムです。
バジルStarynkevitch

@BasileStarynkevitch私は理解していますが、F#のような両方の言語が少しあります。問題は関数型言語ではなく、代数的データ型が解決する問題についてです。
ConditionRacer

7
あなたが期待しclass ThirdOption : Option{}new ThirdOption()場所を定義してあなたに与えるとどうなりますSomeNone
アモン

1
合計型を持つ@amon言語には、一般にそれを拒否する方法があります。たとえば、Haskellは次のように定義しますdata Maybe a = Just a | Nothing(例の場合と同じdata Option a = Some a | Noneです):3番目のケースを事後的に追加することはできません。あなたが示した方法でC#で合計タイプをエミュレートすることはできますが、それは最もきれいではありません。
マーティン

1
「ADTはどのような問題を解決するのか」というよりも、「ADTは物事にアプローチする別の方法」に似ていると思います。
MathematicalOrchid

回答:


44

インターフェイスと継承を備えたクラスは、オープンな世界を提供します誰でも新しい種類のデータを追加できます。特定のインターフェースに対して、世界中のさまざまなファイル、さまざまなプロジェクト、さまざまな企業でそれを実装するクラスが存在する場合があります。データ構造にケースを簡単に追加できますが、インターフェイスの実装は分散化されているため、新しいメソッドをインターフェイスに追加するのは困難です。インターフェースが公開されると、基本的に凍結されます。可能な実装をすべて知っている人はいません。

代数的データ型はそれに対する双対であり、閉じられています。データのすべてのケースが1か所にリストされており、操作ではバリアントを網羅的にリストできるだけでなく、リストに追加することをお勧めします。その結果、代数データ型で動作する新しい関数を書くのは簡単です:いまいましい関数を書くだけです。その見返りに、基本的にコードベース全体を調べて、すべてを拡張する必要があるため、新しいケースの追加は複雑ですmatch。インターフェースの場合と同様に、Rust標準ライブラリでは、新しいバリアントの追加は重大な変更です(パブリックタイプの場合)。

これらは表現問題の両面です。代数データ型はそれらに対する不完全な解決策ですが、OOPもそうです。どちらにも、データのケースの数、それらのケースが変更される頻度、および操作が拡張または変更される頻度に応じて利点があります。(これが、多くの現代言語が両方または類似したものを提供する理由であるか、両方のアプローチを包含しようとするより強力でより複雑なメカニズムに直行する理由です。


一致の更新に失敗した場合、コンパイラエラーが発生しますか、それとも実行時にのみ判明しますか?
イアン

6
@Ianほとんどの関数型言語は静的に型付けされ、パターンマッチングの網羅性をチェックします。ただし、「catch all」パターンがある場合、その機能を実行するために関数が新しいケースを処理する必要がある場合でも、コンパイラは満足しています。さらに、すべての依存コードを再コンパイルする必要があります。1つのライブラリだけをコンパイルして、既にビルドされたアプリケーションに再リンクすることはできません。

また、パターンが網羅的であることを静的にチェックすることは、一般的に費用がかかります。それはせいぜいSATの問題を解決し、最悪の場合、言語はこれを決定不能にする任意の述語を許可します。
usr

@usr言語なし私は完璧な解決策を試みていることを知っています。コンパイラが網羅的であることを理解するか、クラッシュして燃え尽きるキャッチオールケースを追加することを余儀なくされます。SATとの関係はわかりませんが、削減へのリンクはありますか?とにかく、実際のプログラムで記述された実際のコードの場合、徹底的なチェックはバケットの低下です。

N個のブール値で一致するとします。次に、(a、b、_、d、...)のようなmatch句を追加します。キャッチオールケースは!clause1 &&!clause2 && ...です。これは私にはSATのように見えます。
usr

12

それで、関数型プログラミングは、このスタイルのプログラミングをあまり目立たないようにする、より簡潔な構文を追加するだけですか?

それはおそらく簡略化ですが、はい。

他に何が欠けていますか?

代数的データ型が何であるかを明確にしましょう(Learn you as Haskellからのこの素晴らしいリンクを要約します):

  • 「この値はA または Bになります」という合計タイプ
  • 「この値はA Bの両方です」という製品タイプ

あなたの例は最初の例でのみ本当に機能します。

おそらく欠けているのは、これら2つの基本操作を提供することにより、関数型言語を使用して他のすべてを構築できることです。C#には、これらの上に構造体、クラス、列挙、ジェネリック、およびこれらの動作を管理するルールの山があります。

役立つ構文と組み合わせることで、関数型言語は操作をこれらの2つのパスに分解し、型に対する簡潔でシンプルでエレガントなアプローチを提供できます。

代数データ型はどのような問題を解決しますか?

これらは、他の型システムと同じ問題を解決します。「ここで使用できる値は何ですか?」-彼らはそれに対して異なるアプローチをとります。


4
あなたの最後の文は、「船は飛行機と同じ問題を解決する:輸送-彼らはただ異なるアプローチをとる」と誰かに言うようなものです。それは完全に正しい記述であり、かなり役に立たない記述でもあります。
Mehrdad

@mehrdad-少し誇張していると思います。
テラスティン

1
間違いなく代数的なデータ型を理解していることは間違いありませんが、箇条書きリスト(「合計型は...」および「製品型は...」)は、ユニオン型およびインターセクション型の説明のように見えます。合計および製品タイプとまったく同じです。
ピオン

6

パターンマッチングは、オプションを操作するための最も慣用的な方法とは見なされないことを知って驚くかもしれません。詳細については、Scalaのオプションのドキュメントを参照してください。これほど多くのFPチュートリアルがこの使用法を推奨する理由はわかりません。

主に不足しているのは、Optionsの操作を簡単にするために作成された多数の関数があることです。Scalaドキュメントの主な例を考えてみましょう。

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

どのように注意してくださいmapfilterあなたが持っているかどうかを各ポイントでチェックせずに、オプションの操作を連鎖させNoneたりありません。次に、最後にを使用getOrElseしてデフォルト値を指定します。タイプをチェックするような「総計」を行うことはありません。やむを得ないタイプチェックは、ライブラリの内部で行われます。Haskellには、任意のモナドまたはファンクターで機能する大規模な関数セットは言うまでもなく、独自の類似した関数セットがあります。

他の代数データ型には、独自の慣用的な方法があり、ほとんどの場合、トーテムポールでのパターンマッチングは低くなっています。同様に、独自の型を作成する場合、それらと連携する同様の機能を提供することが期待されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.