LoginSignup
49
49

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-11

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

カリー化

ここは、カレー屋です。(と、します)
「種類」と「個数」を指定すると注文(文字列)を返す「注文」手続きを作ってみます。

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

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"]
49
49
6

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
49
49