23

そこで、JavaScriptのモナドが役立つ実際の事例を理解したいと思います。

JavaScriptのモナドに関する記事をたくさん読んで、jQueryがその使用例の1つであることを理解しています。しかし、「連鎖」パターン以外に、フロントエンドエンジニアリングでモナドを使用して効果的に解決できる他の問題は何でしょうか。

参照:

http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/

http://igstan.ro/posts/2011-05-02-understanding-monads-with-javascript.html

4

4 に答える 4

14

これがモナドに貢献する私の試みです-あなたがおそらく他のどこにも見つけたことがない初心者です。

モナドは、関数型プログラミングにおける非常に構成可能な単位(プログラミングの構成要素の一種)です。

(IMO、コンテキストと合理化なしで「モナド法」を導入することは、概念を理解するための役に立たない分類と危険にすぎません。心配しないでください。私はこの記事の後半で仕事をします。)

ほとんどの場合、オブジェクト、関数、リストなど、さまざまな種類のプログラミングの構成要素があります。

プログラミングのブロックの種類を持っていることは自然の法則であり、実用的な目的のための柔軟なプログラミングには避けられないようですが、実際には、ブロックの種類を持っていることはプログラミング環境汚染の主な原因の1つです。

さまざまなブロックを使用してブロックを構築することは、複雑な作業です。プログラマーは、あらゆる状況でさまざまなブロックの中から非常に賢明にブロックを選択する必要があり、長いスパンでは失敗します。

したがって、状況に応じてブロックの種類を選択することはお勧めしません。代わりに、普遍的に標準化されている特定の事前に選択されたブロックを常に使用することをお勧めします。

実際、この知恵は最近のPCの世界では一般的です。

USB(Universal Serial Busの略)は、パーソナルコンピュータとその周辺機器間の接続、通信、および電源供給のためのケーブル、コネクタ、およびプロトコルを定義するために開発された業界標準です。

適切に設計された、普遍的に標準化されたビルディングブロックを取得すると、多くの問題が解消されます。

  1. オブジェクトは(以前は)1つです。
  2. 機能は1つです。
  3. モナドがその1つです。
  4. 仕様
  5. 実装
  6. 検証

1.OOP

オブジェクト指向プログラミング(OOP)は、「オブジェクト」の概念に基づくプログラミングパラダイムであり、フィールドの形式でデータを含めることができます。これは、属性と呼ばれることもあります。プロシージャの形式のコード。多くの場合、メソッドとして知られています。オブジェクトの機能は、オブジェクトのプロシージャが、関連付けられているオブジェクトのデータフィールドにアクセスし、多くの場合変更できることです(オブジェクトには「this」または「self」の概念があります)。OOPでは、コンピュータープログラムは、相互に作用するオブジェクトから作成することによって設計されます。OOP言語にはかなりの多様性がありますが、最も人気のあるものはクラスベースです。つまり、オブジェクトはクラスのインスタンスであり、通常はクラスのタイプも決定します。

普遍的に標準化されたビルディングブロックとしてオブジェクトを選択し、プログラマーはメンバーの値と関数を含む基本クラスを準備し、ブロックのバリエーションを取得するために、継承が使用されます。

OOPのアイデアは、実際の物理オブジェクトを使用して説明されることが多く、パラダイム自体は数学的抽象化に弱いです。

たとえば、関数(またはメソッド)はオブジェクトに従属し、関数はファーストクラスのオブジェクトである必要はありません。これは当然のことです。パラダイムは元々、適切に設計された普遍的に標準化されたビルディングブロックとしてオブジェクトを選択したからです。

標準化されたビルディングブロックとして機能がオブジェクトに従属するエンティティであり、両方の役割が厳密に異なるという観点は、物理的な世界での工学的な意味から来ています。プログラミングが実際に存在する数学的抽象化ではありません。

OOPの根本的な問題は、オブジェクトが適切に設計された、普遍的に標準化されたビルディングブロックではないことが判明したことです。関数型プログラミングまたはモナドは、強力な数学的背景を持つより良い代替手段です。

2.関数型プログラミング

関数型プログラミングとは、関数を構成することです。

それは簡単だと言っていますが、これはプログラミングの歴史のかなりの成果です。

プログラミングの長い歴史を学ぶのではなく、個人的な歴史を共有したいと思います。

私はバージョン1.0からC#​​(OOP)プログラマーでしたが、全体的には満足していましたが、何か非常に悪いと感じましたが、それが何であるかはわかりませんでした。

