あなたの毎日のJavaScriptのための機能プログラミング


Puedes leer la versión en español .


あなたはモナドについて聞いたことがありますどのように素晴らしいですか?たぶん、あなたはまだそれを得ることはありません.まあ私は、彼らが何であるかについてあなたに話すためにここにいません、私はあなたに彼らを売ることを試みません.
我々は何か楽しいことをしないでください、不要な複雑な方法でかなり些細な問題を解決することができます.
JSONファイルまたはプレーンJSオブジェクトに格納されている辞書があるとします.
{
    "accident": ["An unexpected, unfortunate mishap, failure or loss with the potential for harming human life, property or the environment.", "An event that happens suddenly or by chance without an apparent cause."], 
    "accumulator": ["A rechargeable device for storing electrical energy in the form of chemical energy, consisting of one or more separate secondary cells.\\n(Source: CED)"],
    "acid": ["A compound capable of transferring a hydrogen ion in solution.", "Being harsh or corrosive in tone.", "Having an acid, sharp or tangy taste.", "A powerful hallucinogenic drug manufactured from lysergic acid.", "Having a pH less than 7, or being sour, or having the strength to neutralize  alkalis, or turning a litmus paper red."],

     // ... moar words and meanings

    "Paris": ["The capital and largest city of France."]
  }
我々は、ユーザーがこの単語の1つを検索し、次に意味を示すフォームをしたい.これは簡単です、右?何が間違って行く可能性がありますか?
誰でも、HTMLを愛しているので、我々はそれから始めます.
<form id="search_form">
  <label for="search_input">Search a word</label>
  <input id="search_input" type="text">
  <button type="submit">Submit</button>
</form>

<div id="result"></div>
最初のバージョンでは、ユーザーの入力に基づいて1つの値を取得してください.
// main.js

// magically retrieve the data from a file or whatever
const entries = data();

function format(results) {
  return results.join('<br>'); // I regret nothing
}

window.search_form.addEventListener('submit', function(ev) {
  ev.preventDefault();
  let input = ev.target[0];
  window.result.innerHTML = format(entries[input.value]);
});
自然に最初に検索しようとするのは「酸」ですそして、ここで結果を見る.

A compound capable of transferring a hydrogen ion in solution.
Being harsh or corrosive in tone.
Having an acid, sharp or tangy taste.
A powerful hallucinogenic drug manufactured from lysergic acid.
Having a pH less than 7, or being sour, or having the strength to neutralize alkalis, or turning a litmus paper red.


今、私たちは「パリ」を探しています.どうやって手に入れたの?何もない.正確に何もない、我々は得た.

TypeError: results is undefined


また、私たちは予想外の提出ボタンを、いつか動作し、時にはありません.我々は本当に、本当に欲しいですか?安全性、我々のアプリケーションをクラッシュさせないオブジェクトは、我々は信頼性の高いオブジェクトが欲しい.
我々がすることは、我々が彼らが持つ価値について心配することなく実行の流れを説明させられる容器を実装することです.いいね.少しJavaScriptで私が意味するものをあなたに見せさせてください.試してください.
const is_even = num => num % 2 === 0;

const odd_arr = [1,3,4,5].filter(is_even).map(val => val.toString());
const empty_arr = [].filter(is_even).map(val => val.toString());

console.log({odd_arr, empty_arr});
空の配列で例外をスローしましたか?(知っていれば).それじゃない?すべての暖かく、あいまいさを感じていない配列のメソッドを使用すると何も動作していない場合でも正しいことを知っていますか?それが私たちの欲しいものです.
あなたは、我々がちょうど2、3を書くことができなかったと思っているかもしれませんif ステートメントとそれを行う?まあはい、しかし、どこで楽しいですか?私たちは皆、機能が冷静であることを知っています、そして、我々は機能的プログラミングのファンです.
それで、我々は隠れますif ステートメント(または多分カップル)、評価する値が未定義であるならば、我々は何が起ころうともどう振る舞うかについてわかっているラッパーを返します.
// maybe.js
// (I would like to apologize for the many `thing`s you'll see)

