高階関数はコードをユーザフレンドリーにする

計算機プログラムの構造と解釈」の
図形言語のところでつまずいているので
もう一度高階関数に対する自分の理解を
Rubyを使って整理してみる


高階関数」とはその引数として関数を取ったり
その戻り値として関数を返したりする関数のことである


今3つの数の和、差、積、商を求めるメソッドを考える

 def plus_abc(a, b, c)
   a + b + c
 end
 
 def minus_abc(a, b, c)
   a - b - c
 end
 
 def mul_abc(a, b, c)
   a * b * c
 end
 
 def div_abc(a, b, c)
   a / b / c unless b == 0 or c == 0
 end
 
 plus_abc(1, 2, 3) # => 6
 minus_abc(10, 5, 3) # => 2
 mul_abc(4, 5, 6) # => 120
 div_abc(12, 3, 2) # => 2

これらのメソッドは
使っている演算子は違うけれども
a (演算子) b (演算子) cという制御構造は同じだ


だから変数と同じように演算を引数として渡せれば
これらのメソッドは一つにまとめられる

 def calc_abc(a, b, c, op)
   op[a, b, c]  #またはop.call(a, b, c)
 end

ここでopは上記演算の抽象である


これに対して各演算の専用メソッドにおいて
具体的な演算の手続きをcalc_abcメソッドの引数として渡す

 def plus_abc(a, b, c)
   calc_abc(a, b, c, lambda { |x, y, z| x + y + z })
 end
 
 def minus_abc(a, b, c)
   calc_abc(a, b, c, lambda { |x, y, z| x - y - z })
 end
 
 def mul_abc(a, b, c)
   calc_abc(a, b, c, lambda { |x, y, z| x * y * z })
 end
 
 def div_abc(a, b, c)
   calc_abc(a, b, c, lambda { |x, y, z| x / y / z }) unless b ==0 or c == 0
 end
 
 plus_abc(1, 2, 3) # => 6
 minus_abc(10, 5, 3) # => 2
 mul_abc(4, 5, 6) # => 120
 div_abc(12, 3, 2) # => 2


演算を抽象化したcalc_abcメソッドがあれば
後から同種の演算を簡単に追加できる

 def power_abc(a, b, c)
   calc_abc(a, b, c, lambda { |x, y, z| x ** y ** z })
 end
 
 power_abc(2, 2, 2) # => 16


さらに同種の演算子を追加する場合に
毎回手続きであるlambda...の入力は煩わしい
演算子を引数にとって手続きを戻り値とする
メソッドを定義できれば問題は解決する

 def operator(op)
   lambda { |x, y, z| (x.send(op, y)).send(op, z) }
 end

operatorメソッドは演算子を引数に取って
3つの数に順次その演算子を作用させる手続きを返す
つまりoperatorメソッドは演算手続きを抽象化する


これを用いることによって
各演算メソッドはより簡潔になる

 def plus_abc(a, b, c)
   calc_abc(a, b, c, operator(:+))
 end
 
 def minus_abc(a, b, c)
   calc_abc(a, b, c, operator(:-))
 end
 
 def mul_abc(a, b, c)
   calc_abc(a, b, c, operator(:*))
 end
 
 def div_abc(a, b, c)
   calc_abc(a, b, c, operator(:/)) unless b ==0 or c == 0
 end
 
 def power_abc(a, b, c)
   calc_abc(a, b, c, operator(:**))
 end
 
 plus_abc(1, 2, 3) # => 6
 minus_abc(10, 5, 3) # => 2
 mul_abc(4, 5, 6) # => 120
 div_abc(12, 3, 2) # => 2
 power_abc(2, 2, 2) # => 16


ついでに引数をいくつでも取れるようにすれば
より汎用性が高まる

 def calc_abc(*i, op)
   op[i]
 end
 
 def operator(op)
  lambda do |x|
    mem = x.shift
    until x.empty?
      mem = mem.send(op, x.shift)
    end
    mem
  end
end

 def plus_abc(*i)
   calc_abc(*i, operator(:+))
 end
 
 def minus_abc(*i)
   calc_abc(*i, operator(:-))
 end
 
 def mul_abc(*i)
   calc_abc(*i, operator(:*))
 end
 
 def div_abc(*i)
   calc_abc(*i, operator(:/))
 end
 
 def power_abc(*i)
   calc_abc(*i, operator(:**))
 end
 
 def join_abc(*i)
   calc_abc(*i, operator(:join))
 end
 class Fixnum
   def join(other)
     self * 10 + other
   end
 end
 
 plus_abc(1, 2, 3, 4, 5) # => 15
 minus_abc(10, 5, 3, 2) # => 0
 mul_abc(4, 5, 6, 7, 8, 9) # => 60480
 div_abc(12, 3, 2) # => 2
 power_abc(2, 2, 2, 2) # => 256
 join_abc(1, 2, 3, 4, 5) # => 12345


高階関数の別の例を示そう
配列の各要素を倍にするメソッドと
配列の各要素を大文字にするメソッドを考える

 def multi_list(list)
   if list.empty?
     []
   else
     data = list.pop
     multi_list(list) << data * 2
   end
 end
 
 def upper_list(list)
   if list.empty?
     []
   else
     data = list.pop
     upper_list(list) << data.upcase
   end
 end
 
 multi_list([1, 2, 3, 4]) # => [2, 4, 6, 8]
 upper_list(['cat', 'dog', 'horse']) # => ["CAT", "DOG", "HORSE"]

これらの制御構造はそっくりで
dataに対する演算のところだけが違う
だからこの演算のところを抽象化できれば汎用メソッドができる

 def mappie(list, op)
   if list.empty?
     []
   else
     data = list.pop
     mappie(list, op) << op[data]
   end
 end

このメソッドを使えば各メソッドは簡潔に書ける

 def multi_list(list)
   mappie(list, lambda { |x| x * 2 })
 end
 
 def upper_list(list)
   mappie(list, lambda { |x| x.upcase })
 end
 
 multi_list([1, 2, 3, 4]) # => [2, 4, 6, 8]
 upper_list(['cat', 'dog', 'horse']) # => ["CAT", "DOG", "HORSE"]


先の例と同様に
mappieに渡す手続きもメソッドにしよう

 def operator(op, *args)
   lambda { |x| x.send(op, *args) }
 end

そうすればメソッドは一層簡潔になる

 def multi_list(list)
   mappie(list, operator(:*, 2))
 end
 
 def upper_list(list)
   mappie(list, operator(:upcase))
 end
 
 multi_list([1, 2, 3, 4]) # => [2, 4, 6, 8]
 upper_list(['cat', 'dog', 'horse']) # => ["CAT", "DOG", "HORSE"]

メソッドの追加も容易だ

 def reverse_list(list)
   mappie(list, operator(:reverse))
 end
 reverse_list(%w(elppa odnetnin elgoog)) # => ["apple", "nintendo", "google"]

このように高階関数を使えば
任意の抽象手続きを定義できるようになり
その結果下流コードが簡潔になって
それを利用するユーザにやさしいプログラムができる