LoginSignup
2
0

More than 3 years have passed since last update.

RubyのProcでのCurry化、部分適用(partial application)など

Last updated at Posted at 2019-10-07

はじめに

Rubyでは、eachmapなどを使う時、下記のような書き方があります。

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)

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#curryProc#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) })

参考

2
0
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
2
0