要約:関数型プログラミングを理解します。
- 著者:フロントアッシュ
- オリジナル:JS入門における関数型プログラミングの基本的な原則に
Fundebugは、元の作者に属し、転載を承認しました。
オブジェクト指向プログラミングを学習し、使用するのに長い時間の後、背中のステップやシステムの複雑さを考えてみましょう。
いくつかの研究を行った後、私はそのような不変性と純粋な関数として、関数型プログラミングの概念を発見しました。これらの概念は、副作用なしに機能を構築することができますので、保守が容易なシステムは、他の利点があります。
この記事では、我々は詳細に関連する関数型プログラミングや重要な概念のいくつかを説明するためのコード例の多くを通過します。
関数型プログラミングとは何ですか
関数型プログラミングは、数学関数の評価を計算するために考えられている、プログラミングパラダイム、構造及びスタイル要素を構築するためのコンピュータプログラムであると状態変数データの変化を避けることができます。
ピュア機能
私たちは関数型プログラミングを理解したいときは、まず、基本的な概念を知っている必要があり、純粋な関数であるが、純粋な機能は何地獄ですか?
私たちは純粋な機能はここに非常に厳密な定義があるかどうかの関数である方法を知っています?:
- 同じパラメータが与えられると、同じ結果が返される(とも呼ばれる決定論的)。
- これは、任意の副作用を引き起こすことはありません。
同じパラメータを指定すると、同じ結果が得られ、
同じパラメータを指定した場合は、同じ結果を返します。私たちは、円の面積を計算する機能を実装することを想像してみてください。
機能は、純粋なない、受信radius
パラメータとして、その後、計算radius * radius * PI
:
let PI = 3.14;
const calculateArea = (radius) => radius * radius * PI;
calculateArea(10); // returns 314.0
なぜこれが不純な関数である?それはグローバルオブジェクトがパラメータとして関数に渡されていない使用しているので理由は、簡単です。
さて、数学者の数は、PIの値が実際にあると信じていると想像42
し、グローバルオブジェクトの値を変更します。
不純な機能が得られます10 * 10 * 42 = 4200
。同じパラメータ(のためにradius = 10
)、我々は異なる結果を得ます。
それを修正します:
let PI = 3.14;
const calculateArea = (radius, pi) => radius * radius * pi;
calculateArea(10, PI); // returns 314.0
今、PI
外部オブジェクトが導入されないように、値は、引数として関数に渡されます。
- パラメータの場合
radius = 10
とPI = 3.14
、常に同じ結果が得られます:314.0
。 - 以下の場合
radius = 10
とPI = 42
、常に同じ結果が得られます。4200
ファイルを読みます
次の関数は、それが純粋な関数ではありません、外部ファイルを読み込んで、いつでもファイルの内容が異なる場合があります。
const charactersCounter = (text) => `Character count: ${text.length}`;
function analyzeFile(filename) {
let fileContent = open(filename);
return charactersCounter(fileContent);
}
乱数ジェネレータ
任意の関数は純粋であることができない乱数ジェネレータの機能に依存します。
function yearEndEvaluation() {
if (Math.random() > 0.5) {
return "You get a raise!";
} else {
return "Better luck next year!";
}
}
有意な副作用がありません
純粋関数は、任意の副作用が観察されることはありません。副作用の例としては、可視オブジェクトを含むまたは参照によって渡さグローバルパラメータを変更します。
今、私たちは、機能を実現したい、と整数プラス受け取り、整数を返し1
操作と戻ります。
let counter = 1;
function increaseCounter(value) {
counter = value + 1;
}
increaseCounter(counter);
console.log(counter); // 2
不純な関数は値を受け取り、再割り当てcounter
、その値を増加します1
。
関数型プログラミングは、変動を阻止します。私たちは、グローバルオブジェクトを修正するが、どのようにそれの純粋な機能にするために行うには?ちょうど高めるために戻って1
値を。
let counter = 1;
const increaseCounter = (value) => value + 1;
increaseCounter(counter); // 2
console.log(counter); // 1
純粋関数はincreaseCounter
返します2
が、counter
値は同じまま。この関数は、変数の値を変更せずに、増分された値を返します。
我々はこれらの2つの単純なルールに従うならば、私たちのプログラムを理解することが容易になります。今、それぞれの機能が分離され、それがシステムの他の部分に影響を与えることができません。
純粋関数は、安定した一貫性と予測可能です。同じパラメータを考えると、純粋な関数が常に同じ結果を返します。
それが起こることはありませんので、我々は、さまざまな状況を持っている同じパラメータの結果を検討する必要があります。
純粋な機能のメリット
純粋な機能は確かにコードをテストする方が簡単です、何かをモックする必要はありませんので、我々は別のコンテキストをテストし、純粋な機能ユニットを使用することができます。
- パラメータを考えると
A
、必要な関数の戻り値B
- パラメータを考えると
C
、必要な関数の戻り値D
単純な例では、数字のセットを受信し、それぞれの数追加するためのものである1
ような砂の彫刻の動作を制御します。
let list = [1, 2, 3, 4, 5];
const incrementNumbers = (list) => list.map(number => number + 1);
受信numbers
アレーを使用すると、map
各桁をインクリメントし、数字の新しい増分リストを返します。
incrementNumbers(list); // [2, 3, 4, 5, 6]
入力の場合[1,2,3,4,5]
、予想される出力があります[2,3,4,5,6]
。
不変性
時間変更または同じにもかかわらず、純粋な機能の大物は同じです。
データが不変である場合には、それが作成された後、その状態を変更することはできません。
あなたは、不変オブジェクトを変更するには、ハード来ている場合は、ちょうど深いコピーにコピーし、コピー操作を必要とすることはできません。
JSでは、我々は通常使用しfor
、サイクルをfor
それぞれが横断i
変数変数を。
var values = [1, 2, 3, 4, 5];
var sumOfValues = 0;
for (var i = 0; i < values.length; i++) {
sumOfValues += values[i];
}
sumOfValues // 15
各繰り返し、すべての変更のためにi
とsumOfValue
状態が、どのように我々はトラバーサルの変動に対処するのですか?答えは、使用することです再帰を。
let list = [1, 2, 3, 4, 5];
let accumulator = 0;
function sum(list, accumulator) {
if (list.length == 0) {
return accumulator;
}
return sum(list.slice(1), accumulator + list[0]);
}
sum(list, accumulator); // 15
list; // [1, 2, 3, 4, 5]
accumulator; // 0
上記のコードは持つsum
ベクトル値を受信する機能。それがされるまでの機能は、自分自身を呼び出すlist
空の出口再帰です。各「散歩」のために、私たちは合計値に追加されますaccumulator
の。
再帰の使用は、変数が一定のままです。それは変わりませんlist
し、accumulator
変数。これは、同じ値を保持します。
観察:我々が使用することができreduce
、この機能を実現するために。この議論はで高階関数にコンテンツを取りました。
オブジェクトの最終状態の構築も非常に一般的です。私たちは、文字列にこれを変換しようと、文字列があるとurl slug
。
Rubyはオブジェクト指向プログラミングでは、私たちはクラスを作成することができUrlSlugify
、このクラスがありslugify
、入力文字列を変換する方法をurl slug
。
class UrlSlugify
attr_reader :text
def initialize(text)
@text = text
end
def slugify!
text.downcase!
text.strip!
text.gsub!(' ', '-')
end
end
UrlSlugify.new(' I will be a url slug ').slugify! # "i-will-be-a-url-slug"
命令型プログラミング方法は、最初にすべての私たちは、それぞれに小文字を使用する場合は、上記のそこ使用slugify
のプロセスを実行するために、その後、未使用のスペースを削除し、ハイフンで最後に残ったスペースを交換してください。
それは全体のプロセスでの入力状態を変更する。このようにして、純粋な関数の概念に明らかに矛盾しています。
ここでは関数または関数チェーンの組み合わせにより最適化することができます。換言すれば、次の関数の入力として、元の入力文字列を変更することなく、関数の結果。
const string = " I will be a url slug ";
const slugify = string =>
string
.toLowerCase()
.trim()
.split(" ")
.join("-");
slugify(string); // i-will-be-a-url-slug
コードはこれらの事を行うには主に次のとおりです。
toLowerCase
:すべて小文字に文字列を変換します。- トリム:文字列の空白の両端を削除します。
split
そしてjoin
:指定した文字列の置換に一致するすべてのインスタンスを置き換えます
コードが展開された後BUGがリアルタイムで知ることができない存在し、その後これらのバグを解決するためにも、我々は監視ツールを使用してBUGは、簡単な方法をお勧めするためにここに皆のため、時間のデバッグログを費やしFundebugを。
参照透明性
次に、実装square
機能:
const square = (n) => n * n;
同じ入力に設定し、この関数は常に同じ正味出力を持つことになります。
square(2); // 4
square(2); // 4
square(2); // 4
// ...
される2
パラメータ平方関数は常に返しとして渡されました4
。このようにして、我々は置くことができますsquare(2)
に4
、当社の機能参照透明です。
関数が常に同じ入力に対して同じ結果を生成する場合、基本的に、それは透明とみなすことができます。
この概念では、我々は覚えておくべきクールな事は、この機能で行うことができます。機能を想像してみて
const sum = (a, b) => a + b;
これらのパラメータは、それを呼び出すために使用されています
sum(3, sum(5, 8));
sum(5, 8)
合計が同じである13
ので、ショーは、操作を行うことができます。
sum(3, 13);
この式は常に求めている16
私たちは一定の値を持つ式全体を交換し、それを書き留めすることができ、。
関数は、JSの市民であります
市民の関数としてのJSがトレンドで、機能も考慮され、データを使用するための値として使用することができます。
- これは、定数と変数から引用しました。
- これは他の関数にパラメータとして渡されました。
- 他の関数の結果を返します。
アイデアは、データ転送としての価値と機能として機能することです。このように、我々は新しい行動で新しい関数を作成するために、さまざまな機能を組み合わせることができます。
我々は、2つの値が加算される機能を持っている場合、以下に示すように、値は、倍になります。
const doubleSum = (a, b) => (a + b) * 2;
対応する2つの値を差分し、値が2倍になります。
const doubleSubtraction = (a, b) => (a - b) * 2;
これらの機能はなく、その演算子関数の違いと、同様のロジックを持っています。我々は関数値とみなしてパラメータとして渡すことができれば、我々は関数内でそれを使用する受信者動作機能と機能を構築することができます。
const sum = (a, b) => a + b;
const subtraction = (a, b) => a - b;
const doubleOperator = (f, a, b) => f(a, b) * 2;
doubleOperator(sum, 3, 1); // 8
doubleOperator(subtraction, 3, 1); // 4
f
パラメータとそれに対処するa
とb
、転送sum
機能やsubtraction
使用してdoubleOperator
新しい動作を組み合わせて作成する機能を。
高次機能
我々は高階関数について話すとき、一般的に次のものがあります。
- の関数として、一つ以上のパラメータ
- の関数として結果を返します。
上記を達成するためにdoubleOperator
、それがパラメータとしてオペレータに機能し、それを使用するので、関数は、高次の関数です。
私たちはしばしば使用filter
、map
およびreduce
参照の参照を見て、高階関数です。
フィルタ
特定のセットのために、我々は属性によってフィルタリングします。filter
所望の機能true
またはfalse
要素は、結果セットに含まれるべきかどうかを決定する値。
コールバック式が真の場合、フィルタ機能は、そうでない場合、結果セットの要素が含まれ、それはしません。
簡単な例では、我々は整数の集合を持っているとき、我々は唯一でもしたいです。
命令
でも通常行うすべての配列を取得するために不可欠な方法を使用します。
- 空の配列を作成します。
evenNumbers
- 反復
numbers
- でもプッシュする
evenNumbers
配列を
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 == 0) {
evenNumbers.push(numbers[i]);
}
}
console.log(evenNumbers); // (6) [0, 2, 4, 6, 8, 10]
また、使用することができfilter
さえ機能を受け取るために高階関数をと偶数のリストを返します。
const even = n => n % 2 == 0;
const listOfNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
listOfNumbers.filter(even); // [0, 2, 4, 6, 8, 10]
I [ハッカーランクFP] [2]解決するための興味深い問題は、[フィルタアレイ問題]である[3]。問題は、整数の所定のフィルタアレイ、及び指定された値よりだけ少ない出力すなわちX
これらの値の。
命令型のアプローチは、このように、通常は次のとおりです。
var filterArray = function(x, coll) {
var resultArray = [];
for (var i = 0; i < coll.length; i++) {
if (coll[i] < x) {
resultArray.push(coll[i]);
}
}
return resultArray;
}
console.log(filterArray(3, [10, 9, 8, 2, 7, 5, 1, 3, 0])); // (3) [2, 1, 0]
宣言的な方法
必ず上記のために、我々は次のようにこの問題を解決するために、より宣言的なアプローチをしたいです:
function smaller(number) {
return number < this;
}
function filterArray(x, listOfNumbers) {
return listOfNumbers.filter(smaller, x);
}
let numbers = [10, 9, 8, 2, 7, 5, 1, 3, 0];
filterArray(3, numbers); // [2, 1, 0]
でsmaller
機能を使用this
少し奇妙に見えるし始め、が、理解しやすいです。
filter
上述した第2パラメータの関数this
、すなわち、x
値。
また、使用することができmap
、これを行うために方法を。情報の集合を想像してみて
let people = [
{ name: "TK", age: 26 },
{ name: "Kaio", age: 10 },
{ name: "Kazumi", age: 30 }
]
私たちは、フィルタリングするage
ことで、21歳以上filter
の方法
const olderThan21 = person => person.age > 21;
const overAge = people => people.filter(olderThan21);
overAge(people); // [{ name: 'TK', age: 26 }, { name: 'Kazumi', age: 30 }]
地図
map
主な機能は、アイデアの収集を変換することです。
map
関数は、すべての要素に適用され、変換によって返された値に基づいて、集合の新しいセットが構築される方法。
私たちは21歳以上の人をフィルタリングしたくない場合は、私たちは何をしたい、このように表示されます:TK is 26 years old.
不可欠使って、我々は通常の操作を行います。
var people = [
{ name: "TK", age: 26 },
{ name: "Kaio", age: 10 },
{ name: "Kazumi", age: 30 }
];
var peopleSentences = [];
for (var i = 0; i < people.length; i++) {
var sentence = people[i].name + " is " + people[i].age + " years old";
peopleSentences.push(sentence);
}
console.log(peopleSentences); // ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
宣言型の操作を行います。
const makeSentence = (person) => `${person.name} is ${person.age} years old`;
const peopleSentences = (people) => people.map(makeSentence);
peopleSentences(people);
// ['TK is 26 years old', 'Kaio is 10 years old', 'Kazumi is 30 years old']
全体的なアイデアは、新しい配列に指定された配列を変換することです。
もう一つの興味深い質問ですHackerRank [質問のリストを更新] [3]。私たちは、その値を更新するために、アレイの絶対値を使用します。
例えば、入力が[1,2,3,- 4,5]
出力に必要な[1,2,3,4,5]
、-4
絶対値4
。
簡単な解決策は、値の各更新されたセットを配置することで、非常に危険な練習
var values = [1, 2, 3, -4, 5];
for (var i = 0; i < values.length; i++) {
values[i] = Math.abs(values[i]);
}
console.log(values); // [1, 2, 3, 4, 5]
私たちは、Math.abs関数はその絶対値の値とインプレース更新を変換します。
これは、ソリューションを行うための最善の方法ではありません。
まず第一に、私たちは不変性を知って、フロント不変性を学んだ、より一貫性と予測可能な機能を作り、私たちのアイデアは、新しいコレクションは、すべて絶対に持って作成することでした。
第二に、なぜここで使用するmap
「変換」するために、すべてのデータを
私の最初のアイデアは、試験することであったMath.abs
機能を1つの値のみを処理します。
Math.abs(-1); // 1
Math.abs(1); // 1
Math.abs(-2); // 2
Math.abs(2); // 2
私たちは、正の値(絶対値)に各値を変換したいです。
今すぐ値で絶対値操作を実行する方法を知って、この関数はに渡されたパラメータとして使用することができるmap
機能。
高階関数は、関数のパラメータとして、それを受信して使用することができます覚えていますか?はい、map
機能はこれを行うことができます
let values = [1, 2, 3, -4, 5];
const updateListMap = (values) => values.map(Math.abs);
updateListMap(values); // [1, 2, 3, 4, 5]
減らします
reduce
このアイデアは、機能セットを受信することで、これらの項目の組み合わせによって作成された値を返します。
一般的な例は、取得した注文の合計量です。
あなたがショッピングサイトであると仮定し、私たちは1、2製品を持って、製品および製品3 4カート製品(オーダー)で追加します。今、私たちは、ショッピングカートの合計数を計算する必要があります。
命令的な方法に注文し、各製品の量との和の合計額の便利なリストです。
var orders = [
{ productTitle: "Product 1", amount: 10 },
{ productTitle: "Product 2", amount: 30 },
{ productTitle: "Product 3", amount: 20 },
{ productTitle: "Product 4", amount: 60 }
];
var totalAmount = 0;
for (var i = 0; i < orders.length; i++) {
totalAmount += orders[i].amount;
}
console.log(totalAmount); // 120
使用するとreduce
、我々は処理の量を計算する機能を構築することができますsum
し、パラメータとして渡されたreduce
機能。
let shoppingCart = [
{ productTitle: "Product 1", amount: 10 },
{ productTitle: "Product 2", amount: 30 },
{ productTitle: "Product 3", amount: 20 },
{ productTitle: "Product 4", amount: 60 }
];
const sumAmount = (currentTotalAmount, order) => currentTotalAmount + order.amount;
const getTotalAmount = (shoppingCart) => shoppingCart.reduce(sumAmount, 0);
getTotalAmount(shoppingCart); // 120
ここではshoppingCart
、現在の受信currentTotalAmount
機能sumAmount
、およびそれらを合計するorder
オブジェクト。
また、使用することができるmap
ためにshoppingCart
変換amount
セットを、その後使用するreduce
機能とsumAmount
機能。
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;
function getTotalAmount(shoppingCart) {
return shoppingCart
.map(getAmount)
.reduce(sumAmount, 0);
}
getTotalAmount(shoppingCart); // 120
getAmount
受信したproduct
オブジェクトのみ戻りamount
、すなわち、値を[10,30,20,60]
、その後、reduce
一緒にすべての項目を追加することによって。
3つの機能の例
各高階関数の作品を読みます。ここでは簡単な例では、これらの3つの機能を結合する方法を説明し、お見せする例があります。
それは、ショッピングカートに来るとき、私たちはオーダーリストに、この製品を持っていると仮定
let shoppingCart = [
{ productTitle: "Functional Programming", type: "books", amount: 10 },
{ productTitle: "Kindle", type: "eletronics", amount: 30 },
{ productTitle: "Shoes", type: "fashion", amount: 20 },
{ productTitle: "Clean Code", type: "books", amount: 60 }
]
あなたは車のタイプで買い物をしたい場合はbooks
、合計を、通常の操作を行います。
- 書籍のフィルタの種類
- 使用
map
にショッピングカートを変換するamount
コレクション。 reduce
一緒にすべてのアイテム。
let shoppingCart = [
{ productTitle: "Functional Programming", type: "books", amount: 10 },
{ productTitle: "Kindle", type: "eletronics", amount: 30 },
{ productTitle: "Shoes", type: "fashion", amount: 20 },
{ productTitle: "Clean Code", type: "books", amount: 60 }
]
const byBooks = (order) => order.type == "books";
const getAmount = (order) => order.amount;
const sumAmount = (acc, amount) => acc + amount;
function getTotalAmount(shoppingCart) {
return shoppingCart
.filter(byBooks)
.map(getAmount)
.reduce(sumAmount, 0);
}
getTotalAmount(shoppingCart); // 70
コードが展開された後BUGがリアルタイムで知ることができない存在し、その後これらのバグを解決するためにも、我々は監視ツールを使用してBUGは、簡単な方法をお勧めするためにここに皆のため、時間のデバッグログを費やしFundebugを。
Fundebugについて
Fundebugの JavaScriptの上の焦点は、マイクロチャネル、マイクロチャネルのゲーム、アリペイ小さなプログラム、リアルタイムのオンライン監視BUGネイティブ、Node.jsのとJavaアプリケーションに反応アプレット。2016年以来、二から一一が正式に立ち上げ、Fundebugは20億+エラーイベントの合計を取り扱う、有料顧客はサンシャイン保険、クルミのプログラミング、ライチFM、1対1、マイクロパルスの頭部、青年同盟や他の多くのコミュニティのブランドを持っています。無料試用版へようこそ!