5 分で説明: プレーンな JavaScript のモナド

Dec 22 2022
威圧的な表紙の写真にもかかわらず、私はできるだけ難しい数学を使わずにモナドを説明しようとします。圏論、Kleisli、Curry についての言及はありません。
Unsplashのダン・クリスティアン・パドゥレシュによる写真

威圧的な表紙の写真にもかかわらず、私はできるだけ難しい数学を使わずにモナドを説明しようとします。圏論、Kleisli、Curry についての言及はありません。がありますがNaN。

偉大なダグラス・クロックフォードの言葉:

モナドは良くて便利であるだけでなく、呪われており、モナドの呪いは、ひとたびひらめきを得て、ひとたび理解すると — 「ああ、そういうことだ」 — 他の人にそれを説明する能力を失うことです。

ええと、私には理論があります — これは、モナドの背後にある数学的理論とそれらがどのように実装されるべきかについての優れた説明を提供する、この主題に関するすべての優れた記事の中で (絶妙な例とともに!)、1 つ間違っているからです:

私は実際にそれらを何のために必要としますか?

調べようとする前に、一言。少し前に、モナドの背後にある数学と圏論におけるモナドの位置を説明することにしっかりと基づいていないものに基づいて、モナドを理解しようとするべきではないということを読みました。興味のある方へ:このテーマを扱った優れた記事がいくつかあります。たとえば、 this one 、 this one、または1992年からプログラミングにモナドを導入したものです。ここでそれらを繰り返したくはないので、今すぐ読んで (または少なくともスキャンして)、後でここに戻ってきてください (ただし、次に何が起こるかを完全に理解する必要はないと思います)。

よし、これで邪魔にならない - モナドとは? 最も簡単な言い方をすると、ある値のラッパーです。関数型プログラミングの観点から言えば、モナドはfunctor 、関数を適用できる構造体です。これは、値と、値を通過させたい処理との間の追加の抽象化レイヤーです。しかし、それでは何もわかりません。

あなたが想像するかもしれない最も単純なモナドを見てみましょう:Identityモナドの実装です:

const Identity = (value) => ({
  value: () => value,
  map: (fn) => Identity(fn(value)),
});

最も単純な例を見てみましょう。

const addTax = (value) => value * 1.1;

const cartValue = Identity(100);

const cartValueWithTax = cartValue
  .map(addTax) // At this point this is Identity(110)
  .value(); // Equal to 110 as a number

https://makeameme.org/meme/please-bear-with-tmby8v 提供

Identityモナドには、多くの関数型プログラミング ライブラリの関数と同様に、非常identityに透過的なロジックがあります。これは、プレースホルダー関数と同じプレースホルダー モナドです。値への適用は上記のシナリオではうまく機能しますが、カートの値が に等しい場合に何が起こるかを考えてみましょう。JavaScript のすばらしい仕組みのおかげで、.identityaddTaxundefinedNaN

通常、これを回避するための最も直感的な方法は、addTax関数に値のチェックを追加することです。これは間違ったアプローチです。関数の目的が不明瞭になり、関数の純粋性が低下します。このaddTax関数は、1 つのことだけを実行する必要がありaddますTax。

OK、賢明な人 — あなたは言うかもしれません — どのようにundefined値を考慮に入れるべきですか? そうですね、それが私たちがモナドを持っている理由です。関数を純粋に保ちたい。また、関数に渡す値に基づいて、ロジックが異なる動作をすることも必要です。その動作を定義するのに最適な場所はfunctorです!

おそらく最も人気のあるモナドを見てみましょう: Maybe. これは基本的な実装かもしれません (ofわかりやすくするために、モナドで遭遇する可能性のある通常のメソッドを残しています):

const Maybe = (value) => ({
  value: () => value,
  map: (fn) => value === undefined || value === null 
    ? Maybe(null) 
    : Maybe(fn(value)),
});

const primitiveCart = 100;
const undefinedCart = undefined;
const nullishCart = null;

const maybeCart = Maybe(100);
const maybeUndefinedCart = Maybe(undefined);
const maybeNullishCart = Maybe(null);


addTax(primitiveCart); // boring, 110
addTax(undefinedCart); // NaN
addTax(nullishCart); // 0 - thanks, JavaScript!

maybeCart.map(addTax).value(); // 110
maybeUndefinedCart.map(addTax).value(); // null
maybeNullishCart.map(addTax).value(); // null

もちろん、これはモナドを使用することでもたらされる膨大な可能性のほんの一部にすぎません。ホテルに入るすべてのゲストに挨拶したいとしましょう。

const greet = (name) => `Hello, ${name}!`;

もう 1 つ基本的な例を見てみましょう —Optionalモナドです:

const Optional = (value) => ({
  value: () => value,
  orElse: (alternativeValue) => value 
    ? Optional(value) 
    : Optional(alternativeValue),
  map: (fn) => Optional(fn(value)),
});

const firstGuest = "John";
const anonymousGuest = undefined;

greet(firstGuest); // "Hello, John!"
greet(anonymousGuest); // "Hello, undefined" - this is a bit embarrasing

const OptionalWithStrangerFallback = (name) => Optional(name)
  .orElse("stranger");

const firstGuestOptional = OptionalWithStrangerFallback(firstGuest);
const anonymousGuestOptional = OptionalWithStrangerFallback(anonymousGuest);

// I really hope you know where this is going at this point

firstGuestOptional.map(greet).value(); "Hello, John!"
anonymousGuestOptional.map(greet).value(); "Hello, stranger!"

これがモナドで問題を解決することを本当に願っています。そうでない場合は、コメントでお知らせください。また、JavaScript、関数型プログラミング、コンピューター ビジョン、アーキテクチャ、ジオメトリ、ミーム、技術的リーダーシップ、または上記の組み合わせに関する記事をさらに読みたい場合は、こちらまたはLinkedInでフォローしてください。