OneDay

40歳からプログラマとして生活しています。

curryが良くわからなかったので、ひたすら引用したら少し見えてきた -Scala, Ruby

2016年で新たな言語に取り組むと宣言していました。SwiftPythonを候補に考えていたが、Scalaを選んだ。関数型言語と呼ばれるものだ。


なぜScalaを選んだか。他の言語を通して、Rubyを深めたいのと、ちょっと変わった組み合わせができれば、それがプログラマとしての付加価値に繋がるかなと考えた。SwiftPythonは新しい書籍がバンバン出ていて、新鮮な書籍に困らないという良さがあったが、一から文字列や配列といったことを学んで拡張ライブラリを用いて何かを作っていくという過程を踏むというのが、時間という制約がある41歳からしてみれば勿体無かった。なんでScalaやりはじめました。


で、本題。なぜcurryを取り上げたか。



この書籍、ここ数日Curryという概念というが私を縛り続けている。その書籍p34にあるEXERCISE 2.3

カリー化(currying)では、引数2つの関数fが、fを部分的に適用する引数1つの関数に変換される。この実装を記述せよ。
(こたえ)

def curry[A,B,C](f: (A, B) => C): A => (B => C) =
  a => b => f(a, b)

この数式に何の意味があるのか?そもそもカリーって何だ。
カリー化 - Wikipediaによると

複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。

よく分からない。勉強してきたRubyを通してならば、ある程度掴めるのではないか、とググッてコードと説明を引用する。Rubyは2.1.0です。

書籍引用 パーフェクト Ruby on Rails p285 Proc#curry

add = Proc.new{|x, y| x + y} #=> #<Proc:0x007f8f9e7383d8@(pry):13>
inc = add.curry.(1) #=> #<Proc:0x007f8f9e70acd0>
inc.(2) #=> 3

inc.(2)は他にもこのようなシンタックスシュガーがある。inc.call(2), inc[2]

この引数(1,2)のadd関数を遡ってみる
①inc.(2) ②add.curry.(1).(2)
単純にincを置き換えただけだが、これもちゃんと3を返す。まるでメソッドチェーンのように左から順に計算が積み上がっていく。add.curryでadd関数がcurry化してできた関数 ▶ add.curry.(1)は左の関数に引数1を取った関数 ▶ add.curry.(1).(2)は左の関数に引数2を取った関数でadd関数の引数数と引数が揃うから、これでadd関数が返る。


