RubyのProcでのCurry化、部分適用(partial application)など
はじめに
Rubyでは、each
やmap
などを使う時、下記のような書き方があります。
strs = %w(1000 1001 1010)
strs.map do |s|
s.to_i
end
# OR
strs.map{ |s| s.to_i }
# OR
strs.map(&:to_i)
しかし、.to_i(2)
など、要素自身以外の変数を代入しようとする時、strs.map(&:to_i)
の書き方が書けないのは少し不便でした。
ちょうど最近、私はPythonを触っていて、関数の部分適用(partial application)からヒントを得ました。
例えば上記Rubyの例をPythonにすると
strs = ['1000', '1001', '1010']
import functools
int_bin = functools.partial(int, base=2)
[int_bin(s) for s in strs] # [8, 9, 10]
# Or
list(map(lambda s:int_bin(s), strs))
# Or
list(map(int_bin, strs))
上記Pythonコードでは、int_bin
というint(s, base=2)
の部分適用を作り出し、レシーバーとしてmap
に渡しました。
map
に任意のBlockやProcを代入する
下記の方法を使います。
解説はこちらの記事を読んでください。
# Proc を使う場合
to_i_bin = ->(s, base=2) { s.to_i(base) }
strs = %w(1000 1001 1010)
strs.map(&to_i_bin)
# [8, 9, 10]
# def を使う場合
def to_i_bin(s, base=2)
s.to_i(base)
end
strs = %w(1000 1001 1010)
strs.map(&method(:to_i_bin))
# => [8, 9, 10]
他モジュール内メソッドを使いたい場合
こちらの記事を参考してください。
strs = %w(1000 1001 1010)
strs.map(&JSON.method(:parse))
# [1000, 1001, 1010]
# Ruby 2.7 以上
strs.map(&JSON.:parse)
カリー(Curry)化関数をmap
に渡す
foo.bar()
を bar(foo)
へ
foo.bar()
を bar(foo)
へJavaScript界隈でよく使われるトリックの一つ、call
の活用みたいに、Rubyにも類似する使い方があります。
'abc'.toUpperCase(); // "ABC"
// 下記の形式に書き換えられる
''.toUpperCase.call('abc'); // "ABC"
Rubyだと
'abc'.upcase # "ABC"
# 下記の形式に書き換えられる
:upcase.to_proc.call('abc')
# => "ABC"
# callの簡略形式も可
:upcase.to_proc.('abc')
# => "ABC"
:upcase.to_proc['abc']
# => "ABC"
Curry化
RubyはPythonみたいにfunctools.partial
のような関数が組み込まれていない(単純に私の不勉強であるかもしれませんが)ですが、Method#curry
やProc#curry
を活用すれば、想定の効果が得られます。
to_i_c = :to_i.to_proc.curry(2)
to_i_c.call('1001').call(2)
# => 9
# Or
to_i_c['1001'][2]
# => 9
Curry化メソッドの代入
上記Curry化メソッドをmap
に代入してみます
to_i_c = :to_i.to_proc.curry(2)
[2, 8, 10].map(&to_i_c['1001'])
# => [9, 513, 1001]
なぜそうなっているかは、map
でカリー化メソッドを呼び出しつづ、レシーバーにする時、最後の引数だけがiterationの対象となっていて、つまり上記のロジックを展開するとこうなります。
[2, 8, 10].map do |i|
to_i_c['1001'][i]
end
もし、中間の引数をiterationの対象にしたい場合、引数の位置を変更する必要があります。
引数の位置を変更
to_i_r = -> (b, s) { :to_i.to_proc[s, b] }
to_i_c = to_i_r.curry(2)
strs = %w(1000 1001 1010)
strs.map(&to_i_c[2])
# => [8, 9, 10]
引数をMixinして実行(2019-10-11 Update)
とある親切なRubyのコミッターの方が教えてくれたやり方のアレンジです。
curry_exec = -> (f, *args_outer) {
-> (*args_inner) { f[*args_inner, *args_outer] }
}.curry
%w(1000 1001 1010).map(&curry_exec[:to_i.to_proc, 2])
# => [8, 9, 10]
結局、特別な必要がなければ、大人しくProcかBlockを書くほうが、シンプルですね。
strs = %w(1000 1001 1010)
strs.map{ |s| s.to_i(2) }
# Or
strs.map(&->(s){ s.to_i(2) })
参考
- なぜRubyでブロック付き引数を持つメソッドの引数として&:upcaseみたいな値を渡せるのか
- &演算子と、procと、Object#method について理解しなおす
- Rubyのmethodをlambdaに変換する
- Ruby block/proc/lambdaの使いどころ
- Rubyの ブロック、Proc.new、lambdaの違い
- Procのカリー化と部分適用
- instance method Method#curry
- Partial application in Ruby
- Partial function application
- What is the difference between currying and partial application?
Author And Source
この問題について(RubyのProcでのCurry化、部分適用(partial application)など), 我々は、より多くの情報をここで見つけました https://qiita.com/jerrywdlee/items/3aac326f7a7bb83f80de著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .