モナドは、継承階層の実行可能な(たぶん望ましい)代替手段ですか?


20

このようなモナドの言語に依存しない説明を使用します。最初にモノイドを説明します。

モノイドは(おおよそ)のパラメータとして、いくつかの種類を取り、同じ型を返す関数のセットです。

モナドを取る関数の(おおよそ)が設定されているラッパーパラメータとしてタイプと同じラッパー・タイプを返します。

これらは説明であり、定義ではないことに注意してください。その説明を気軽に攻撃してください!

したがって、OO言語では、モナドは次のような操作構成を許可します。

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

モナドは、含まれるクラスではなく、これらの操作のセマンティクスを定義および制御することに注意してください。

従来、OO言語では、クラス階層と継承を使用してこれらのセマンティクスを提供していました。したがってBird、メソッドtakeOff()flyAround()およびを持つクラスがありland()、Duckはそれらを継承します。

しかし、その後、penguin.takeOff()失敗するため、飛べない鳥とのトラブルに巻き込まれます。例外のスローと処理に頼らなければなりません。

また、ペンギンがであると言うとBird、たとえばの階層もある場合、多重継承の問題に遭遇しますSwimmer

基本的に、私たちはクラスをカテゴリーに分類し(カテゴリー理論に謝罪して)、個々のクラスではなくカテゴリーごとにセマンティクスを定義しようとしています。しかし、モナドは階層よりもそれを行うためのはるかに明確なメカニズムのようです。

したがって、この場合、Flier<T>上記の例のようなモナドができます。

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

...そして、インスタンス化することはありませんFlier<Penguin>。おそらくマーカーインターフェイスを使用して、静的な型付けを使用してそれを防ぐこともできます。または、実行時の機能チェックで解決します。しかし、実際には、プログラマーはゼロで割るべきではないという意味で、ペンギンをフライヤーに入れてはいけません。

また、より一般的に適用できます。チラシは鳥である必要はありません。たとえばFlier<Pterodactyl>、またはFlier<Squirrel>、これらの個々のタイプのセマンティクスを変更せずに。

型階層ではなく、コンテナ上の構成可能な関数によってセマンティクスを分類すると、特定の階層に適合する「やる、しない」クラスの古い問題を解決します。また、簡単&はっきりと同じように、クラスの複数の意味を可能にするFlier<Duck>だけでなく、Swimmer<Duck>。動作をクラス階層で分類することにより、インピーダンスの不一致に苦労しているようです。モナドはそれをエレガントに処理します。

私の質問は、継承よりも合成を優先するようになったのと同じように、継承よりもモナドを優先することも理にかなっていますか?

