今週の「思考提供」は、関数型プログラミングの理解を思い起こし、まとめてみました。
関数型プログラミングとは
一般的なプログラミングパラダイムには、命令型と宣言型があり、例えば、おなじみのオブジェクト指向は命令型、関数型プログラミングは宣言型です。ついでに言うと、関数型プログラミングで出てくる「関数」とは、プログラミングにおける「関数」という概念ではなく、数学における関数、つまり変数間の写像のことです。
では、関数型プログラミングとおなじみの宣言型プログラミングの違いは何でしょうか?要約すると、関数型プログラミングはデータのマッピングに関係し、命令型プログラミングは問題を解決することに関係します。
例えば、配列の各数をそれ自身の2倍にする関数を書く場合、命令型プログラミングの考え方は、配列を一度繰り返し、各数を2倍することです:
const solution = (arr) => {
const newArr = [];
for(let i = 0;i < arr.length;i++) {
newArr.push(arr[i]*2);
}
return newArr;
}
しかし、関数型プログラミングの観点から考えるならば、配列Aの各要素は配列Bの各要素の2倍であるという事実に他なりません。 => [2a, 2b, 2c, 2d, ...]。コードは以下の通り:
const solution = (arr) => {
return arr.map(item => {
return item*2;
})
}
上の2つの簡単な例からわかるように、関数型プログラミングと命令型プログラミングの考え方の最大の違いは、関数型はデータのマッピングに重点を置いていることです。
フロントエンド開発の分野では、Reactというフレームワーク自体がView = Fn(Data)というコンセプトで設計されているなど、関数型が多く使われていますし、関数型コンポーネントや高階コンポーネントなどもあり、関数型プログラミングの実践が見られます。
純粋関数
純粋関数は関数型プログラミングにおいて非常に重要な概念で、観測可能な副作用がなく、外部環境に依存せず、同じ入力が常に同じ出力をもたらすような関数として定義されます。
たとえば、配列メソッドの slice と splice です:
var xs = [1,2,3,4,5];
//
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
xs.slice(0,3);
//=> [1,2,3]
//
xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []
例からわかるように、sliceは同じ入力に対して常に同じ出力を返しますが、 spliceは元の配列に直接変更を加えます。
もう一つ例を挙げましょう:
//
var minimum = 21;
var checkAge = function(age) {
return age >= minimum;
};
//
var checkAge = function(age) {
var minimum = 21;
return age >= minimum;
};
明らかに、不純なバージョンでは、checkAgeの返り値は外部変数のminimumに直接依存します。これは、外部環境の変数が変化するとすぐに関数の出力に直接影響するため、同じ入力に対して常に同じ出力を返すことが不可能になります。
副作用
副作用が話題になりますが、そもそも副作用とは何でしょうか?どのような影響が副作用として数えられるのでしょうか?
副作用とは、結果を計算する過程におけるシステムの状態の変化、または外界との観測可能な相互作用として定義されます。
副作用には以下のようなものがありますが、これらに限定されるものではありません:
- ファイルシステムの変更
- データベースへのレコード挿入
- http リクエストの送信
- 変数データ
- /log
- ユーザー入力の取得
- DOM
- システムステータスへのアクセス
おわかりのように、外部環境と相互作用する動作には副作用があり、外部環境との相互作用は関数の出力に影響を与える可能性があります。 関数型プログラミングが副作用を気にするのは、関数型プログラミングの哲学が副作用を誤動作の主な原因と想定しているからです。
利点
純粋な機能は、純粋で副作用がないため、いくつかの利点があります。
予測可能性
純粋な関数には副作用がないため、同じ入力に対して、その出力を予測することができます。
Reduxの公式ドキュメントをもう一度見てください:
アクションによって状態ツリーがどのように変換されるかを指定するには、純粋な還元器を記述します。 還元器は、前の状態とアクションを受け取り、次の状態を返す純粋な関数です。レデューサーは、前の状態とアクションを受け取り、次の > 状態を返す純粋な関数です。 前の状態を変更するのではなく、新しい状態オブジェクトを返すことを忘れないようにしましょう。
明らかに、Reduxでは純粋関数を使ってリデューサーを書く必要があり、予測可能で副作用のない純粋関数はあなたの好みにぴったりです。
キャッシュ可能性
純粋関数では、固定入力は固定出力を与えるので、これはキャッシュに利用できます。
例
var memoize = function(f) {
var cache = {};
return function() {
var arg_str = JSON.stringify(arguments);
cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
return cache[arg_str];
};
};
var squareNumber = memoize(function(x){ return x*x; });
squareNumber(4);
//=> 16
squareNumber(4); // 入力4の結果をキャッシュから読み出す。
//=> 16
squareNumber(5);
//=> 25
squareNumber(5); // 入力5の結果をキャッシュから読み込む。
//=> 25
カレー化
コリアライゼーションの考え方は単純で、引数の一部だけを渡して関数を呼び出し、残りを処理する関数を返すというものです。
一例です:
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
上記の例では、引数xを受け取り、呼び出されたときに最初の引数xをクロージャの形で記憶する関数を返すadd関数を定義しています。
日常的なプログラミングにおけるcurryingの主な用途は、「関数のプリロード」、つまり、将来使用できるようにあらかじめパラメータの一部をキャッシュしておくことです。例えば、vueのソースコードでは、典型的なcurryingの使い方があります:
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
return function patch (oldVnode, vnode, hydrating, removeOnly) {
//...
}
}
vueは、Webとweexをサポートする必要があるため、異なるプラットフォーム上のパッチ操作内部のロジックは基本的に同じですが、唯一の操作方法の実際の要素にマッピング仮想DOMが異なっているので、事前に関連するAPIにcreatePatchFunctionに、APIパッチメソッドの要素の操作を固めるために良いプラットフォームを返します、将来の使用。あなただけの良いにoldVnodeとvnodeと他のパラメータを渡す必要がある場合は、コリーのスキルのこの使用は、学習の価値があります。
コンビナトリアル
次のような方法が存在すると考えてください:
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = function(x){
return exclaim(toUpperCase(x)); //
};
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
メソッドの中にメソッドを入れ子にするこのやり方は、見た目が少々不格好でエレガントではありません:
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
コンポジション・ライティングは、Reduxのcomposeメソッドのように、直接的なカプセル化の代わりにコロケーションを使うことで、よりエレガントなコードを開発することができます:
右から左へファンクションを構成します。
thisは関数型プログラミングのユーティリティで、Reduxには利便性のために含まれています。 複数の店舗エンハンサーを適用するために使いたいかもしれません。このユーティリティを使用して、複数の店舗エンハンサーを連続して適用したいと思うかもしれません。
Arguments
各関数は1つのパラメータを受け取り、その戻り値は左隣の関数の引数として提供されます。例外は、複数のパラメータを受け取ることができる一番右の引数です。例外は、複数のパラメータを受け取ることができる一番右の引数です。
リターン
与えられた関数を右から左へ合成して得られる最終関数。
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers'
const store = createStore(
reducer,
compose(applyMiddleware(thunk), DevTools.instrument())
)
銀の弾丸はありません。
ソフトウェア開発に銀の弾丸はなく、万能のプログラミングパラダイムやフレームワークは存在しません。関数型プログラミングにも同じことが言えます。ハンマーを持ったときにすべてが釘のように見えるのを防ぐために、最良のものから最良のものを選び、最悪のものから最悪のものを取り除くのです。