function Maybe(the_thing) {
  if(the_thing === null 
     || the_thing === undefined 
     || the_thing.is_nothing
  ) {
    return Nothing();
  }

  // I don't want nested Maybes
  if(the_thing.is_just) {
    return the_thing;
  }

  return Just(the_thing);
}
このラッパは本書では標準にならないMaybe 適切な関数型プログラミング言語で参照してください.我々は、利便性と副作用の名前で少しカンニングします.また、それらのメソッドは、あなたがRustで見つけるオプションタイプのメソッドの後で名前をつけられます(私はよりよくそれらの名前が好きです).ここで魔法が起こる場所です.
// maybe.js

// I lied, there will be a lot of cheating and `fun`s.

function Just(thing) {
  return {
    map: fun => Maybe(fun(thing)),
    and_then: fun => fun(thing),
    or_else: () => Maybe(thing),
    tap: fun => (fun(thing), Maybe(thing)),
    unwrap_or: () => thing,

    filter: predicate_fun => 
      predicate_fun(thing) 
        ? Maybe(thing) 
        : Nothing(),

    is_just: true,
    is_nothing: false,
    inspect: () => `Just(${thing})`,
  };
}

function Nothing() {
  return {
    map: Nothing,
    and_then: Nothing,
    or_else: fun => fun(),
    tap: Nothing,
    unwrap_or: arg => arg,

    filter: Nothing,

    is_just: false,
    is_nothing: true,
    inspect: () => `Nothing`,
  };
}
これらの方法の目的は何ですか?
  • map : 機能を適用するfun to the_thing そして、多分、党を行かせ続けるために、もう一度それを包んでください..私はオブジェクトの形を保つつもりです.
  • and_then : これは主にエスケープハッチです.機能の適用fun そして、運命を決めましょう.
  • or_else : それはelse あなたにmap and and_then . その他のパス.「そこに何がないか」
  • tap : これらは、ちょうど副作用のためにそこにあります.あなたがそれを見るならば、それはおそらくそれの範囲の外に何かに影響を及ぼしていますconsole.log ).
  • フィルタ:それは“述語関数は何か真実を返す場合”を介して行くことができます.
  • unwrap_or : これはあなたが得る方法ですthe_thing アウト.あなたはあなたがメソッドを連鎖しているときに、これをする必要がありますし、命令の世界に戻る準備が整いました.
  • 我々のフォームに戻って、行動でそれを見させてください.機能を作りますsearch これはユーザの問い合わせにマッチを取得しないかもしれません.それがそうするならば、我々は「安全な文脈」で実行される他の機能をチェインします
    // main.js
    
    const search = (data, input) => Maybe(data[input]);
    
    const search_word = word => search(entries, word)
      .map(format)
      .unwrap_or('word not found');
    
    そして今、私たちは新しい安全な(R)機能と私たちの不吉な古い方法を置き換えます.
     window.search_form.addEventListener('submit', function(ev) {
       ev.preventDefault();
       let input = ev.target[0];
    -  window.result.innerHTML = format(entries[input.value]);
    +  window.result.innerHTML = search_word(input.value);
     });
    
    今テストします.“事故”を探す

    An unexpected, unfortunate mishap, failure or loss with the potential for harming human life, property or the environment.
    An event that happens suddenly or by chance without an apparent cause.


    現在パリ.“パリ”を検索

    word not found


    それはボタンを凍結しませんでした.しかし、私はパリがそこにあるということを知っています.あなたがチェックするならば、それは「パリ」であるのを見ます彼らがそうしないために、我々はちょうどユーザー入力を資本にします.最初に、正確な入力を検索しようとします.もし失敗したなら、私たちは資本化の方法を試します.
    // main.js
    
    function create_search(data, exact) {
      return input => {
        const word = exact ? input : capitalize(input);
        return Maybe(data[word]);
      }
    }
    
    function capitalize(str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
    
    検索機能を変更します.
    - const search = (data, input) => Maybe(data[input]);
    + const search = create_search(entries, true);
    + const search_name = create_search(entries, false);
    -
    - const search_word = word => search(entries, word)
    + const search_word = word => search(word)
    +   .or_else(() => search_name(word))
        .map(format)
        .unwrap_or('word not found');
    
    非常に良い.これは我々がメインでこれまでに得たもの.閉じるこの動画はお気に入りから削除されています.
    // main.js
    
    const entries = data();
    
    function create_search(data, exact) {
      return input => {
        const word = exact ? input : capitalize(input);
        return Maybe(data[word]);
      }
    }
    
    function capitalize(str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
    
    function format(results) {
      return results.join('<br>');
    }
    
    const search = create_search(entries, true);
    const search_name = create_search(entries, false);
    
    const search_word = word => search(word)
      .or_else(() => search_name(word))
      .map(format)
      .unwrap_or('word not found');
    
    window.search_form.addEventListener('submit', function(ev) {
      ev.preventDefault();
      let input = ev.target[0];
      window.result.innerHTML = search_word(input.value);
    });
    
    しかし、我々は生活の中でそれはすべてですか?いいえ、もちろん、我々は愛が欲しいですが、JavaScriptは私たちは少し“提案単語”機能のために解決することはできませんので.私は「ACLU」を捜して、「アキュムレータを意味しましたか?」
    私たちは、この1つで助けを必要とします.依存関係を持ってきます.エントリのあいまいな検索を行うことができます.fuzzy-search . それで、我々は以下を加えます.
    // main.js
    
    import FuzzySearch from 'https://unpkg.com/[email protected]/src/FuzzySearch.js';
    
    const fzf = new FuzzySearch(
      Object.keys(entries),
      [],
      {caseSensitive: false, sort: true}
    );
    
    しかし、再び我々は安全な操作を実行することはできません我々は空の配列からマッチを取得しようとする瞬間は、全体のものが離れて落ちるだろう.それで、我々は何をしますか?我々は、機能の下でものを隠します.
    // main.js
    
    function suggest(word) {
      const matches = fzf.search(word);
      return Maybe(matches[0]);
    }
    
    あいまいな検索は、今すぐスーパー恐ろしい確認ダイアログでスローすることができます準備が整いました.あなたはそれを好きになる.
    // main.js
    
    function confirm_word(value) {
      if(value && confirm(`Did you mean ${value}`)) {
        return value;
      }
    }
    
    我々は、新しい機能を組み合わせるsearch .
    // main.js
    
    const suggest_word = value => () => suggest(value)
      .map(confirm_word)
      .map(search);
    
    機能を追加するsearch_word .
     const search_word = word => search(word)
       .or_else(() => search_name(word))
    +  .or_else(suggest_word(word))
       .map(format)
       .unwrap_or('word not found');
    
    あれはしかし、我々がアレルギーであると言うことができますif 言うまでもなく、それはちょうど戻る失礼ですundefined 関数から.私たちはもっとうまくできる.
     function confirm_word(value) {
    -  if(value && confirm(`Did you mean ${value}`)) {
    -    return value;
    -  }
    +  return confirm(`Did you mean ${value}`);
     }
    
     const suggest_word = value => () => suggest(value)
    -  .map(confirm_word)
    +  .filter(confirm_word)
       .map(search);
    
    何かが私を悩ます.私は“ACCU”を検索し、ダイアログがポップし、私は提案を確認し、結果が表示されます.しかし、「ACLU」それはまだ入力にあります、それは厄介です.正しい単語で入力を更新します.
    const update_input = val => window.search_form[0].value = val;
    
     const suggest_word = value => () => suggest(value)
       .filter(confirm_word)
    +  .tap(update_input)
       .map(search);
    
    アクションでそれを見たいですか?そこに行く.

    ボーナストラック


    Warning: The main point of the post (which is me showing that codepen example) was already accomplished. What follows is a strange experiment to see if I could make that Maybe function support asynchronous operations. If you are tired just skip everything and check out the last example code.


    今、あなたは言うかもしれません:これはかわいいです、そして、しかし、しかし、我々がHTTP要求をする、本当の世界では、データベースを質問して、非同期のもののあらゆる種類を作ります、これはまだその文脈で役に立つことができますか?
    聞こえます.現在の実装は、通常のブロックタスクをサポートしています.あなたのチェーンを破る必要がありますMaybes 瞬間Promise 上映.
    でも・・・リッスン.我々は約束を認めるJust . 我々はそれを行うことができますAsyncJust ? JustAsync ? ああ、ひどい.
    あなたがわからないならばPromise JavaScriptが将来のイベントを調整するのに使用するデータ型です.それを行うには、それはthen これはコールバック(catch 何故なら、物事がうまくいかない時にはthen それから、我々は我々の素晴らしいを保つことができますMaybe インターフェイス.
    どのように良いコールバックの束を次のですか?
    ここに行く.あなたに見せましょうFuture .
    // Don't judge me. 
    
    function Future(promise_thing) { 
      return {
        map: fun => Future(promise_thing.then(map_future(fun))),
        and_then: fun => Future(promise_thing.then(map_future(fun))),
        or_else: fun => Future(promise_thing.catch(fun)),
        tap: fun => Future(promise_thing.then(val => (fun(val), val))),
        unwrap_or: arg => promise_thing.catch(val => arg),
    
        filter: fun => Future(promise_thing.then(filter_future(fun))), 
    
        is_just: false,
        is_nothing: false,
        is_future: true,
        inspect: () => `<Promise>`
      };
    }
    
    我々が雑音を取り除くならば、我々はよりよくわかることができました.
    // In it's very core is callbacks all the way.
    
    {
      map: fun => promise.then(fun),
      and_then: fun => promise.then(fun),
      or_else: fun => promise.catch(fun),
      tap: fun => promise.then(val => (fun(val), val))),
      unwrap_or: arg => promise.catch(val => arg),
    
      filter: fun => promise.then(fun), 
    }
    
  • map/and_then : これらは、あなたがAから出ることができないので、同じことをしますPromise .
  • or_else : あなたのコールバックをcatch を模倣する方法else 動作.
  • tap : 用途then 値を覗く.これが副作用のため、我々は再び値を返します.
  • unwrap_or : あなたが使うことができるように、それは約束を返しますawait . すべてがうまく行くならばPromise を返します.await , それ以外の場合、指定した引数が返されます.いずれにせよ、約束はエラーをスローしませんFuture 添付catch メソッド.
  • filter : これらは特別な種類のmap それでfilter_future が存在する.
  • ほとんどすべてのこれらのメソッドはFuture '原因promise.then 返り値Promise .
  • 何がFuture 不思議なのは、何が起こるのかmap . 記憶するmap_future ?
    function map_future(fun) { // `fun` is the user's callback
      return val => {
        /* Evaluate the original value */
        let promise_content = val;
    
        // It needs to decide if the value of the Promise
        // can be trusted
        if(Maybe(promise_content).is_nothing) {
          Promise.reject();
          return;
        }
    
        // If it is a Just then unwrap it.
        if(promise_content.is_just) {
          promise_content = val.unwrap_or();
        }
    
        /* Evaluate the return value of the user's callback */
    
        // Use Maybe because I have trust issues.
        // For the javascript world is undefined and full of errors.
        const result = Maybe(fun(promise_content));
    
        if(result.is_just) {
          // If it gets here it's all good.
          return result.unwrap_or();
        }
    
        // at this point i should check if result is a Future
        // if that happens you are using them in a wrong way
        // so for now I don't do it 
    
        // There is something seriously wrong.
        return Promise.reject();
      }
    }
    
    現在filter_future .
    function filter_future(predicate_fun) { // the user's function
      return val => {
        const result = predicate_fun(val);
    
        // Did you just returned a `Promise`?
        if(result.then) {
          // You did! That's why you can't have nice things.
    
          // peek inside the user's promise.
          const return_result = the_real_result => the_real_result 
            ? val
            : Promise.reject();
    
          // keep the promise chain alive.
          return result.then(return_result);
        }
    
        return result ? val : Promise.reject();
      }
    }
    
    私がしたい1つの最後のものがあり、それは、正規値をAに変換するヘルパー関数を作成しますFuture .
    Future.from_val = function(val) {
      return Future(Promise.resolve(val));
    }
    
    すべてをサポートするために今しなければならないFuture インMaybe これです.
     function Maybe(the_thing) {
       if(the_thing === null 
         || the_thing === undefined 
         || the_thing.is_nothing
       ) {
         return Nothing();
       }
    -
    -  if(the_thing.is_just) {
    +  if(the_thing.is_future || the_thing.is_just) {
         return the_thing;
        }
    
        return Just(the_thing);
     }
    
    しかし、100万ドルの質問は残っています.それは実際に動作しますか?
    私はCLI version これです.そして、ここではいくつかの微調整と同じcodepenの例ですFuture 関連する機能は、ダイアログの確認ダイアログですthis one ) そして、イベントリスナーは現在、await 結果.

    ボーナスボーナス


    それは、我々がカンニングをするときのように見えます.我々がカンニングしなかったならば、それは似ていますthis .

    その他の資源

  • The Marvellously Mysterious JavaScript Maybe Monad
  • Option/Maybe, Either, and Future Monads in JavaScript, Python, Ruby, Swift, and Scala


  • お読みありがとうございます.あなたがこの記事を役に立つならば、私の努力を支持したいです.buy me a coffee ☕ .