12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

javascript での関数部分適用

Last updated at Posted at 2012-08-20

javascript での関数の部分適用(カリー化の説明を参考)は比較的カンタンにできます。
サンプルコードは CoffeeScript で書いています。

partial = (f) ->
  require_length = f.length
  partialized = (args...) ->
    if require_length <= args.length # 関数に必要な引数がすでに足りている
      g = f.apply(null, args.slice(0, require_length)) # 適用して結果を取り出す
      if typeof g is "function" # 結果、関数が返ってきた
        # args の余りから、次の関数を更に部分適用して返す
        partial(g).apply(null, args.slice(require_length)))
      else 
        g # 関数以外のものがきたら、適用しきった事にしてオブジェクトを返す
    else
      # 引数が足りてない場合、足りない引数を待ち受ける関数を返す
      partial_apply = (adds...) -> partialized.apply(null, args.concat(adds))

この partial は先にβ簡約を行い、正規形になるまで部分適用した関数を返す仕組みです。正確には s-m-n定理 と部分適用の組み合わせじゃないかとは思います。

また、一見 末尾再帰のように見えますが、関数かオブジェクトをすぐに返すのでコールスタックが深くなるような事はありません。

部分適用を利用したライブラリの実装例

cps.js
# 継続渡し Array forEach
cps_each = partial (iterator, callback, array) ->
  limit = array.length
  index = 0
  count = 0
  finished = no
  while index < limit
    next = (error) ->
      count += 1
      if not finished and (error or count >= limit)
        finished = yes
        callback error
    iterator next, array[index], index, array
    index += 1

# 継続渡し Array map
cps_map = partial (iterator, callback/*, array */) ->
  results = []
  mapping = null
  cps_each (next, value, index, iterable) ->
    mapping = (error, x) ->
      unless error
        results[index] = x
      next error
    iterator mapping, value, index, iterable
  , (error) -> callback error, results

partial 以下は普通の関数の書き方で宣言が書けます。この時、partial に渡す関数の仮引数の数は必要最小限で宣言するようにします。cps_map の例は「最終的に必要となる仮引数」を /* */ で伏せ、ソースの可読性を損なわないようにしています。

利用例

use_cps.js
# {number} を二乗して次の処理に渡す
# value が {number} でない場合、エラーオブジェクトを次の処理に渡す
cps_square = (next, value, index) ->
  unless typeof value is "number"
    next new TypeError "require number , but typeof value is {#{typeof value}: #{value}} at [#{index}]"
  else
    next null, x * x

# Array.<number> の各要素を二乗して次の処理に渡す
cps_square_map = cps_map cps_square/*, callback, array*/
# callback, array 共に現時点では指定されていない
# cps_square_map の後処理となる callback 、
# 具体的にイテレータを回したい array は
# 後から決める事ができる

cps_log = (error, result) ->
  console.log if error then "!!!ERROR!!! #{error}" else ":::SUCCESS::: #{result}"

square_map = cps_square_map cps_log

square_map [1, 2, 3] # 正常系
#=> :::SUCCESS::: 1, 4, 9

square_map [10, "dummy", 30] # 異常系
#=> !!!ERROR!!! TypeError: require number, but type of value is {string: dummy} at [1]

# 必要な引数を一気に渡しても当然よい。
cps_map cps_square, cps_log, [1, 2, 3]
#=> :::SUCCESS::: 1, 4, 9

以上のように、汎用性と応用に富む強力なユーティリティーを提供することができます。
CoffeeScript より後発で、より関数指向プログラミングを目的とした LiveScript(x, y, z ...) --> という書式で、カリー化された関数にコンパイルしますが、冒頭のような「引数を余らせたら更に適用できるか試す」ような実装にはなっていません。あと、あれもカリー化と部分適用の誤用な気がする…。
また、この実装には オプショナルな可変長引数に対して弱い面がありますが、それは別のエントリーで解説します。

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?