0. 目次
- JavaScript, Haskell, Python でネストした関数を定義する
- Ruby でネストしたメソッドは定義できるが、変数のスコープに注意
- 内部スコープから、外部スコープを参照できない
- トップレベルに定義したメソッドの所属先は Object
- ネストしたメソッドの所属先は、外側のメソッドと同じクラス
1. JavaScript, Haskell, Python でネストした関数を定義する
2 つの値を足し合わせる関数を定義したい。
a. JavaScript
JavaScript で書くなら、
function sum(x, y){ return x + y; }; sum(1, 2); //=> 3
JavaScript では、ネストした関数を定義できる。
JavaScript Reference - MDN の入れ子の関数とクロージャ によると、
関数の内部に関数を入れ子にする事ができます。入れ子にされた (内側の) 関数は、それを含む (外側の) 関数に対してプライベートです。それは同時に クロージャ を形成します。
よって、次のように sum 関数を書くことができる。
function sum(x){ function _sum(y){ return x + y; }; return _sum; }; sum(1)(2); //=> 3
sum 関数を利用するには、関数の呼び出しを 2 回行い、一つづつ引数を与える。
上記の sum 関数の定義は、最初に定義した関数をカリー化したもの。
カリー化 - Wikipedia とは、
複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること。
次に、ネストした関数を無名関数に変更。JavaScript で無形関数を定義するには、関数定義から名前を削除すれば良い。
JavaScript Reference - MDN の 関数を定義する によると、
function name([param[, param[, ... param]]]) { statements }
name
- 関数名。省略する事ができ、その場合関数は無名関数と見なされます。
関数を定義するとき、`function’ と書くのは長くて面倒。しかし、無名関数の作り方は、素直でわかりやすい。
function sum(x){ return function(y){ return x + y; } } sum(1)(2); //=> 3
sum 関数を、全て無名関数で定義して、実行するには、
(function(x){ return function(y){ return x + y; } })(1)(2); //=> 3
b. Haskell
Haskell で sum 関数を定義するなら、
sum' x y = x + y main = print $ sum' 1 2 -- 3
Haskell で関数をネストするには、let 式か、where 節を用いる。
sum' x = sum'' where sum'' y = x + y
無名関数の書き方は、The Haskell 98 Language Report の3.3 カリー化された適用とラムダ抽象 によると、
ラムダ抽象は \ p1 ... pn -> e と書く。ここで、pi はパターンである。
JavaScript のように、関数定義から名前を削除したら、無名関数になる訳ではない。最初に書いた sum’ 関数の定義は、
と呼ばれる。無名関数を使った書き方と、同等であることが保証されている。
ネストしている sum’’ 関数を、無名関数に変更すると、
sum' x = \y -> x + y
引数 2 つの定義よりも、上記の方が、Haskell では本質的な形。
理由は、Haskell - Wikipedia によると、
Haskell において、2つの引数を持つ関数は、1つの引数をとり「1つの引数をとる関数」を返す関数と同義である。…
f x = ... x ...
は、f = (\x -> ... x ... )
のシンタックスシュガーであるとも言える。
In Haskell, all functions are considered curried: That is, all functions in Haskell take just single arguments.
このため、引数 2 つの関数束縛と、カリー化した定義は、関数を適用するとき、同じように書ける。この点、Haskell の記述はシンプルで使いやすい。
ところで、Haskell - Wikipedia によると、
Haskell の定義は変数に束縛するのが定数であるか関数であるかにかかわらず、「変数 = 値」という一貫した形でも定義できる。
JavaScript でも、関数を定義するときに、無名関数を変数に代入する形式で書くことができる。
var sum = function(x, y){ return x + y; };
Haskell で、sum 関数を全て無名関数で定義し、関数を適用するには、
main = print $ (\x -> \y -> x + y) 1 2
c. Python
Python でも、sum 関数を定義してみる。
def sum(x, y): return x + y print sum(1, 2) #=> 3
Python も、ネストした関数を定義できる。
実行モデル — Python 2.6ja2 documentation によると、
プログラムテキスト中に名前が出現するたびに、その名前が使われている最も内側の関数ブロック中で作成された 束縛 (binding) を使って名前の参照が行われます。
ブロック(block)は、Python のプログラムテキストからなる断片で、一つの実行単位となるものです。モジュール、関数本体、そしてクラス定義はブロックです。…
ある名前がコードブロック内で使われると、その名前を最も近傍から囲うようなスコープ (最内スコープ: nearest enclosing scope) を使って束縛の解決を行います。こうしたスコープからなる、あるコードブロック内で参照できるスコープ全ての集合は、ブロックの環境(environment)と呼ばれます。
ある名前がブロック内で束縛されている場合、名前はそのブロックにおけるローカル変数 (local variable) です。ある名前がモジュールレベルで束縛されている場合、名前はグローバル変数 (global variable) です。 (モジュールコードブロックの変数は、ローカル変数でもあるし、グローバル変数でもあります。) ある変数がコードブロック内で使われているが、そのブロックでは定義されていない場合、変数は自由変数(free variable)です。
(装飾は引用者による、以下同じ)
def sum(x): def _sum(y): return x + y return _sum
sum 関数を呼び出すには、引数を一つづつ渡す。JavaScript と同じ。
print sum(1)(2) #=> 3
ただし、無名関数を定義するとき、JavaScript のように関数から名前を削除する、という書き方はできない。よって、以下はエラーとなる。
def sum(x): return def(y): return x + y
無名関数を書くには、ラムダ形式を利用する。
def sum(x): return lambda y: x + y
関数の呼び出し方は、ネストした関数と同じ。
全てラムダ形式で定義し、実行するには、
print (lambda x: lambda y: x + y)(1)(2)
2. Ruby でネストしたメソッドは定義できるが、変数のスコープに注意
Rubyでも、同様に sum メソッドを定義してみる。
def sum(x, y) x + y end p sum(1, 2) #=> 3
他の言語と同じように、ネストしたメソッドを定義することができるだろうか?
def sum(x) def _sum(y) x + y end _sum end p sum(1)(2)
しかし、これは sum メソッドを呼び出した時点で
syntax error
となる。
また、sum メソッドを引数一つにして呼び出した場合、_sum の呼び出しで、
「引数の数が違う」
とエラーがでる。
a. 手続きオブジェクトを使い、外側の変数を参照
これに対処するには、ネストしたメソッドの代わりに、手続きオブジェクトを利用しなければならない。
手続きオブジェクトは、Proc.new で生成するか、もしくは、以下の形式を使う。
lambda{ } または proc{ ]
def sum(x) lambda{|y| x + y} end p sum(1).call(2)
上記では、sum メソッドの呼び出しにより、手続きオブジェクトが返される。手続きオブジェクトに引数を渡して、計算を行わせるには、call メソッドを呼び出す。call メソッドを書くのが面倒なら、
[]
を使えばよい。
p sum(1)[2]
ただし、[] による手続きオブジェクトの呼び出しは、配列の要素を参照するメソッドと被るので、あまり好きではない。
- cf. callを省略する (via Rubyの呼び出し可能オブジェクトの比較(1) - 世界線航跡蔵)
全て無名関数で書くなら、
p lambda{|x| lambda{|y| x + y }}.call(1).call(2) p lambda{|x| lambda{|y| x + y }}[1][2]
やはり、スッキリとした書き方とは思えない。(+_+)
b. ネストしたメソッドから、外側の変数を参照できない
Ruby では、他の言語のように、ネストしたメソッドは定義できないのだろうか?
メソッド定義のネスト によると、
ネストされた定義式は、それを定義したメソッドが実行された時に定義されます。
ドキュメントにこのように書かれているので、定義することはできるようだ。
「実行されたときに定義される」
という性質は、Ruby のクラスとインスタンス変数の関係に似ている。
メタプログラミングRuby , p45 によると、
Ruby ではオブジェクトのインスタンス変数はクラスと何のつながりもない。インスタンス変数は値を代入したときに初めて出現する。
例えば、 以下の Person クラスのインスタンス変数 @name は、name メソッドを呼び出さなければ、存在しない。
class Person def name @name end def name=(name) @name = name end end person = Person.new p person.instance_variables #=> [] person.name = "Tarou" p person.instance_variables #=> [:@name]
ネストしたメソッドは定義できる。そのため、下記のコードを実行しても、エラーがでなかった。
def sum(x) def _sum(y) x + y end end
不思議なことに、ネストされた _sum メソッドは、トップレベルから呼び出すことができる。呼び出した結果、
「メソッド内の x が、ローカル変数でもメソッドとしても定義されてない
という内容のエラーが表示された。
NameError: undefined local variable or method `x' for main:Object
未定義のローカル変数や定数を使用したときに発生します。
_sum メソッドを呼び出したとき、そのスコープに変数 x は存在しない。
他の言語のように、ネストした関数と同じつもりで書くことはできない。
3. 内部スコープから、外部スコープを参照できない
内側のメソッドから、外側のメソッドの変数を参照できなかった。理由は、Ruby のスコープの仕様による。
メタプログラミングRuby によると、
Java や C# と言った言語には、「内部スコープ」から「外部スコープ」の変数を参照できる仕組みもあるが、Ruby にはこうした可視性の入れ子構造は存在せず、スコープはきちんと区別されている。新しいスコープに入ると、束縛は新しい束縛と交換される。(p113)
プログラムがスコープを切り替えて、新しいスコープをオープンする場所は 3 つある。
- クラス定義
- モジュール定義
- メソッド呼び出し
… この3つの境界線は、class, module, def といったキーワードで印が付けられている。3つのキーワードはそれぞれスケープゲートのように振る舞う。(p115)
クラスやモジュールの定義のコードはすぐに実行される。一方、メソッド定義のコードは、メソッドを呼び出したときに実行される。(p116)
メソッドに関連した箇所を、まとめると、
- キーワード def によるメソッドの定義は、メソッドの呼び出しのときに実行される。
- メソッドの呼び出しにより、新しいスコープに切り替わる。
- つまり、ネストしたメソッドが実行されるとき、新しいスコープに切り替わり、外側のスコープの変数を参照できない。
フラットスコープ
ちなみに、class, module, def によるスコープの制約を超えて、変数を参照したい場合、
を利用する。これを
「フラットスコープ」
と呼ぶそうだ。( メタプログラミングRuby, p118)
4. トップレベルに定義したメソッドの所属先は Object
先ほどの sum メソッドは、トップレベルに定義した。Ruby では、トップレベルに定義するメソッドは、関数定義のように見える。Java のように、main メソッドを持つ、特別なクラスを作成する必要がない。
では、トップレベルに定義したメソッドは、どこに所属するのだろう?
その手がかりは、特殊な変数である self
「現在のメソッドの実行主体」
(変数と定数 より)
にある。
メタプログラミングRuby によると、
Ruby のコードはオブジェクト(カレントオブジェクト)の内部で実行される。カレントオブジェクトは self とも呼ばれる。self キーワードでアクセスできるからだ。…
メソッドを呼び出すときは、レシーバが self になる。…
レシーバを明示せずにメソッドを呼び出すと、全て self に対するメソッドの呼び出しになる。 (p63)
自分が Ruby の小さなデバッガになったと考えてみよう。ブレークポイントに当たるまで命令文を次々に渡っていくとする。さて、一息ついて、周りを見てみよう。どんな景色が見えるだろうか?それがあなたのスコープだ。
スコープ一面に束縛があるはずだ。足元をみると、ローカル変数がいくつもある。顔を見上げると、自分がオブジェクトの中に立っていることに気づく。傍にはメソッドとインスタンス変数がいる。ここがカレントオブジェクト、つまり self だ。遠くのほうに定数のツリ-がはっきりと見える。これで自分の位置が把握できた。目を細めてもっと遠くを見ると、グローバル変数がたくさん見える。(p112)
例えば、Person クラスを定義し、class 定義と、メソッドの定義の中で self を出力してみる。
class Person p self #=> Person def initialize(name) p self #=> <Person:0x2252928> @name = name end end tarou = Person.new("Tarou")
クラス定義内では、Person クラスが実行主体となる。メソッド内では、Person クラスのインスタンスが実行主体となる。
先ほどの sum 関数において、同様に self を出力すると、main と表示される。
p self #=> main def sum(x) p self #=> main def _sum(y) x + y end end sum(1)
main とは、第1章 Ruby言語ミニマム によると、
… 実はRubyプログラム実行中はどこにいようと
self
が設定されている。つまりトップレベルにもクラス定義文にもself
があるのだ。例えばトップレベルでも
self
がある。トップレベルのself
は、main
という。なんの変哲もない、Object
クラスのインスタンスだ。…… トップレベルの
self
即ちmain
はObject
のインスタンスなので、トップレベルでもObject
のメソッドが呼べるということになる。そしてObject
にはKernel
というモジュールがインクルードされており、そこでp
やputs
などの「関数風メソッド」が定義されている(図10)。だからこそトップレベルでもp
やputs
が呼べるのだ。
図10:main
とObject
、Kernel
多くの言語では、トップレベルの関数(メソッド)にはレシーバーがないが、Rubyではトップレベルで関数を定義すると、それは暗黙的にmainオブジェクトの”関数的”メソッドとして定義される。Rubyではすべてがメソッドで、動的束縛を行うためメソッド探索のコストがかかる。
トップレベルに定義するメソッドは、Object クラスのメソッドとなる。Ruby のトップレベルは、Java における main メソッドを持ったクラスの中のようなもの。
5. ネストしたメソッドの所属先は、外側のメソッドと同じクラス
a. Ruby を構成する基本的な要素
ところで、Ruby では「オブジェクト」が、プログラムの基本的な要素として扱われる。
オブジェクト によると、
Ruby で扱える全ての値はオブジェクトです。 Ruby のオブジェクトに対して可能な操作はメソッド呼び出しのみです。あるオブジェクトが反応できるメソッドは、そのオブジェクトが所属するクラスによって一意に決定します。
Ruby に純粋な関数は存在しない。関数のように見える記述も、全てオブジェクトのメソッド。
クラスもオブジェクトとして扱われる。先ほど、Person クラスの定義の中で、self を出力したとき、クラス名である
Person
が表示された。このとき、実行主体はクラスを表すオブジェクト。Java のように「静的なクラス定義と、実行中の動的なオブジェクトが別の世界にある」イメージとは趣が違う。
Person クラスは、Class クラスのインスタンス。self に対して、Object#class を呼び出せば、所属するクラスを確認できる。詳しくは、以下を参照。
メタプログラミングRuby, p46、
オブジェクトの内部には、インスタンス変数とクラスへの参照があるだけだ。…メソッドは、オブジェクトじゃなくて、クラスにあるんだ。…
クラスはオブジェクトである。… クラスは Class クラスのインスタンスなのだ。
-
cf. class Class
オブジェクトはクラスに所属し、クラスはメソッドを持つ。逆に言えば、メソッドは必ずどこかのクラスに所属する。よって、ネストしたメソッドも、所属先のクラスがある。
では、ネストしたメソッドは、どのクラスに所属するのだろう?
b. コンテキスト
メソッドの評価 によると、
引数のデフォルト式も含め、すべてそのメソッドのコンテキストで評価されます。
コンテキストとはなんだろう?Ruby のリファレンスマニュアルを読んでいると、「コンテキスト」という言葉を、しばしば見かける。
例えば、Object#instance_eval 。
オブジェクトのコンテキストで文字列 expr またはオブジェクト自身をブロックパラメータとするブロックを評価してその結果を返します。
オブジェクトのコンテキストで評価するとは評価中の self をそのオブジェクトにして実行するということです。
Person クラスに、name 属性と、与えられたブロックを実行するだけのメソッド test を定義する。
class Person attr_accessor :name def test yield end end
このクラスを使い、instance_eval を呼び出すときに、self を出力。
tarou = Person.new tarou.name = "Tarou" tarou.instance_eval{ p self #=> #<Person:0x232d640 @name="Tarou"> p @name #=> "Tarou" }
instance_eval メソッドにおけるブロックの self が、レシーバーのインスタンスとなっている。
次に、test メソッドで self を呼び出したときと、上記を比較してみる。
tarou.test{ p self #=> main p @name #=> nil }
test メソッドのブロックでは、self がトップレベルのオブジェクトである main となる。よって、インスタンス変数 name を参照しても、nil となる。なぜなら、main オブジェクトに name 属性を設定していないため。
同じ内容のブロックでも、コンテキストが変化することにより、評価の結果が異なる。
Rubyの呼び出し可能オブジェクトの比較 (2) - というよりコンテキストの話 - 世界線航跡蔵 によると、
コンテキストとはおおざっぱに言えばローカル変数の状態、self, klassから成る。
ここでローカル変数というのは、参照できる限り外側のスコープも含めた全部だ。selfというのはデフォルトでメソッドを受け取る相手である。Rubyのプログラムにはいつでもselfがいて、擬似変数selfを通じてアクセスできるのであった。
Rubyの呼び出し可能オブジェクトの比較 (3) - なんかklassの話 - 世界線航跡蔵
… Rubyでは、メソッドはselfに定義されると考えたくなるが、そうではない。実はこれが前に名前だけ触れたklassである。klassは正式な用語ではなく、この記事では仮にそう呼ぶというだけである。…
Rubyレベルでは表にあわれないがRuby処理系は内部でここでいうklassを常に保持している。これは「今メソッドを定義したらどこに定義されるか」というクラスである。この存在を考えるとRuby文法の理解がすっきりする。
トップレベルでは"main"と呼ばれるある特定のObjectインスタンスがselfなのに、トップレベルでメソッドを定義するとObjectのインスタンスメソッドになる。defの中に更にdefを書くと、それらのdefを含むクラスのインスタンスメソッドになる。メソッドは「selfに対して」定義されるわけではないのだ。
… トップレベルではselfはmainで、klassはObjectだ。class文に入るとselfは構築中のクラスのClassオブジェクトになり、 klassも同じになる。defの中を評価するときは(メソッドを実行するときは)selfはレシーバーで、klassはレシーバーのクラスである。
また、第14章 コンテキスト によると、
典型的な手続き型言語のイメージだと、手続き呼び出しのたびに、ローカル変数領域や戻る場所など手続き一つの実行のために必要な情報を構造体(スタックフレーム)に格納し、それをスタックに積む。…
… Rubyでは…理由がいろいろあって、スタックはなんと七本もある。…
スタックとして、メソッド定義先クラスを管理する ruby_class 、ローカル変数スコープを管理する ruby_scope などがあるとのこと。
ruby_class
はdef
でメソッドを定義するときに対象となるクラスを表す。普通のクラス定義文ではself
がそのクラスになるのでruby_class == self
だが、トップレベルやeval
、instance_eval
という特殊なメソッドの途中ではself != ruby_class
になることがある。 (同上より)
ruby_scope
はローカル変数スコープを表すスタックである。メソッド・クラス定義文・モジュール定義文・特異クラス定義文などが独立したスコープだ。(同上より)
c. トップレベルのネストしたメソッドは、Object クラスに所属する
上記から sum メソッドについて考えると、
- トップレベルに定義した sum メソッドが評価されることにより、self は main で、メソッドの定義先は Object となる。
- 「メソッドの評価は、すべてそのメソッドのコンテキストで評価される。」よって、ネストしたメソッドを評価するとき、外側のメソッド定義先クラス ruby_class は変わらないが、キーワード def により、ローカル変数スコープ ruby_class が刷新されるのではないか?
- つまり、ネストしたメソッド _sum のコンテキストは、self は main で、メソッドの定義先は Object となる。しかし、外側のメソッドのローカル変数を参照することはできない。
簡単にいえば、外側のメソッドと同じレジーバーのクラスに、ネストしたメソッドが追加される。よって、トップレベルでネストしたメソッドを定義すると、Object クラスのメソッドになる。そのため、トップレベルではレシーバーを省略して _sum メソッドを呼び出すことができた。
例えば、適当にトップレベルにネストしたメソッドを定義し、呼び出しを行なってみる。
def add1(x) def add2(y) y + 2 end x + 1 end p add1(100) #=> 101 p add2(100) #=> 102トップレベルの main オブジェクトに add1, add2 メソッドが定義されたのが分かる。
d. クラス定義におけるネストしたメソッドは、そのクラスに所属する
次に、トップレベルでネストしたメソッドを定義した場合と、クラスで定義した場合を比較する。
最初に書いた Person クラスで、ネストしたメソッドを定義し、呼び出してみる。Person#initialize 内に age メソッドを定義。
class Person p self #=> Person def initialize(name, age) p self #=> <Person:0x2201b08> @name, @age = name, age def age p self #=> <Person:0x2201b08 @age=30, @name="Tarou"> @age end end end tarou = Person.new("Tarou", 30) p tarou.age #=> 30
結果、initialize メソッドの内側に定義された age メソッドは、Person クラスのメソッドとして呼び出せた。つまり、ネストしたメソッドは、外側のメソッドが所属するクラスに属する。
ところで、このように書けるメリットはあるのだろうか?あるメソッドを実行したときに、新規にメソッドを追加したり、別のメソッドを上書きして、挙動を変化させることに意味が見いだせれば、使いどころがある。ただし、内側に定義したメソッドにおいて、自由変数を使えないのはデメリットな気がする。
e. ネストしたメソッドの、呼び出し制限の違い
あるオブジェクトに定義されたメソッドを、調べるためのメソッドが、Object, Module クラスに定義されている。
そのオブジェクトに対して呼び出せるメソッド名の一覧を返します。このメソッドは public メソッドおよび protected メソッドの名前を返します。
上記のメソッドでは、private メソッドは含まれないことに注意。Object#methods 以外に、以下のメソッドが定義されている。
そのモジュールで定義されている public および protected メソッド名の一覧を配列で返します。
上記と同じく、private メソッドは含まれな。Module#instance_methods 以外に、以下のメソッドが定義されている。
これらのメソッドを使い、先ほどトップレベルに定義した add1, add2 メソッドを調べてみる。
p self.methods.grep /add1/ #=> [] p self.methods.grep /add2/ #=> [] p self.private_methods.grep /add1/ #=> [:add1] p self.private_methods.grep /add2/ #=> [:add2]
add1, add2 メソッドは、main オブジェクトのプライベートメソッドとして追加されているのが分かる。
これに対して、先ほどの Person クラスに追加した age メソッドについて確認すると、パブリックメソッドとして追加されていた。
p tarou.methods.grep /age/ #=> [:age] p tarou.public_methods.grep /age/ #=> [:age]
トップレベルに定義されたメソッドと、クラス内に定義されたメソッドで、呼び出し制限に一貫性がないのはなぜだろう?
f. ネストしたメソッドを Method オブジェクトとして取り出す
最後に、ネストしたメソッドを、Method オブジェクトとして取り出してみる。
class Method とは、
Object#method によりオブジェクト化されたメソッドオブジェクトのクラスです。
メソッドの実体(名前でなく)とレシーバの組を封入します。 Proc オブジェクトと違ってコンテキストを保持しません。
先ほどの add2 メソッドを Method オブジェクトとして取り出す。
add = self.method :add2 p add.call(100) #=> 102 p add[100] #=> 102
Person クラスの age メソッドも取り出してみる。
tarou = Person.new("Tarou", 30) age = tarou.method :age p age.call #=> 30 p age[] #=> 30
参考記事
参考サイト
参考書籍
メタプログラミングRuby, p110ブロックは単体では実行できない。コードを実行するには、ローカル変数、インスタンス変数、self といった環境が必要になる。…
ブロックを定義すると、その時その場所にある束縛を取得する。ブロックをメソッドに渡すときは、その束縛も一緒に持っていく。
0コメント:
コメントを投稿