ミュータブルな変数を安全に使うためにクロージャやカリー化でできることめも


目的

スコープのこと、何も分かってなかった

先日コードレビューをお願いしに行ったら「スコープを意識したプログラミングを」とアドバイス頂いたのでスコープについておさらいしました(・.・)

前回、BaconJS を使うことで可変な変数の値をいい感じにラップしてくれることを調べてみました。今回も関数型プログラミング〜でミュータブルな変数を守るアプローチを考えてみました。

この記事でやること

  • クロージャを使ったアプローチ
  • カリー化を使ったアプローチ

やらないこと

  • クラスを使ったアプローチ()

題材

html 内に #plus#minus という DOM を作っておき、クリックすることで数値を #result に出力するカウンターを作ってみます。

やっちゃいけない例

$plus = $('#plus')
$minus = $('#minus')

sum = 0
$plus.on 'click', -> 
  sum += 1
  $('#result').text(sum)
$minus.on 'click', -> 
  sum -= 1
  $('#result').text(sum)

絶対やっちゃいけない事例ですね! sum を守るものがありません。これではどこから and 誰からにでも代入されてしまいます!

BaconJS を使ったアプローチ(おさらい)

plus = $('#plus').asEventStream('click').map(1)
minus = $('#minus').asEventStream('click').map(-1)
both = plus.merge(minus)

counter = both.scan(0, (x, y) -> return x + y)

## 実行する
counter.onValue (sum) -> $('#result').text(sum)

plus, minus というイベントストリームを定義し、counter という変数にこの 2 つのストリームを合成する。合成した counter にイベントを監視して登録したイベント発火時のみに処理が走る。内部の値は外から触れないのでこれで変数が守られた!

クロージャを使ったアプローチ

ではではクロージャを使ってみましょう。Javascript は関数スコープらしいので、関数の内部で定義された外部からはアクセスできません。
内部にミュータブルな変数を定義して関数を返す関数をつくることで、この関数の内部の変数を守れますね!

$plus = $('#plus')
$minus = $('#minus')
$result = $('#result')

func_closure = do ->
  num = 0
  return ->
    $result.text(num)
    $plus.on 'click', -> 
      num += 1
      $result.text(num)
      return num
    $minus.on 'click', -> 
      num -= 1 
      $result.text(num)
      return num
    return num

## 実行する
func_closure()

カリー化を使ったアプローチ

クロージャを書きながら、更に抽象化して返す関数を一般化したくなってきましたね。返す関数を引数に返す関数。カリー化というやつですね。カリー化関数を作っておくことで、この func_closure_curry は ミュータブルな数値 num を扱う普遍的な関数に適応することが出来ますね。カリー化素晴らしい!

$plus = $('#plus')
$minus = $('#minus')
$result = $('#result')

# カリー化してみる
func_closure_curry = (f) ->
  num = 0  
  return f(num)

## 実行
func_closure_curry (num) ->
    $result.text(num)
    $plus.on 'click', -> 
      num += 1
      $result.text(num)
      return num
    $minus.on 'click', -> 
      num -= 1 
      $result.text(num)
      return num
    return num

更に一般化してみる(おまけ)

よく見ると $result.text(num) というカウンターの値を出力する部分が重複しているので更に一般化できそうです。こういう View を表示する関数は別で切り分けたほうが後々使いやすそうです。

func_closure_curry = (f) ->
  num = 0  
  return f(num)

func_closure_curry_more = (views) ->
  return func_closure_curry (num) ->
    views(num)
    $plus.on 'click', -> 
      num += 1
      views(num)
      return num
    $minus.on 'click', -> 
      num -= 1 
      views(num)
      return num
    return num

## 実行する
func_closure_curry_more (num) -> 
  $result.text(num)
  return

まとめ

以上、ざっくりでしたが Javascript でスコープ内のミュータブル変数を守ることを考えながらスコープのおさらいをしてみました。

  • スコープ少しは分かったかも。
  • カリー化された関数にその引数を渡すことで関数を一般化して作ることができる。
  • 美味しいカレーライスが食べたい。(ナンでも可)