私は後にJavaScriptプログラマーになり、初期の頃は次のように書いていました。

function add1(a) {
    return a + 1;
}

ある日、「JavaScriptでは関数も値だ」というWeb記事を読みました。

この事実は私にとって非常に驚くべきことであり、私のプログラミングスキルへの突破口です。

それまでは、値が値であり、関数が関数であることは明らかです。どちらも、異なる領域ではまったく異なるエンティティです。

もちろん、C#1.0はすでにデリゲートを実装しており、イベントの内部メカニズムに関するものであることを少し理解しています。結局のところ、C#は主要なOOP言語であり、少なくともバージョン1.0では、関数型プログラミングにとっては非常に醜いものでした。

JavaScriptでは、関数も値です。JavaScriptの関数はファーストクラスのオブジェクトなので、他の関数を引数として受け取るか、結果として返すことができる関数を定義できます。

だから、今、私はこれを書きます:

const add1 = x => x + 1;
const add2 = x => x + 2;
[1, 2, 3].map(add1); //[2,3,4]
[1, 2, 3].map(add2); //[3,4,5]

また

const plus = (x) => (y => x + y);
plus(1)(5); //6

実際、これは私がC#プログラミングでひどく必要としていたものであり、私が感じた非常に間違ったものでした。

これは関数合成と呼ばれ、プログラミングの制約を解放するための真の秘密です。

つまり、JavaScriptの関数は一流のオブジェクトであり、よく設計された普遍的に標準化されたビルディングブロックのようです。これからは、これを「高度に構成可能なユニット」と呼びましょう。

関数はBEFORE => AFTERです。

基本的な考え方は、関数を作成することです。

写像の合成に焦点を当てるときは、のさまざまな合成だけを気にしますBEFORE => AFTER

機能合成に焦点を当てるときは、コードの上から下に流れるフローチャートや、場合によってはループするフローチャートを忘れてください。

フローチャートコーディングは命令型プログラミングと呼ばれ、一般的に言えば、バグが多く複雑すぎます。OOPはこのスタイルになる傾向があります。

一方、関数型プログラミングは自動的にプログラミングスタイルをDeclarative_programmingに導き、一般的に言えば、バグがなく、デバッグも簡単ではありません。

フローの追跡と制御は困難ですが、構成の追跡と制御はかなり簡単です。プログラマーはフローを制御するのではなく、関数を作成する必要があります。

3.モナド

ちなみに、ここではHaskellコードは使用しません。

ほとんどの人にとって、モナドのことを理解する上での大きな障害は

  1. モナドを学ぶためには、初心者はHaskellのコードと用語に慣れている必要があります。
  2. Haskellのコードと用語に慣れるためには、初心者はモナドを学ぶ必要があります。

これは「鶏が先か卵が先か」です。問題。必ず避けてください。

そうは言っても、この記事の冒頭で述べたように、モナドの知識を共有するために、最初に「モナド法」を引用することもばかげているようです。

人々は彼らがすでに知っていることに基づいてのみ学ぶことができます。

それでは、JavaScriptコードに戻りましょう。

関数は非常に構成可能な単位のように見えますが、これはどうですか?

console.log("Hello world!");

これは最も単純なJSコードの1つであり、確かに関数です。

ChromeBrowserでF12キーを押し、開発者コンソールにコードをコピーして貼り付けます。

Hello world!
undefined

OK、コードは「Helloworld!」を表示するタスクを実行しました。ただし、コンソールでは、console.log関数の戻り値はですundefined

関数を作成するために、状況は不快です。不快な機能。

一方、快適な機能があります。次のコードを調べてみましょう。

const add1 = x => x + 1;
[1, 2, 3].map(add1); //[2,3,4]

JavaScriptの配列は、関数型プログラミングの世界ではかなりうまく動作します。

[1, 2, 3].map(add1)   //[2,3,4]

示す:
Array Function=> Array

関数の入力と出力は同じタイプです:Array

数学的構造は全体を通して同一ですBEFORE => AFTER

一貫性とアイデンティティの性質は美しいです。

USBインターフェースとの興味深い類似性は、当然のことながらアイデアを導きます:
Array Function=> Array Function=> Array Function=> Array..。

JavaScriptコードの場合:

[1, 2, 3]
  .map(add1) //[2,3,4]
  .map(add1) //[3,4,5]
  .map(add1);//[4,5,6]

