社内勉強会の資料です。

Table of Contents

  1. Why
  2. 関数型プログラミングの特徴
  3. 副作用とは
  4. 副作用がよく出る場所
  5. 副作用の例1
  6. 副作用がない
  7. 副作用の例2
  8. 副作用はなぜよくないのか
  9. 状態、時間、順番全部可変の場合
  10. 関数型がよく使うパターン
  11. Reduceの例
  12. 中間状態の取得
  13. オブジェクト指向(カプセル化)+関数型
  14. Redux
  15. Pipeline 1
  16. Pipeline 2
  17. 関数型のデメリット
  18. オブジェクト指向との関係
  19. まとめ

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再帰
  • 制限が多い

オブジェクト指向との関係

まとめ

  • 副作用を注意しましょう