読者です 読者をやめる 読者になる 読者になる

Ruby の lambda は第一級オブジェクト(だと思う)

JavaScript Ruby

JavaScript の関数は第一級オブジェクト(第一級関数)だとよく言われますね。変数に代入できるし、関数の引数にすることができるし、関数の戻り値にすることもできます。以下のとおりです。

function a() {
  console.log("Hello!");
}

function f(x) {
  x();
}

a();    //=>"Hello!"
var b = a;
b();    //=>"Hello!"
f(b);   //=>"Hello!"

function g() {
  return function () {console.log("Hello, world!");};
}

var c = g();
c();    //=>"Hello, world!"



じつは Ruby の lambda は、上で JavaScript の関数がやっていることをすべて行うことができます。

a = lambda {
  puts "Hello!"
}

def f(x)
  x[]
end

a.call    #=>"Hello!"
a[]       #=>"Hello!"
b = a
b[]       #=>"Hello!"
f(b)      #=>"Hello!"

def g
  return lambda {puts "Hello, world!"}
end

c = g
c[]       #=>"Hello, world!"

h = lambda {
  return lambda {puts "Hello, world!!"}
}

d = h[]
d[]       #=>"Hello, world!!"

j = lambda {|x| x[]}
j[a]      #=>"Hello!"

つまり、変数に代入できるし、lambda の引数にすることができるし、lambda の戻り値にすることもできます。さらに、上の例でわかると思いますが、メソッドの引数にすることもできますし、メソッドの戻り値にすることもできます。

つまり、Ruby の lambda は第一級オブジェクト(第一級関数)なのではないでしょうか。

ちなみに、lambda の呼び出しは、a.call でも a[] でも、いずれでも可能です。後者の方が簡潔ですが、前者の方がいかにも「lambda を呼び出している」という感じで、紛れがないでしょう。上の例では後者流で書いてあります。なお、Ruby[] はじつはインスタンスメソッドです。.[]シンタックス・シュガーなのです(なので、上の例の最終行は、j.[](a) とも書けます)。ここでも、Ruby の一貫性がよくわかります。

なお、proc も lambda とほぼ同じ使い方ができます。ただし、上の例なら h = lambda {... のところだけ修正が必要です(return を消す)。


じつは、Rubyメソッドでも似たようなことがやれます。メソッドもオブジェクトなのです(過去記事)。

def a
  puts "Hello!"
end

def f(x)
  x[]
end

a       #=>"Hello!"
b = Object.new.method(:a)
b[]     #=>"Hello!"
f(b)    #=>"Hello!"

def g
  def e
    puts "Hello, world!"
  end
  return Object.new.method(:e)
end

c = g
c[]     #=>"Hello, world!"

p b     #=><Method: Object#a>

ただし、「無名メソッド」というのはないので、メソッドの返り値としてメソッド・オブジェクトを使うのは(上のように、出来ないことはないけれど)ちょっと苦しいです*1。だから、Rubyメソッドを第一級オブジェクトと呼ぶことはたぶんできないでしょう。いずれにせよ、メソッドをこのように使うのは、あくまでも可能であるというだけで、あまり趣味がいいとは思えません(有用な例はリファレンス・マニュアルを参照)。だから、このように多少面倒になっているのだと思います。そのような目的には、lambda を使えと。

*1:スコープとしても問題があります。メソッドをネストしても、メソッドとして新しい名前空間は作られません。上の場合だと、Object.new.method(:e) の :e を :a に替えても使えてしまいます。