Object#method とカリー化で関数型っぽく(Ruby)

Object#method はメソッドをオブジェクト化するものである。
 

Method は取り出しの対象であるメソッドがなければ作れませんが、Proc は準備なしに作れます。その点から Proc は使い捨てに向き、Method は何度も繰り返し生成する場合に向くと言えます。また内包するコードの大きさという点では Proc は小規模、Method は大規模コードに向くと言えます。

https://docs.ruby-lang.org/ja/latest/class/Method.html

 
例として、ここダイクストラ法のメソッドを使ってみる。まず、Hash で全体のグラフ構造を与える。

graph = {s: {t: 6, y: 4}, t: {x: 3, y: 2},
         x: {z: 4}, y: {z: 3, t: 1, x: 9}, z: {s: 7, x: 5}}


このグラフに対して、始点を与えてダイクストラ法を実行する関数を、Method オブジェクトから作ってみる。

dijkstra = method(:dijkstra).curry
give_shortest = dijkstra.(graph)


これで、例えば始点:s:xを与えて最短経路が求められる。

give_shortest.(:s).first    #=>{:s=>0, :t=>5, :y=>4, :z=>7, :x=>8}
give_shortest.(:x).first    #=>{:x=>0, :z=>4, :s=>11, :t=>16, :y=>15}

例えば:sを始点としたとき、:xまでの最短距離は8であるとわかる。同様に、:xを始点としたとき、:sまでの最短距離は11である。


次いで、始点と終点を与え、最短距離と最短経路を出力する関数を作ってみる。

calc_path = ->(dijkstra_func, start, goal) {
  shortest, pred = dijkstra_func.(start)
  
  route = [goal]
  route.unshift(pred[route[0]]) until route[0] == start

  [shortest[goal], route]
}.curry
shortest_path = calc_path.(give_shortest)


こんな風に使える。

shortest_path.(:s, :x)    #=>[8, [:s, :y, :t, :x]]
shortest_path.(:x, :s)    #=>[11, [:x, :z, :s]]

つまり、始点が:sで終点が:xのとき、最短経路は8で、その経路は s→y→t→x の順であるとわかる。


このように、Method オブジェクトがカリー化されたgive_shortestが、何度も使い回されていることがわかると思う。これを変えれば、同じコードで別のグラフにも簡単に対応できる。