「関数型Ruby」という病(3) - カリー化(Proc#curry, Proc#flip) - ( ꒪⌓꒪) ゆるよろ日記から引用

[80] pry(main)> f = lambda{|a,b,s| s.to_s.gsub(a,b) }
=> #<Proc:0x007fa042026068@(pry):94 (lambda)>
[87] pry(main)> arr = [:foo, :bar,:oooppai, :nooo, :baz]
=> [:foo, :bar, :oooppai, :nooo, :baz]

[88] pry(main)> arr.map{|s| f.(/oo/, "aa", s) } #ブロックを使用した場合
=> ["faa", "bar", "aaoppai", "naao", "baz"]

[89] pry(main)> g = f.curry.(/oo/).("aa") #カリーを使用
=> #<Proc:0x007fa0420f31f8 (lambda)>

[91] pry(main)> arr.map(&g)
=> ["faa", "bar", "aaoppai", "naao", "baz"]

[92] pry(main)> arr.map(&f.curry.(/oo/).("aa"))
=> ["faa", "bar", "aaoppai", "naao", "baz"]

カリー化を使えば、ブロックを書かなくても目的を達成できる。上記のブロック内では、第1引数と第2引数が固定で、第3引数のみがarrの各要素に置き換えられるわけだ。であれば、最初から第1引数と第2引数を部分適用済みの関数があればよい。関数fをカリー化して、第1引数と第2引数に/oo/と"aa"を部分適用させた関数gを用意した。この関数gは1引数の関数なので、mapに渡すことができる。結果、arrの各要素を第3引数として適用した結果を得ることが可能だ。なお、カリー化はmapに渡す際に行っても問題ない。このように、多引数の関数をカリー化した上で部分適用し、1引数関数を取る高階関数に渡すのが、カリー化の主な用途だ。

Ruby lambdasから引用Ruby lambdas · GitHub

# Example 1
l = lambda { |x, y, z| x + y + z }
l.curry[1][2][3] # => 6

# Example 2
a = l.curry[1] # => <Proc:0x007fc759a22920 (lambda)>
b = a[2]       # => <Proc:0x007fc759a68b00 (lambda)> 
b[3]           # => 6

# Better real world example
apply_math = -> fn, a, b { a.send fn, b }
add = apply_math.curry.(:+)
add.(1, 2) # => 3
increment = add.curry.(1)
increment.(1) # => 2
increment.(5) # => 6

curryの引数は、数値だけでなく関数も可。Example 2のb[3]は遡れば、a[2][3] となり、さらにl.curry[1][2][3]となるからEcample 1と同じ。l.curry[1][2][3]はl.curry.(1).(2).(3)と同じ。

Procを制する者がRubyを制す(嘘)から引用 http://melborne.github.io/2014/04/28/proc-is-the-path-to-understand-ruby/

join = ->suffix,body{ body + suffix }.curry #Procオブジェクトをカリー化する

# カリー化されたProcオブジェクトに一部の引数を部分適用する。
# これにより残りの引数を受けるProcオブジェクトが生成される。
join['ist'] # => #<Proc:0x007fdf011ce1b0 (lambda)>

%w(real social ruby).map(&join['ist']) # => ["realist", "socialist", "rubyist"]
%w(sell climb haskell).map(&join['er']) # => ["seller", "climber", "haskeller"]
%w(music physic Janis).map(&join['ian']) # => ["musician", "physician", "Janisian"]

joinというオブジェクトに代入したProcオブジェクトの引数の順序に注意。先にsuffixがある。curry化は関数で設定された引数を順番に呼んでいく。


チルダがRubyカレーをもっと好きにさせるから引用 http://melborne.github.io/2012/06/30/tilde-makes-curry-better/

multi = ->x,y{ x * y }.curry # => #<Proc:0x0000010109ae00 (lambda)>

multi_by2 = multi[2] # => #<Proc:0x0000010109a928 (lambda)>
[*1..10].map { |i| multi_by2[i] } # => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

lambdaブロックを直接curry化している。最初の引数を固定して新たな関数を作る。5を引数にしたら配列の要素に5を掛ける関数となる。


RubyAPI(2.2.0)から引用 http://docs.ruby-lang.org/en/2.2.0/Method.html#method-i-curry

def foo(a,b,c)
  [a,b,c]
end

proc  = self.method(:foo).curry
proc2 = proc.call(1, 2)          #=> #<Proc>
proc2.call(3)                    #=> [1,2,3]

def vararg(*args)
  args
end

proc = self.method(:vararg).curry(4)
proc2 = proc.call(:x)      #=> #<Proc>
proc3 = proc2.call(:y, :z) #=> #<Proc>
proc3.call(:a)             #=> [:x, :y, :z, :a]

curry(arity)->proc curryは引数をとると関数の引数数を決められる。curry(4)とすれば4が引数ということだ。

着眼すべきは、Object#methodを用いて、methodをオブジェクト化している。Ruby2.2以降はこの対応によって、ブロックだけでなくメソッドもcurry化できる。以前のバージョンはNoMethodErrorが返った。何気にすごい。メソッドからcurry化ができて、さらに引数の数が合わないからってArgumentErrorにならない。

proc  = self.method(:foo).curry
NoMethodError: undefined method `curry' for #<Method: Object#foo> #2.1.0ではできない




いろいろと引用したりしたけれど、今時点でcurryを使って「おー」と思ったことが一つある。
result = h(g(f(x)))
ちょっと前までこんな数式見ただけで、スルーしていたが、これがcurryでやるとスゴく分かりやすくなる。Rubyを用いる。まずはこの数式のような例。やっていることは簡単。2倍する/ 10加算する/ 2乗するという3つの関数をlambdaを使ってオブジェクトにし、10を引数にして h(g(f(x)))という関数を作ってみる。

[59] pry(main)> calc1 =->x{x * 2}
=> #<Proc:0x007fb9f6cdf1d8@(pry):64 (lambda)>
[60] pry(main)> calc2 =->x{x + 10}
=> #<Proc:0x007fb9f6cf61f8@(pry):65 (lambda)>
[61] pry(main)> calc3 =->x{x ** 2 }
=> #<Proc:0x007fb9f78b1240@(pry):66 (lambda)>

[64] pry(main)> result1 = calc3.(calc2.(calc1.(10)))
=> 900

配列の要素にこの関数を当てはめてみる。

[65] pry(main)> ary=[*1..10]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[67] pry(main)> ary.map{|x|calc3.(calc2.(calc1.(x)))}
=> [144, 196, 256, 324, 400, 484, 576, 676, 784, 900]

この関数が長くなってしまったので簡潔にする。

[79] pry(main)> calc_123 = ->(x){calc3.(calc2.(calc1.(x)))}
=> #<Proc:0x007fb9f6dce968@(pry):84 (lambda)>
[81] pry(main)> ary.map{|x| calc_123.(x)}
=> [144, 196, 256, 324, 400, 484, 576, 676, 784, 900]

lambdaやprocを用いて関数をオブジェクト化して、関数を組み合わせると括弧が増えコードが読みづらい。ここからcurryしてみる。

[59] pry(main)> calc1 =->x{x * 2}
=> #<Proc:0x007fb9f6cdf1d8@(pry):64 (lambda)>
[60] pry(main)> calc2 =->x{x + 10}
=> #<Proc:0x007fb9f6cf61f8@(pry):65 (lambda)>
[61] pry(main)> calc3 =->x{x ** 2 }
=> #<Proc:0x007fb9f78b1240@(pry):66 (lambda)>

[68] pry(main)> calcfn = ->(fn1, fn2, fn3, a){fn1.(fn2.(fn3.(a)))}
=> #<Proc:0x007fb9f79820e8@(pry):73 (lambda)>
[69] pry(main)> calcfn_curry=calcfn.curry
=> #<Proc:0x007fb9f79a9b98 (lambda)>

[75] pry(main)> result = calcfn_curry.(calc3).(calc2).(calc1).(10)
=> 900

[77] pry(main)> calcfn_3 =calcfn_curry.(calc3).(calc2).(calc1)
=> #<Proc:0x007fb9f7a23420 (lambda)>
[78] pry(main)> ary.map{|x| calcfn_3.(x)}
=> [144, 196, 256, 324, 400, 484, 576, 676, 784, 900]

calcfn_curry.(calc3).(calc2).(calc1).(10)の何が「おー」と思ったかというと

  • 関数が横並びしてように見えて可視性があがったように見える。
  • 右端から順に計算していくことができる。(calc1).(10)で20が返り、(calc2).(20)で30が返り、(calc3).(30)で900が返る。
  • 左端からは途中でメソッドを切ってもcurry化されたブロックが返る。 calcfn_curry.(calc3).(calc2)で切って変数に代入できる。

(何か凄そうだけど、まだ良く分からない。)



最後に再びScalaRuby、同じような関数をcurryを用いる。

scala> val func= (x: Int, y: Int, z: Int)=> Array(x*10 , y*100, z*1000)
func: (Int, Int, Int) => Array[Int] = <function3>

scala> val func_curry = func.curried
func_curry: Int => (Int => (Int => Array[Int])) = <function1>

scala> func_curry(5)(5)(5)
res13: Array[Int] = Array(50, 500, 5000)
[87] pry(main)> func = ->(x, y, z){ [x * 10, y * 100, z * 1000] }
=> #<Proc:0x007fb9f6c07030@(pry):92 (lambda)>
[88] pry(main)> func_curry = func.curry
=> #<Proc:0x007fb9f6bf6780 (lambda)>
[89] pry(main)> func_curry[5][5][5]
=> [50, 500, 5000]
[90] pry(main)> func_curry.(5).(5).(5)
=> [50, 500, 5000]