日本のカレーは本場インドのとは違うけど、インドの人が食べても美味しいと思うらしい。。。
カリー化
ここは、カレー屋です。(と、します)
「種類」と「個数」を指定すると注文(文字列)を返す「注文」手続きを作ってみます。
注文 = -> 種類, 個数 { "注文は、#{種類}カレーを#{個数}個です" }
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個です"
以上。
#まとめ
注文 = -> 種類, 個数 { "注文は、#{種類}カレーを#{個数}個です" }
カリー化された注文 = 注文.curry # カリー化
ビーフカレーの注文 = カリー化された注文["ビーフ"] # 部分適用
p 注文.("ビーフ", 1) #=> "注文は、ビーフカレーを1個です"
p ビーフカレーの注文.(1) #=> "注文は、ビーフカレーを1個です"
変数名等に日本語を使ってない版は以下。
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) # 〃
- class Proc (Ruby2.1.0 リファレンスマニュアル) : http://docs.ruby-lang.org/ja/2.1.0/class/Proc.html
#おわりに
Ruby は変数名などに日本語が使えます。さすが国産言語です。
このメモはそれをネタに遊んでみたくて書いてみました。
スクリプトの動作確認は以下の環境で行っています。
- Ruby 2.1.5 (Ubuntu Linux 14.04)
カリー化などの詳しいことは、以下の参考リンク、その他をご覧ください。
- カリー化 (Wikipedia) : http://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96
#追記
ちょこっと、追記します。
##適用する引数の数が足りた場合
適用する引数の数が足りた場合は、部分適用された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"]