(ところで、これがComp Sciにあるのか、それともComp Sciにあるのかはわかりませんでしたが、これは実際的なモデリングの問題のように見えます。


1
リスとアヒルが同じように飛ぶわけではないので、それらのクラスに「フライアクション」を実装する必要があります。そして、チラシとアヒルを作成するメソッドが必要です。飛ぶ...多分共通のフライヤーインターフェイスで...ちょっと待って...何か見逃しましたか?
assylias

インターフェイスは機能を継承し、機能の継承は実際の動作を定義するため、インターフェイスはクラスの継承とは異なります。「継承に対する構成」でも、インターフェースを定義することは重要なメカニズムです(たとえば、多態性)。インターフェイスは、同じ多重継承の問題に遭遇しません。さらに、各チラシは、コンテナが使用する「getFlightSpeed()」または「getManuverability()」などの機能プロパティを(インターフェースとポリモーフィズムを介して)提供できます。
ロブ14

3
パラメトリック多型を使用することが常にサブタイプ多型の実行可能な代替であるかどうかを尋ねようとしていますか?
ChaosPandion 14

うん、セマンティクスを保持する構成可能な関数を追加するというしわです。パラメータ化されたコンテナタイプは長い間存在していましたが、それだけでは完全な答えであるとは思いません。それで、モナドパターンがより基本的な役割を果たすかどうか疑問に思っています。
ロブ14

6
モノイドとモナドの説明がわかりません。モノイドの重要な特性は、連想バイナリ操作を伴うことです(浮動小数点の加算、整数の乗算、または文字列の連結を考えてください)。モナドは、さまざまな(場合によっては依存する)計算を何らかの順序で順序付けることをサポートする抽象概念です。
ラッフルウィンド14

回答:


15

短い答えはnoです。モナドは継承階層(サブタイプポリモーフィズムとも呼ばれます)の代替ではありません。あなたはモナドが利用しているパラメトリック多相性を説明しているようですが、そうする唯一のものではありません。

私の知る限り、モナドは本質的に継承とは何の関係もありません。私は2つのことは多かれ少なかれ直交していると言うでしょう:それらは異なる問題に対処することを意図しています、そして:

  1. 少なくとも2つの意味で相乗的に使用できます。
    • Haskellの型クラスの多くをカバーするTypeclassopediaをチェックしください。それらの間には継承のような関係があることに気付くでしょう。たとえば、MonadはApplicativeから派生しており、ApplicativeはFunctorから派生しています。
    • Monadのインスタンスであるデータ型は、クラス階層に参加できます。Monadはインターフェイスに似ていることを忘れないでください。特定の型に実装すると、すべてではなくデータ型に関する情報が得られます。
  2. 一方を使用してもう一方を実行しようとすると、困難でandいものになります。

最後に、これはあなたの質問とは正反対ですが、モナドには信じられないほど強力な作曲方法があることを知りたいと思うかもしれません。詳細については、モナド変換子を参照してください。しかし、これはまだ研究の活発な領域です。なぜなら、私たち(そして私たちは私よりも100000x賢い人たち)がモナドを作成するための優れた方法を見つけていないためです。


さて、あなたの質問を選びましょう(申し訳ありませんが、私はこれが役立つように、そしてあなたが気分を悪くするつもりはありません)。

  1. モナドは、パラメータとしてコンテナタイプを取り、同じコンテナタイプを返す関数のセットです。

    いいえ、これMonadHaskellにあります:およびのm a実装を持つパラメーター化された型で、次の規則を満たします。return :: a -> m a(>>=) :: m a -> (a -> m b) -> m b

    return a >>= k  ==  k a
    m >>= return  ==  m
    m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h
    

    コンテナではないMonadのインスタンスがいくつかあり((->) b)、Monadのインスタンスではない(作成できない)コンテナがいくつかあります(Set型クラスの制約のため)。したがって、「コンテナ」の直観は貧弱なものです。他の例についてはこちらをご覧ください。

  2. したがって、OO言語では、モナドは次のような操作構成を許可します。

      Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
    

    いいえ、まったくありません。この例ではMonadは必要ありません。必要なのは、入力タイプと出力タイプが一致する関数だけです。これを記述するもう1つの方法は、単なる関数アプリケーションであることを強調しています。

    Flier<Duck> m = land(flyAround(takeOff(new Flier<Duck>(duck))));
    

    これは「流fluentなインターフェイス」または「メソッドチェーン」として知られるパターンだと思います(しかし、私にはわかりません)。

  3. モナドは、含まれるクラスではなく、これらの操作のセマンティクスを定義および制御することに注意してください。

    モナドでもあるデータ型は、モナドとは無関係の操作を持つことができます(ほとんど常にそうです!)。[]モナドとは関係のない3つの関数で構成されるHaskellの例を次に示します。[]「操作のセマンティクスを定義および制御する」と「含まれるクラス」はありませんが、モナドを作成するには不十分です。

    \predicate -> length . filter predicate . reverse
    
  4. クラス階層を使用して物事をモデル化する際に問題があることを正しく指摘しました。ただし、あなたの例は、モナドができることの証拠を示していません:

    • 継承が得意なもので良い仕事をしてください
    • 継承が苦手なもので良い仕事をしてください

3
ありがとうございました!私が処理するのがたくさん。私は気分が悪くありません-私は洞察を大いに感謝します。悪い考えを持ち歩くのはもっと悪くなるだろう。:)(stackexchangeのポイント全体に行きます!)
ロブ14

