さまざまなプログラミングスタイル(OOP、関数型、手続き型)の研究を始めたばかりです。
私はJavaScriptを学び、underscore.jsから始めて、ドキュメントの this の小さなセクションにたどり着きました。ドキュメントによると、underscore.jsはオブジェクト指向または機能スタイルで使用でき、これらは両方とも同じ結果になります。
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
どちらが機能的でどれがOOPであるかはわかりません。また、これらのプログラミングパラダイムを調査した後でも、その理由はわかりません。
「=」とは「機能的」ではないものの正しい定義はありませんが、一般に関数型言語は、データと関数が関係する場合、単純さに重点を置いています。
ほとんどの関数型プログラミング言語には、オブジェクトに属するクラスとメソッドの概念がありません。関数は、データ構造に属するのではなく、明確に定義されたデータ構造を操作します。
最初のスタイル_.map
は、_
名前空間の関数です。これはスタンドアロン関数であり、それを返すか、引数として別の関数に渡すことができます。
function compose(f, g) {
return function(data) {
return f(g(data));
}
}
const flatMap = compose(_.flatten, _.map);
メソッドインスタンスは本質的にオブジェクトの構築に使用されるデータに関連付けられているため、2番目のスタイルに対して同じことを行うことはできません。したがって、最初の形式はmore機能的であると言えます。
どちらの場合も、一般的な関数型プログラミングスタイルでは、データを関数の最後の引数にする必要があります。これにより、以前の引数をカリー化したり、部分的に適用したりしやすくなります。 Lodash/fp および ramda これは、マップに次の署名を付けることで対処します。
_.map(func, data);
関数がカリー化されている場合は、最初の引数を渡すだけで、関数の特定のバージョンを作成できます。
const double = x => x * 2;
const mapDouble = _.map(double);
mapDouble([1, 2, 3]);
// => [2, 4, 6]
オブジェクト指向プログラミング(OOP)と関数型プログラミング(FP)はプログラミングパラダイムです。大まかに言えば、プログラミングパラダイムに従うと、特定のルールセットに準拠したコードが記述されます。たとえば、コードをユニットに編成することをOOPと呼び、副作用を回避することをFPと呼びます。
各プログラミングパラダイムは特定の機能で構成されていますが、お気に入りの言語が1つのパラダイムに分類されるすべての機能を提供する必要はありません。実際、OOPは inheritance または encapsulation なしで生きることができるため、JavaScript(JS)はOOP継承あり、カプセル化なしの言語。
これでプログラミングパラダイムとは何かを理解できたと思います(うまくいけば)OOPとFPの基本について簡単に説明します。
OOPでは、オブジェクトは、同じ概念を参照することになっている情報と操作を含むボックスです。情報は「属性」と呼ばれ、操作は「メソッド」と呼ばれます。属性を使用するとオブジェクトの状態を追跡でき、メソッドを使用するとオブジェクトの状態を操作できます。
JSでは、特定のメソッドを実行するためにオブジェクトにメッセージを送信できます。以下のコードは、JSでメソッドを呼び出す方法を示しています。 「point」オブジェクトには、「x」と「y」の2つの属性と、「translate」と呼ばれるメソッドがあります。 「translate」メソッドは、指定されたベクトルに基づいて「point」の座標を更新します。
_point = {
x: 10, y: 10,
translate: function (vector) {
this.x += vector.x;
this.y += vector.y;
}
};
point.x; // 10
point.translate({ x: 10, y: 0 });
point.x; // 20
_
このような単純なケースに関連する機能は多くありません。 OOPでは、コードはクラスに分割されることが多く、通常は継承とポリモーフィズムをサポートします。しかし、私はすでにあなたのスコープの外にいるので、詳細については触れません。質問。
FPでは、コードは基本的に関数の組み合わせです。さらに、データは不変であるため、副作用のないプログラムを作成できます。関数型コードでは、関数は外界を変更することができず、出力値は指定された引数にのみ依存します。これにより、プログラムフローを強力に制御できます。
実際、JSはFP言語として使用できますが、副作用を処理する限り、そのための組み込みメカニズムはありません。次のコードは、そのようなプログラミングスタイルの例です。 " zipWith "関数は Haskell の世界に由来します。この関数は、与えられた関数add(point[i], vector[i])
を使用して2つのリストをマージします。
_zipWith = function (f, as, bs) {
if (as.length == 0) return [];
if (bs.length == 0) return [];
return [f(as[0], bs[0])].concat(
zipWith(f, as.slice(1), bs.slice(1))
);
};
add = function (a, b) {
return a + b;
};
translate = function (point, vector) {
return zipWith(add, point, vector);
};
point = [10, 10];
point[0]; // 10
point = translate(point, [10, 0]);
point[0]; // 20
_
ただし、この定義は表面的なものです。たとえば、純粋な関数型言語であるHaskellは、関数の構成、ファンクタ、カレー、モナドなど、さらに多くの概念を実装しています。
実際、OOPとFPは、共通点のない2つの異なる概念であり、比較するものは何もないと言います。したがって、 Underscore.jsのドキュメントから読んだことがあるのは、言語の誤用です。
このライブラリの範囲でプログラミングパラダイムを研究するべきではありません。実際、Underscore.jsを使用してコードを作成する方法は、OOPおよびFPに似ていますが、見た目の問題だけです。したがって、実際にはわくわくするようなことは何もありません:-)
詳しくはウィキペディアを参照してください。
Functional:オブジェクトを関数に渡して、
_.map([1, 2, 3], function(n){ return n * 2; });
OOP:オブジェクトの関数を呼び出して、
_([1, 2, 3]).map(function(n){ return n * 2; });
どちらの例でも、[1,2,3] (array)
はオブジェクトです。
例OOP reference: http://underscorejs.org/#times
[〜#〜] fp [〜#〜]
FPでは、関数は入力を受け取り、同じ入力が同じ出力を生成することを保証して出力を生成します。これを行うには、関数は常に、その関数が操作する値のパラメーターを持っている必要があり、cannotは状態に依存します。つまり、関数が状態に依存していて、その状態が変化する場合、関数の出力は異なる可能性があります。 FPは、これをすべてのコストで回避します。
FPおよびOOPでmap
の最小実装を示します。このFPの例では、以下の例で、map
がローカル変数でのみ動作し、依存しないことに注意してください。オン状態-
const _ = {
// ???? has two parameters
map: function (arr, fn) {
// ???? local
if (arr.length === 0)
return []
else
// ???? local
// ???? local // ???? local // ???? local
return [ fn(arr[0]) ].concat(_.map(arr.slice(1), fn))
}
}
const result =
// ???? call _.map with two arguments
_.map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
このスタイルでは、map
が_
オブジェクトに格納されているかどうかは関係ありません。オブジェクトが使用されていたため、「OOP」にはなりません。簡単に書くことができたでしょう-
function map (arr, fn) {
if (arr.length === 0)
return []
else
return [ fn(arr[0]) ].concat(map(arr.slice(1), fn))
}
const result =
map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
これは、FP-
// ???? function to call
// ???? argument(s)
someFunction(arg1, arg2)
FP=の注目すべき点は、map
にarr
とfn
の2つのパラメーターがあり、map
の出力がこれらの入力にのみ依存していることです。これがどのように劇的に変化するかは、 OOP以下の例。
[〜#〜] oop [〜#〜]
OOPでは、オブジェクトは状態の格納に使用されます。オブジェクトのメソッドが呼び出されると、メソッド(関数)のコンテキストは動的を受け取りオブジェクトにthis
としてバインドされます。 this
はchangeing値であるため、OOPは、同じ入力が指定されていても、どのメソッドでも同じ出力になることを保証できません。
注意:map
は、fn
という1つの引数のみを受け取ります。 map
だけを使用してfn
をどうやってできますか?私たちは何をmap
にしますか?ターゲットをmap
に指定するにはどうすればよいですか? FP関数の出力が入力のみに依存しなくなったため、これを悪夢と見なします。__[変数]に依存するため、map
の出力を判別するのが難しくなりました動的this
の値-
// ???? constructor
function _ (value) {
// ???? returns new object
return new OOP(value)
}
function OOP (arr) {
// ???? dynamic
this.arr = arr
}
// ???? only one parameter
OOP.prototype.map = function (fn) {
// ???? dynamic
if (this.arr.length === 0)
return []
else // ???? dynamic // ???? dynamic
return [ fn(this.arr[0]) ].concat(_(this.arr.slice(1)).map(fn))
}
const result =
// ???? create object
// ???? call method on created object
// ???? with one argument
_([1, 2, 3]).map(function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
これはOOP-の動的呼び出しの基本的なレシピです
// ???? state
// ???? bind state to `this` in someAction
// ???? argument(s) to action
someObj.someAction(someArg)
FPの再検討
最初のFP=の例では、.concat
と.slice
が表示されます-これらはOOP動的呼び出しではありませんか?これらは特に入力配列を変更しないため、FPで安全に使用できます。
とはいえ、通話スタイルの混在は少し面倒かもしれません。 OOPメソッド(関数)が表示される「中置」表記を優先between関数の引数-
// ???? arg1
// ???? function
// ???? arg2
user .isAuthenticated (password)
これはJavaScriptの演算子も機能する方法です-
// ???? arg1
// ???? function
// ???? arg2
1 + 2
FPは、関数が常に引数の前に来る「接頭辞」表記を優先します。理想的な世界では、OOPメソッドと演算子をanyの位置で呼び出すことができますが、残念ながらJSはこのように機能しません-
// ???? invalid use of method
.isAuthenticated(user, password)
// ???? invalid use of operator
+(1,2)
.conat
や.slice
などのメソッドを関数に変換することで、FPプログラムをより自然な方法で記述できます。プレフィックス表記を一貫して使用すると、想像しやすくなることに注意してください。計算の実行方法-
function map (arr, fn) {
if (isEmpty(arr))
return []
else
return concat(
[ fn(first(arr)) ]
, map(rest(arr, 1), fn)
)
}
map([1, 2, 3], function(n){ return n * 2; })
// => [ 2, 4, 6 ]
メソッドは次のように変換されます-
function concat (a, b) {
return a.concat(b)
}
function first (arr) {
return arr[0]
}
function rest (arr) {
return arr.slice(1)
}
function isEmpty (arr) {
return arr.length === 0
}
これは、FPの他の利点を示し、関数は小さく保たれ、1つのタスクに焦点を合わせます。これらの関数は入力でのみ動作するため、プログラムの他の領域で簡単に再利用できます。
あなたの質問はもともと2016年に尋ねられました。それ以来、最新のJS機能により、より洗練された方法でFPと書くことができます-
const None = Symbol()
function map ([ value = None, ...more ], fn) {
if (value === None)
return []
else
return [ fn(value), ...map(more, fn) ]
}
const result =
map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
statements の代わりに expressions を使用したさらなる改良
const None = Symbol()
const map = ([ value = None, ...more ], fn) =>
value === None
? []
: [ fn(value), ...map(more, fn) ]
const result =
map([1, 2, 3], n => n * 2)
console.log(result)
// [ 2, 4, 6 ]
ステートメントは副作用に依存しますが、式は直接値に評価されます。式を使用すると、コードに潜在的な「穴」が残り、ステートメントがエラーをスローしたり、値を返さずに関数を終了したりするなど、いつでも何かを実行できます。
FPとオブジェクト
FPは「オブジェクトを使用しない」という意味ではありません。これは、プログラムを簡単に推論する機能を維持することです。 OOPを使用しているような錯覚を与える同じmap
プログラムを作成できますが、実際にはFPのように動作します。これはlooksメソッド呼び出しに似ていますが、実装はローカル変数のみに依存し、-notは動的状態(this
)に依存しています。
JavaScriptはリッチで表現力豊かなマルチパラダイム言語であり、ニーズと好みに合わせてプログラムを作成できます-
function _ (arr) {
function map (fn) {
// ???? local
if (arr.length === 0)
return []
else
// ???? local
// ???? local // ???? local // ???? local
return [ fn(arr[0]) ].concat(_(arr.slice(1)).map(fn))
}
// ???? an object!
return { map: map }
}
const result =
// ???? OOP? not quite!
_([1, 2, 3]).map(function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
両方のmap
は機能的であり、どちらのコードもマップ関数による概念、値=>値に基づいています。
ただし、object.mapスタイルのため、どちらもOOPで表示されます。
Underscoreによる関数型プログラミングを理解するように勧めません。