あなたはすでにモナドを知っています

10385 ワード

モナドの背後にある抽象的な理論を説明する代わりに、すでに理解している Java コードをいくつか紹介します.そして、この実用的なアプローチを使用すると、モナドをすでに数回使用している可能性が高いことに気付くでしょう.

Oh and if you prefer to listen to me instead, this post is based on a lightning talk I gave, where I (try to) explain them in 7 minutes.

モナドとは?



モナドは、次の 2 つのメソッドを実装する単なるクラスです.

  • of (説明では通常 unit または return と呼ばれます) および

  • flatMap (通称 bind )
  • of は単にいくつかの値を取り、そのラッパー (モナド) を返す必要があります. flatMap では、関数をすべての値に適用し、それらの関数によって返されたモナドを取得して、それらを再度 1 つに結合 (別名「フラット化」) することで、これらの値にアクセスできます.

    それを使用すると、可能な限り単純なモナドは次のようになります.

    public class Monad<T> {
        private T value;
    
        private Monad(T value) {
            this.value = value;
        }
    
        public static <T> Monad<T> of(T value) {
            return new Monad<>(value);
        }
    
        public<R> Monad<R> flatMap(Function<T, Monad<R>> f){
            return f.apply(value);
        }
    }
    


    それだけですか?



    ほとんどの場合、しかし明らかに、メソッドに特定の方法で名前を付けてモナドを持つことはできません.これら 2 つの方法がどのように連携する必要があるかについては、規則があります.

    幸いなことに、これらのメソッドを上記のように (そして一般的に理解されているように) 実装すれば、既にそれらすべてを満たすことができます.しかし、とにかくここにあります:
  • of メソッドは flatMap
  • の left-identity です
  • of メソッドは、flatMap および
  • の正しい同一性です.
  • flatMap メソッドは関連付けられています.

  • または、コードで:

    // using
    Monad<V> m;
    V x;
    Function<V, Monad<V>> f;
    Function<V, Monad<V>> g;
    
    // rule 1: of is left-identity of flatMap
    of(x).flatMap(f)
        .equals(f.apply(x))
    
    // rule 2
    m.flatMap(Monad::of)
        .equals(m)
    
    // rule 3
    m.flatMap(f).flatMap(h)
        .equals(m.flatMap(v -> g.apply(v).flatmap(h)))
    


    なぜ気にする必要があるのですか?



    モナドとは何か、モナドを作成する方法はわかりましたが、なぜモナドに名前があるのでしょうか?

    関数型言語を使用している場合、関数型プログラミングを行う際にツールや問題が発生すると、自然にいくつかのモナドが作成される可能性が高いため、これは問題にはなりません¹.それがモナドの起源です.

    そうは言っても、それらはこれらの言語の外でも非常に有用であり、実際、多くの新しい API はモナドを特徴としています.

    ドメインに (エラー) 状態を含める



    最高の API は、間違いを犯さないようにします.これを行う方法の 1 つは、戻り値にエラー状態を明示的に含めることです.複数の関数呼び出しが連続してエラーを返す可能性がある場合、これには、結果のコードがよりきれいになるという追加のボーナスもあります.

    Optional を使用した例を見てみましょう.

    public Optional<Integer> doSomething(Stream<Integer> stream) {
        return stream
            .findFirst()
            .flatMap(this::integerHalf)
            .flatMap(v -> Optional.of(v*5));
    }
    
    public Optional<Integer> integerHalf(Integer i) {
        if (i%2 == 0) {
            return Optional.of(i/2);
        } else {
            return Optional.empty();
        }
    }
    


    この例では、いつでも「エラー」を返すオプションがあっただけでなく、後ですぐにチェックする必要もありません.代わりに、これは後で行うことができます.

    モナドは、非エラー状態を処理するためにも使用できます.これは、コレクションまたは非同期プログラミング ( CompletableFuture を参照) でよく使用され、コールバック地獄も防ぎます.

    より一般的な関数の使用を許可する



    上記の関数 integerHalf を例に取ります.これは、プレーンな整数だけでなく、 FlatMap メソッドを使用して Optional や Stream などのモナドでも使用できます.

    モナドのドメインを離れずに基礎となる情報に簡単にアクセスできるため、多くのエラーを防ぐ驚くべき API を作成できます.

    いくつかのモナドを知っていますか?



    Java で最も一般的な 3 つのモナドについては既に説明しました.
  • Optional
  • Stream
  • CompletableFuture

  • しかし、あなたはすでにいくつか遭遇したと確信しています.別の良い例を知っている場合は、コメントを残してください.

    その他のリンク


  • Maybe the best explanation of the theory behind monads (and functors and applicatives) — with pictures!
  • ¹ A great explanation of why monads naturally arise when programming functionally


  • P.s.: タイトル画像は Nicole De Khors による写真で、モナドは単純にいくつかのものをラップするため、ボックスとして説明されることが多いため、選択されました.