Edited at

Ruby のカリー化を日本語で説明してみる

More than 3 years have passed since last update.

日本のカレーは本場インドのとは違うけど、インドの人が食べても美味しいと思うらしい。。。


カリー化

ここは、カレー屋です。(と、します)

「種類」と「個数」を指定すると注文(文字列)を返す「注文」手続きを作ってみます。

注文 = -> 種類, 個数 { "注文は、#{種類}カレーを#{個数}個です" }

p 注文.("ビーフ", 1) #=> "注文は、ビーフカレーを1個です"
p 注文.("ポーク", 2) #=> "注文は、ポークカレーを2個です"
p 注文.("ほうれん草", 3) #=> "注文は、ほうれん草カレーを3個です"

Proc#curry を使って「注文」をカリー化してみます。

カリー化された注文 = 注文.curry

「カリー化された注文」に「種類」を適用して、それぞれの注文手続きを作ってみます。

ビーフカレーの注文     = カリー化された注文["ビーフ"]        # "ビーフ"を適用

ポークカレーの注文 = カリー化された注文["ポーク"] # "ポーク"を適用
ほうれん草カレーの注文 = カリー化された注文["ほうれん草"] # "ほうれん草"を適用

使ってみます。

p ビーフカレーの注文.(1)     #=> "注文は、ビーフカレーを1個です"

p ポークカレーの注文.(2) #=> "注文は、ポークカレーを2個です"
p ほうれん草カレーの注文.(3) #=> "注文は、ほうれん草カレーを3個です"

以上。


部分適用

カリー化された手続きに引数の適用までするのが部分適用です。

「Proc#部分適用」メソッドを定義して、それで「チキンカレーの注文」手続きを作ってみます。

class Proc

alias カリー化 curry # curry に別名「カリー化」をつける

def 部分適用(適用する引数) # 「Proc#部分適用」メソッドを定義
カリー化[適用する引数]
end
end

チキンカレーの注文 = 注文.部分適用("チキン") # (注)メソッドなので「()」で呼び出す

使ってみます。

p チキンカレーの注文.(5)          #=> "注文は、チキンカレーを5個です"

メソッドを定義しなくとも、下のように一文でもできます。

マトンカレーの注文 = 注文.カリー化["マトン"]

p マトンカレーの注文.(10) #=> "注文は、マトンカレーを10個です"

以上。


まとめ


currying_sample_in_japanese.rb

注文 = -> 種類, 個数 { "注文は、#{種類}カレーを#{個数}個です" }

カリー化された注文 = 注文.curry # カリー化

ビーフカレーの注文 = カリー化された注文["ビーフ"] # 部分適用

p 注文.("ビーフ", 1) #=> "注文は、ビーフカレーを1個です"
p ビーフカレーの注文.(1) #=> "注文は、ビーフカレーを1個です"


変数名等に日本語を使ってない版は以下。


currying_sample.rb

order = -> kind, n { "注文は、#{kind}カレーを#{n}個です" }

curried_order = order.curry

beef_order = curried_order["ビーフ"]

p order.("ビーフ", 1) #=> "注文は、ビーフカレーを1個です"
p beef_order.(1) #=> "注文は、ビーフカレーを1個です"


(※ @riocampos さんからコメントをいただいたので追記します。)

以下は、いずれも Proc を呼び出す構文です。詳しくはマニュアルをご覧ください。

ビーフカレーの注文.(1)        # 構文

ビーフカレーの注文[1] # Proc#[] は Proc#call の別名です
ビーフカレーの注文.call(1) # 〃


おわりに

Ruby は変数名などに日本語が使えます。さすが国産言語です。

このメモはそれをネタに遊んでみたくて書いてみました。

スクリプトの動作確認は以下の環境で行っています。


  • Ruby 2.1.5 (Ubuntu Linux 14.04)


カリー化などの詳しいことは、以下の参考リンク、その他をご覧ください。


追記

ちょこっと、追記します。


適用する引数の数が足りた場合

適用する引数の数が足りた場合は、部分適用されたProcでなく元のProcの実行結果が返ります。

sum = -> a, b, c { a + b + c }

sum1 = sum.curry[1]
sum1_2 = sum.curry[1,2]
sum1_2_3 = sum.curry[1,2,3] # 引数の数が足りた

p sum1.(2,3) #=> 6
p sum1_2.(3) #=> 6
#p sum1_2_3.() #=> NoMethodError!! (Proc でないので呼び出すとエラーになる)
p sum1_2_3 #=> 6
p sum1_2_3.class #=> FixNum


部分適用したProcを引数不足で呼び出した場合

部分適用したProcを引数不足で呼び出した場合、部分適用されたProcが返ります。

sum = -> a, b, c { a + b + c }

sum1 = sum.curry[1]
sum1_2 = sum1.(2) # 引数を二つとる sum1 を一つの引数で呼び出す

p sum1_2.(3) #=> 6

(※投稿後ですが、書き漏らしていたので追記します。)

。。。(上記のよう)なので、以下の2種類の書き方はいずれも同じ効果になります。

sum1_2_3 = sum.curry[1,2,3]   # カリー化されたProcに一度に引数を渡す

sum1_2_3 = sum.curry[1][2][3] # 返ってきたProcに引数を渡す、を繰り返す


Proc#curryの引数

Proc#curry の引数には arity(引数の数) を指定できます。

上のように引数の数が固定の場合はあまり意味がないと思います(上の場合 3 以外を指定するとエラーになる)が、可変引数をとるProcの場合は以下のように使えます。

sum = -> *ns { ns.reduce :+ }  # sum は可変引数をとる

p sum.() #=> nil
p sum.(1) #=> 1
p sum.(1,2) #=> 3
p sum.(1,2,3) #=> 6
p sum.(1,2,3,4) #=> 10
p sum.(*1..10) #=> 55
p sum.(*"a".."z") #=> "abcdefghijklmnopqrstuvwxyz" (この例は、お遊び)

sum1 = sum.curry(3)[1] # curry の引数で 3 を指定
sum1_2 = sum1.(2)
sum1_2_3 = sum1_2.(3)

# 結果は、元のProcが3引数だった場合と同じ
p sum1.(2,3) #=> 6
p sum1_2.(3) #=> 6
p sum1_2_3 #=> 6


case 文の when 句やブロックで使う場合

Proc は case 文の when句や、メソッドにブロックとして渡せますが、カリー化されたものは渡す時に(部分的に)引数を渡せます。

less = -> n, x { x < n }.curry

case 3
when less[5] ; p "Yes" #=> "Yes"
end

p [*1..9].select &less[5] #=> [1, 2, 3, 4]

is_array = -> cond, x { x.kind_of?(Array) && x.all? {|e| cond === e } }.curry

case [1,2,3]
when is_array[Integer] ; p "Yes1" #=> "Yes1"
end

case [1,2,3]
when is_array[(1..5)] ; p "Yes2" #=> "Yes2"
end

case ["abc", "bbc", "ccc"]
when is_array[/c/] ; p "Yes3" #=> "Yes3"
end

match = :===.to_proc.curry(2) 

# Array#grep と同様の機能
p ["abc","aaa","bbc","ccc"].select &match[/a/] #=> ["abc", "aaa"]
p [1,:foo,"aaa","bbc",{a:10}].select &match[String] #=> ["aaa", "bbc"]