コードは、配列レルムに入ると、出口は常に配列レルムになることを示唆しているため、ある意味で出口はありません。

配列レルムは自己完結型の世界であるため、関数型プログラミングで代数のようなことを行うことができます。

私たちが持っているとき:

Array.map(F).map(F).map(F)...

JavaScript配列固有の構文を考えると、たとえば、 Babel.map(F)などのトランスパイラーを利用することで、より簡潔な構文に置き換えることができます。

したがって、に置き換え.map(F)ます*F

Array*F*F*F...

これは代数のように見えます。

非常に構成可能なユニットを取得することで、プログラマーは代数のようなコードを書くことができます。これは重要であり、非常に真剣に研究する価値があることを意味します。

代数では、

a
= 0+a
= 0+0+a
= 0+0+0+a

また

a
= 1*a
= 1*1*a
= 1*1*1*a

0+(加算)演算では、

a + 0 = a  //right identity
0 + a = a  //left identity

1*(乗算)演算では、

a ∗ 1 = a  //right identity
1 ∗ a = a  //left identity

単位元と呼ばれます。

代数では、

1 + 2 + 3 = 1 + 2 + 3
(1+2) + 3 = 1 + (2+3)
    3 + 3 = 1 + 5
        6 = 6

結合法則と呼ばれます

number + number = number

number * number = number

string + string = string

"Hello" + " " + "world" + "!" 
= "Hello world" + "!" 
= "Hello "+ "world!"

は連想的であり、単位元は""です。

では、関数型プログラミングの単位元とは何ですか?

何かのようなもの:

identityF * f = f = f * identityF

関数型プログラミングの結合法則はどのようなものですか?

const add1 = x => x + 1;
const add2 = x => x + 2;
const add3 = x => x + 2;

何かのようなもの:

add1 * add2 * add3
= (add1 * add2) * add3
= add1 * (add2 * add3)

また

  (add1)(add2)(add3) = (add1)(add2)(add3)
 ((add1)(add2))(add3) = (add1)((add2)(add3))
         (add3)(add3) = (add1)(add5)
              (add6) = (add6)

関数型プログラミングは、すべて関数の合成に関するものです。

関数型プログラミングに必要なのは

function * function = function

もちろん、JavaScript(または他の言語)では、すべての言語の構文の制限のため、上記の正確な形式を書くことはできません。

実際、「代数JavaScript仕様」(JavaScriptの一般的な代数的構造の相互運用性に関する仕様)を持つことができます。

代数JavaScript仕様

では、JavaScript配列はいわゆるモナドですか?

いいえ、でも閉じます。JavaScript配列はFunctorとして分類できます。

モナドはファンクターの特別な形式であり、いくつかの追加の性質があります(より多くのルールが適用されます)。

ファンクターは今でも非常に構成可能なユニットの1つです。

だから私たちはモナドが何であるかに近づいています。さらに進んでみましょう。

これで、JavaScript配列は、少なくともある程度は代数を実行できる、非常に構成可能なユニットの1つであることがわかりました。

では、配列以外のJavaScript値についてはどうでしょうか。関数はどうですか?

代数JavaScript仕様を研究して従うと、FunctorやMonadを含むさまざまな構成可能なユニットの実装を簡単に試みることができますが、ポイントは何ですか?

結局のところ、それらは数学構造の分類表にすぎず、仕様に従うことは盲目的に意味がありません。

4.仕様

重要なのは、レルムが自己完結型である、非常に構成可能なユニットを取得することです。これが満たすべき唯一の仕様です。

そこで、問題の確立は次のとおりです。
自己完結型のレルムを生成するMath構造を実装し、それがどのように行われるかを確認します。

何でも問題ありません。最初から始めますが、参照するのに適したモデルはすでにあります。

JavaScript配列

Array.map(F).map(F).map(F)...

M配列レルムの代わりに、次のように元のレルム を作成しましょう。

M.map(F).map(F).map(F)...

Array.map簡潔な構文ではなく、Mそれ自体が関数 だと思います。

M(F)(F)(F)...

まあ、普遍的に標準化されている特定の事前に選択されたブロックを常に使用することは良い規律です。それが開始のアイデアなので、おそらく、次のようにFする必要がありますM

M(M)(M)(M)...

うーん、これはどういう意味ですか?

だから、これが私のクレイジーなアイデアです。