1
@RobYどういたしまして!ちなみに、それを聞いたことがないなら、モナド(およびHaskell!)を学ぶための素晴らしいソースであるLYAHをお勧めします。モナドに取り組む)。

ここにはたくさんあります。私はコメントを埋めたくありませんが、いくつかのコメント:#2 land(flyAround(takeOff(new Flier<Duck>(duck))))は機能しません(少なくともOOでは)。クラスで操作をチェーン化することにより、Flierの詳細は非表示のままになり、セマンティクスを保持できます。それはHaskellでモナドが結合であることを理由に似てい(a, M b)ない(M a, M b)ので、モナドは、「アクション」機能に、その状態を公開する必要がないこと。
ロブ14

#1は、残念ながら私はよしようとハスケルにマッピングするものは大きな問題があるため、Haskellではモナドの厳密な定義のそれぞれで独立しぼかす:関数合成を、上の組成物を含むコンストラクタ、あなたはJavaのような歩行者の言語で簡単に行うことができません。そのunitため、含まれる型の(大部分)コンストラクターにbindなり、(大部分)「アクション」関数をクラスに結び付ける暗黙のコンパイル時操作(つまり、早期バインディング)になります。ファーストクラスの関数、またはFunction <A、Monad <B >>クラスがある場合、bindメソッドはレイトバインディングを実行できますが、次にその悪用を取り上げます。;)
ロブ14

#3同意し、それがその美しさです。Flier<Thing>飛行のセマンティクスを制御する場合、飛行のセマンティクスを維持する多くのデータと操作を公開できますが、「モナド」固有のセマンティクスは、実際に連鎖可能にしてカプセル化することです。これらの懸念は、モナド内のクラスの懸念ではないかもしれません(私が使用しているものではそうではありません)。たとえばResource<String>、httpStatusプロパティがありますが、Stringはそうではありません。
ロブ14

1

私の質問は、継承よりも合成を優先するようになったのと同じように、継承よりもモナドを優先することも理にかなっていますか?

非OO言語では、はい。より伝統的なオブジェクト指向言語では、ノーと言うでしょう。

問題は、ほとんどの言語には型の特殊化がないためFlier<Squirrel>Flier<Bird>異なる実装を作成して実装することはできないことです。あなたは次のようなことをしなければなりませんstatic Flier Flier::Create(Squirrel)(そして各タイプのオーバーロード)。つまり、新しい動物を追加するたびにこのタイプを変更する必要があり、それを機能させるためにかなりの量のコードを複製する可能性があります。

ああ、そして少なからず言語(たとえばC#)public class Flier<T> : T {}が違法です。ビルドさえしません。すべてではありませんが、ほとんどのオブジェクト指向プログラマーはFlier<Bird>、まだaであることを期待していますBird


コメントをありがとう。私はもう少し考えがありますFlier<Bird>が、パラメータ化されたコンテナであるにもかかわらず、それは文字列ではなくリストBird(!?)List<String>であると誰も考えないでしょう。
ロブ14

@RobY- 単なるコンテナでFlierはありません。それを単なるコンテナと考えるなら、なぜ継承の使用を置き換えることができると思うでしょうか?
テラスティン14

私はそこであなたを失いました...私のポイントはモナドが強化されたコンテナであるということです。Animal / Bird / Penguinあらゆる種類のセマンティクスをもたらすため、通常は悪い例です。実用的な例は、私たちが使用しているREST風のモナドです。Resource<String>.from(uri).get() ResourceセマンティクスをString(または他のタイプ)の上に追加するため、明らかにaではありませんString
ロブ14

@RobY-ただし、継承にも関係しません。
テラスティン14

別の種類の封じ込めであることを除いて。StringをResourceに入れることも、ResourceStringクラスを抽象化して継承を使用することもできます。私の考えでは、クラスを連鎖コンテナに入れる方が、継承を使用してクラス階層に入れるよりも、動作を抽象化するのに適しています。したがって、「置き換え/回避」という意味では「関連性はありません」-はい。
ロブ14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.