Functional Programming
社内勉強会の資料です。
Table of Contents
- Why
- 関数型プログラミングの特徴
- 副作用とは
- 副作用がよく出る場所
- 副作用の例1
- 副作用がない
- 副作用の例2
- 副作用はなぜよくないのか
- 状態、時間、順番全部可変の場合
- 関数型がよく使うパターン
- Reduceの例
- 中間状態の取得
- オブジェクト指向(カプセル化)+関数型
- Redux
- Pipeline 1
- Pipeline 2
- 関数型のデメリット
- オブジェクト指向との関係
- まとめ
Why
- 最近の傾向だとオブジェクト指向と関数型の混在使用多い
- Underscore.js, lodash: 関数型ライブラリ
- Immutable.js: Immutable
- React: Declarative (宣言的)
- Redux: reduce
- 新しい言語仕様では関数型の機能が多く含まれている
- Java8~ 関数型インターフェース Lambda
- ES6 Lambda Promise: Monad
- チームで使いたい
関数型プログラミングの特徴
- できるだけ副作用がかいコードを書く
- 副作用がある場合は範囲を絞って書く
副作用とは
副作用がないコード
- 同じ条件を与えれば必ず同じ結果が得られる
- 他の機能に影響を与えない
副作用がよく出る場所
- 変数
- IO
副作用の例1
Quiz: 出力結果は?
let v = 0;
function inc1() {
v++;
console.log(v);
return 2;
}
function inc2() {
v += inc1();
console.log(v);
return v;
}
inc2();
inc2();
副作用がない
function func() {
let v = 0;
function inc1() {
v++;
return 2;
}
function inc2() {
v += inc1();
return v;
}
return inc2();
}
func();
func();
副作用の例2
計算機
class Calculator {
constructor(value) {
this.currentValue = value;
}
plus(value) {
this.currentValue += value;
}
multiply(value) {
this.currentValue *= value;
}
}
const calc = new Calculator(1);
calc.plus(1);
calc.multiply(2);
副作用はなぜよくないのか
- 状態が可変
- 毎回実行結果が変わるかどうか
- 内部状態が変わったかどうか
- どこで変わったのか
-
順番と時間を意識する必要がある
// 1 calc.plus(1); calc.multiply(2); // 2 calc.multiply(2); calc.plus(1);
状態、時間、順番全部可変の場合
プログラムの実行結果が予測できない
const calc = new Calculator(1);
// thread 1 で実行
calc.plus(1);
// thread 2 で実行
calc.multiply(2);
関数型がよく使うパターン
- map
- reduce
- filter
- pipeline(stream)
メリット
- 副作用を避けられる
- ループを抽象化
- 関数組み合わせで再利用可能
Reduceの例
function reducer(accumulator, {type: t, value: v}) {
switch (t) {
case '+':
return plus(accumulator, v);
case '*':
return multiply(accumulator, v);
default:
return accumulator;
}
}
const input = [{type: '+', value: 1}, {type: '*', value: 2}];
_.reduce(input, reducer);
中間状態の取得
const state0 = {};
const state1 = reducer(state0, {type: '+', value: 1});
const state2 = reducer(state1, {type: '*', value: 1});
オブジェクト指向(カプセル化)+関数型
const createStore = (reducer) => {
let state;
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
};
dispatch({});
return { getState, dispatch };
};
const { getState, dispatch } = createStore(reducer);
dispatch({type: '+', value: 1});
// stateの中身は変更不可(read only)
const state = getState();
dispatch({type: '*', value: 2});
Redux
function reducer(currentState, action) {
pureFunc(...);
}
const { getState, dispatch, ... } = createStore(reducer);
// action: {type: 'CLICK_BUTTON', value: 'v1'}
dispatch({type: 'CLICK_BUTTON', value: 'v1'});
dispatch({type: 'SELECT_BUTTON', value: 'v2'});
Pipeline 1
Web Framework
-
データ構造
const connection = { request: { host: 'www.abc.com', request_path: '/', req_headers: {}, }, response: { status: 200, }, assigns: { userData1: 'user', }, ... }
Pipeline 2
Web Framework
-
データの流れ connection -> router() -> pipelines() -> controller() -> model() const connection2 = router(connection1); const connection3 = pipelines(connection2); const connection4 = controller(connection3); const connection5 = model(connection4);
-
controllerの中身 connection -> commonservices() -> action() const connection1 = commonservices(connection); const connection2 = action(connection1);
関数型のデメリット
- メモリと速度 JavaScript再帰
- 制限が多い
オブジェクト指向との関係
- 共通が多い
- オブジェクト指向の原則は関数型も適用 https://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html
まとめ
- 副作用を注意しましょう