関数型プログラミングでは、すべての関数もファーストクラスのオブジェクトであり、それが画期的なことです。したがって、値/オブジェクト/関数をとして解釈するMと、別のブレークスルーが発生します。

これは、「すべての値は配列です!」と言うようなクレイジーです。

正確には、JavaScriptの領域にある場合はクレイジーですが、Arrayの自己完結型の領域にある場合は正当です。

したがって、元のMレルムがすべての裸の値/オブジェクト/関数を次のように扱うように設計しますM

たとえば、Mレルムでは、naked value:5が見つかった場合、。として解釈されM(5)ます。

言い換えると、レルム内にある限り、プログラマーは暗黙的にとして解釈されるため、M記述する必要はありません。M(5)5M(5)

したがって、M領域では:

5
= M(5)
= M(M(5))
= M(M(M(5)))
...

その結果、私Mはある程度透明でありM、領域内の単位元である必要があることを発見しました。

私が強調してきたように、関数型プログラミングはすべて関数を構成することです。

関数の合成は、関数型プログラミングに関連しています。

M関数を構成するために柔軟に作成する必要があります 。

const add1 = x => x + 1;
M(10)(add1);             //11
M(10)(add1)(add1);       //12
M(10)(add1)(add1)(add1); //13
const add2 = M(add1)(add1);
M(10)(add2);             //12
const add3 = M(add2)(add1);   
M(10)(add3);             //13

また、高階関数の構成:

const plus = (x) => (y => x + y);
M(plus(1)(5));    //6
M(5)(M(1)(plus)); //6
const plus1 = M(1)(plus);
M(5)(plus1)(;     //6

5.実装

これがの実装ですM

const compose = (f, g) => (x => g(f(x)));
const isMonad = (m) => !(typeof m.val === "undefined");

const M = (m = []) => {
  const f = m1 => {
    try { //check type error
      return M(M(m1).val(m));
    } catch (e) {
      return M(compose(m, M(m1).val)); // f-f compose
    };
  };
  f.val = m;
  return isMonad(m)
    ? m
    : f;
};
M.val = m => m;

ロギング機能:

const log = (m) => (typeof m !== 'function')
  ? (() => {
    console.log(m);
    return m;
  })()
  : err();

テストコード:

const err = () => {
  throw new TypeError();
};

const log = (m) => (typeof m !== 'function')
  ? (() => {
    console.log(m);
    return m;
  })()
  : err();

const loglog = M(log)(log);
M("test")(loglog);

M("------")(log);
M([1])(log);
M(M(M(5)))(log)
M(99)(M)(log)

M("------")(log);
M([1, 2, 3])(([a, b, c]) => [a + 1, b + 1, c + 1])(log)

M("------")(log);

const add1 = a => (typeof a == 'number')
  ? a + 1
  : err();

M(10)(add1)(log); //11
M(10)(add1)(add1)(log); //12
M(10)(add1)(add1)(add1)(log); //13
const add2 = M(add1)(add1);
M(10)(add2)(log); //12
const add3 = M(add2)(add1);
M(10)(add3)(log); //13

M("------")(log);
const plus = (x) => (y => x + y);
M(plus(1)(5))(log); //6
M(5)(M(1)(plus))(log); //6
const plus1 = M(1)(plus);
M(5)(plus1)(log); //6

M("------")(log);
const map = (f) => (array => array.map(f));
const map1 = M(add1)(map);
M([1, 2, 3])(log)(map1)(log);

//===

M("left identity   M(a)(f) = f(a)")(log);
M(7)(add1)(log) //8

M("right identity  M = M(M)")(log);
console.log(M) //{ [Function: M] val: [Function] }
console.log(M(M)) //{ [Function: M] val: [Function] }

M("identity")(log);
M(9)(M(x => x))(log); //9
M(9)(x => x)(log); //9

M("homomorphism")(log);
M(100)(M(add1))(log); //101
M(add1(100))(log); //101

M("interchange")(log);
M(3)(add1)(log); //4
M(add1)(f => f(3))(log); //4

M("associativity")(log);
M(10)(add1)(add1)(log); //12
M(10)(M(add1)(add1))(log); //12

出力:

test
test
------
[ 1 ]
5
99
------
[ 2, 3, 4 ]
------
11
12
13
12
13
------
6
6
6
------
[ 1, 2, 3 ]
[ 2, 3, 4 ]
left identity   M(a)(f) = f(a)
8
right identity  M = M(M)
{ [Function: M] val: [Function] }
{ [Function: M] val: [Function] }
identity
9
9
homomorphism
101
101
interchange
4
4
associativity
12
12

わかりました、働きました。

M関数型プログラミングで非常に構成可能なユニットです。

6.検証

それで、これはいわゆるモナドですか?

はい。

https://github.com/fantasyland/fantasy-land#monad

モナド

Monad仕様を実装する値は、ApplicativeおよびChain仕様も実装する必要があります。1. (左のアイデンティティ)と同等ですM.of(a).chain(f)2. f(a)(右のアイデンティティ)と同等ですm.chain(M.of)m

左の単位元M(a)(f)= f(a)
M(7)(add1) //8
M(add1(7)) //8
正しい単位元M=M(M)
console.log(M) //{ [Function: M] val: [Function] }
console.log(M(M)) //{ [Function: M] val: [Function] }

応募可能

Applicative仕様を実装する値は、 Apply仕様も実装する必要があります。1. (同一性)と同等v.ap(A.of(x => x))2. (準同型)と同等3. (交換)と同等vA.of(x).ap(A.of(f))A.of(f(x))A.of(y).ap(u)u.ap(A.of(f => f(y)))

身元
M(9)(M(x => x)) //9
準同型
M(100)(M(add1)) //101
M(add1(100)) //101
交換
M(3)(add1)    //4
M(add1)(f => f(3))  //4

チェーン仕様を実装する値は、適用仕様も実装する必要があります。1. (結合法則)m.chain(f).chain(g)と同等ですm.chain(x => f(x).chain(g))

連想性
M(10)(add1)(add1) //12
M(10)(M(add1)(add1)) //12
于 2018-05-23T00:20:26.510 に答える
9

さて、最初の記事は素晴らしく、非常に詳細だと思います。JQueryとそのモナドの性質によって解決される多くの問題について説明します。

  1. JQueryはDOM要素をラップし、より豊富なインターフェースを提供します。解決された問題は数多くあります:より豊富なイベント(「mouseenter」、「mouseleave」、「hashchnged」など)。イベントバインディングは、オーバーライドする代わりにハンドラーを追加します。CSS処理のインターフェースは、JQueryによって公開される他のインターフェースと同様です。

これは、JQueryが私たちが知っていることを単にラップし、HTMLを再発明しようとしないため、多くの開発者にとって非常に直感的である理由でもあります。

言うまでもなく、nullを参照するときに多くのエラーを節約できます。idの要素がない場合guy、実行して$("#guy").text("I am not here")もJQueryでエラーは発生しません。

  1. JQueryはDOM要素を簡単にラップし、生のJSとJQueryのインターフェース間を行き来できるようにします。これにより、開発者はコード全体を一度に書き直すのではなく、自分のペースでJQueryを学ぶことができます。

  2. JQueryが引数を使用してコールバックにフィードする場合、JQueryのラッパーの代わりにDOMオブジェクトを使用します。これにより、サードパーティはJQueryに依存する必要がないため、JQueryと簡単に統合できます。たとえば、生のJavaScriptを使用してテキストを赤でペイントする関数を作成したとします。function paintRed(element){element.style.color="red"}-この関数をコールバックとしてJQuery関数に簡単に渡すことができます。

于 2012-08-21T21:13:29.450 に答える
3

モナド(「純粋な」コードなど)を使用する場合は、グローバル変数と状態の使用を避けることができます。https://github.com/brownplt/flapjax/もご覧ください。Flapjaxは、関数型リアクティブプログラミングライブラリであり、これもモナディックアプローチを使用しています。

于 2012-08-15T09:16:41.910 に答える
0

特定のスコープ内の特定のコールバック関数に引数を渡すことは、モナドを使用して一般化できます。

/* Unit function */
function Monad(value)
  {
  // Construct monad and set value to given argument or undefined
  this.value = value || undefined;
  }

/* Constructor function */
Monad.prototype.pass = function(value, cb, scope)
  {
  // return constructor result if no default value is passed
  if (/undefined/.test(this.value) )
    {
    return new this.constructor();
    }
  // return callback result for given value in given context if scope is passed
  if(scope)
    {
    /* Bind function */
    return cb.call(scope, value);
    }
  // return callback result for given value otherwise
  return cb(value);
  }

 /* Separate arguments from function, and function from global scope */
 var foo = new Monad(RegExp);
 var bar = foo.pass(2, Function("count","return ++count"), Math);

参考文献

于 2013-01-22T20:30:11.527 に答える