ラベル 無名関数 の投稿を表示しています。 すべての投稿を表示
ラベル 無名関数 の投稿を表示しています。 すべての投稿を表示

2012年2月21日火曜日

Ruby のネストしたメソッドと、変数のスコープ

0. 目次

  1. JavaScript, Haskell, Python でネストした関数を定義する
  2. Ruby でネストしたメソッドは定義できるが、変数のスコープに注意
  3. 内部スコープから、外部スコープを参照できない
  4. トップレベルに定義したメソッドの所属先は Object
  5. ネストしたメソッドの所属先は、外側のメソッドと同じクラス

 

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 Report3.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 ... )シンタックスシュガーであるとも言える。

Currying - HaskellWiki

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]

ただし、[] による手続きオブジェクトの呼び出しは、配列の要素を参照するメソッドと被るので、あまり好きではない。

全て無名関数で書くなら、

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

class NameError は、

未定義のローカル変数や定数を使用したときに発生します。

_sum メソッドを呼び出したとき、そのスコープに変数 x は存在しない。

他の言語のように、ネストした関数と同じつもりで書くことはできない。

 

3. 内部スコープから、外部スコープを参照できない

内側のメソッドから、外側のメソッドの変数を参照できなかった。理由は、Ruby のスコープの仕様による。

4048687158
メタプログラミングRuby によると、

Java や C# と言った言語には、「内部スコープ」から「外部スコープ」の変数を参照できる仕組みもあるが、Ruby にはこうした可視性の入れ子構造は存在せず、スコープはきちんと区別されている。新しいスコープに入ると、束縛は新しい束縛と交換される。(p113)

プログラムがスコープを切り替えて、新しいスコープをオープンする場所は 3 つある。

  • クラス定義
  • モジュール定義
  • メソッド呼び出し

… この3つの境界線は、class, module, def といったキーワードで印が付けられている。3つのキーワードはそれぞれスケープゲートのように振る舞う。(p115)

クラスやモジュールの定義のコードはすぐに実行される。一方、メソッド定義のコードは、メソッドを呼び出したときに実行される。(p116)

メソッドに関連した箇所を、まとめると、

  1. キーワード def によるメソッドの定義は、メソッドの呼び出しのときに実行される。
  2. メソッドの呼び出しにより、新しいスコープに切り替わる。
  3. つまり、ネストしたメソッドが実行されるとき、新しいスコープに切り替わり、外側のスコープの変数を参照できない。

 

フラットスコープ

ちなみに、class, module, def によるスコープの制約を超えて、変数を参照したい場合、

を利用する。これを

「フラットスコープ」

と呼ぶそうだ。( メタプログラミングRuby, p118)

 

4. トップレベルに定義したメソッドの所属先は Object

先ほどの sum メソッドは、トップレベルに定義した。Ruby では、トップレベルに定義するメソッドは、関数定義のように見える。Java のように、main メソッドを持つ、特別なクラスを作成する必要がない。

では、トップレベルに定義したメソッドは、どこに所属するのだろう?

その手がかりは、特殊な変数である self

「現在のメソッドの実行主体」

変数と定数 より)

にある。

4048687158
メタプログラミング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即ちmainObjectのインスタンスなので、トップレベルでもObjectのメソッドが呼べるということになる。そして ObjectにはKernelというモジュールがインクルードされており、そこで pputsなどの「関数風メソッド」が定義されている(図10)。だからこそトップレベルでもpputsが呼べるのだ。

(Kernel)
図10: mainObjectKernel

Rubyが抱える課題、NaClの前田氏が講演 - @IT

多くの言語では、トップレベルの関数(メソッド)にはレシーバーがないが、Rubyではトップレベルで関数を定義すると、それは暗黙的にmainオブジェクトの”関数的”メソッドとして定義される。Rubyではすべてがメソッドで、動的束縛を行うためメソッド探索のコストがかかる。

トップレベルに定義するメソッドは、Object クラスのメソッドとなる。Ruby のトップレベルは、Java における main メソッドを持ったクラスの中のようなもの。

 

5. ネストしたメソッドの所属先は、外側のメソッドと同じクラス

a. Ruby を構成する基本的な要素

ところで、Ruby では「オブジェクト」が、プログラムの基本的な要素として扱われる。

オブジェクト によると、

Ruby で扱える全ての値はオブジェクトです。 Ruby のオブジェクトに対して可能な操作はメソッド呼び出しのみです。あるオブジェクトが反応できるメソッドは、そのオブジェクトが所属するクラスによって一意に決定します。

Ruby に純粋な関数は存在しない。関数のように見える記述も、全てオブジェクトのメソッド。

クラスもオブジェクトとして扱われる。先ほど、Person クラスの定義の中で、self を出力したとき、クラス名である

Person

が表示された。このとき、実行主体はクラスを表すオブジェクト。Java のように「静的なクラス定義と、実行中の動的なオブジェクトが別の世界にある」イメージとは趣が違う。

Person クラスは、Class クラスのインスタンス。self に対して、Object#class を呼び出せば、所属するクラスを確認できる。詳しくは、以下を参照。

4048687158
メタプログラミングRuby, p46、

オブジェクトの内部には、インスタンス変数とクラスへの参照があるだけだ。…メソッドは、オブジェクトじゃなくて、クラスにあるんだ。…

クラスはオブジェクトである。… クラスは 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_classdefでメソッドを定義するときに対象となるクラスを表す。普通のクラス定義文ではselfがそのクラスになるのでruby_class == selfだが、トップレベルやevalinstance_evalという特殊なメソッドの途中では self != ruby_classになることがある。  (同上より)

ruby_scopeはローカル変数スコープを表すスタックである。メソッド・クラス定義文・モジュール定義文・特異クラス定義文などが独立したスコープだ。(同上より)

 

c. トップレベルのネストしたメソッドは、Object クラスに所属する

上記から sum メソッドについて考えると、

  1. トップレベルに定義した sum メソッドが評価されることにより、self は main で、メソッドの定義先は Object となる。
  2. 「メソッドの評価は、すべてそのメソッドのコンテキストで評価される。」よって、ネストしたメソッドを評価するとき、外側のメソッド定義先クラス ruby_class は変わらないが、キーワード def により、ローカル変数スコープ ruby_class が刷新されるのではないか?
  3. つまり、ネストしたメソッド _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 クラスに定義されている。

Object#methods

そのオブジェクトに対して呼び出せるメソッド名の一覧を返します。このメソッドは public メソッドおよび protected メソッドの名前を返します。

上記のメソッドでは、private メソッドは含まれないことに注意。Object#methods 以外に、以下のメソッドが定義されている。

Module#instance_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

 

参考記事

参考サイト

参考書籍

4048687158
メタプログラミングRuby, p110

ブロックは単体では実行できない。コードを実行するには、ローカル変数、インスタンス変数、self といった環境が必要になる。…

ブロックを定義すると、その時その場所にある束縛を取得する。ブロックをメソッドに渡すときは、その束縛も一緒に持っていく。

2011年3月8日火曜日

Scala の List クラスで map, filter, foldRight と シーケンス内包表記

1. Scala の覚えやすそうな点と、すぐに忘れそうな仕様

Scala に興味を持った理由は、「今からでも遅くない これから始めるScala(前編)」 で挙げられていた以下の特徴による。

  • オブジェクト指向と関数型言語の特徴を組み合わせています。Scalaでは、関数をオブジェクトとして扱うことができますし、高階関数も利用できるなど、関数型言語からさまざまな特徴を取り入れています。
  • Scalaは静的型付け言語ですが、型推論によって型の記述を省略することができます。変数や関数の宣言時に、コンパイラが型を推論してくれるため、型の指定をいちいち行わなくてもよいのです。
  • Trait(トレイト)という、実装を持つことができるインターフェイスを利用して、Mix-Inができます
  • 暗黙の型変換(implict conversion)などを利用して、DSL(ドメイン固有言語,Domain-Specific Language)を作成しやすくなっています。
  • マルチコア時代を意識した、分散処理ライブラリ(Actorライブラリ)が用意されています。

(太文字は引用者による)

久しぶりに本を購入して読んでいる。しかし、いつもの如く頭に入らないし、覚えたものは身に付かず抜けていく。

Scala は、統一されていて覚えやすい仕様もあれば、「何だかごちゃごちゃしていて忘れること間違いなし」と感じるところもある。結果として見やすくなるコードのために覚えておくルールの多さに圧倒されてしまったり。。 (+_+)

Guido van RossumNeopythonic: Scala? で次のように述べている。

Perhaps my biggest disappointment in Scala is that they have so many rules to make it possible to write code that looks straightforward, while being anything but -- these rules all seem to have innumerable exceptions, making it hard to know when writing simple code will work and when not.

 

a. Haskell の関数と似た名前のメソッド名

Scala は関数型の特徴を持ち合わせている。しかも型推論をしてくれる。しかし、「Haskell ではこう書けるのに、何で Scala はだめなんだろう?」と疑問を抱くことがある。例えば、

  • メソッドの引数は必ず型を指定しないといけない。

Haskell で関数を定義するとき、引数の型を省略しても型を推論してくれる。

Prelude> let add x y = x + y

Scala では引数の型を指定しなくてはならないので面倒。 (+_+)

scala> def add(x:Int, y:Int) = x + y

この辺り、オブジェクト指向と折衷しているのが理由なのだろうか?スクリプトを書くのにお手軽に使えると期待していただけにちょっと残念。

ところで、Haskell との比較で言えば、関数名が似ているのはなぜだろう?例えば、Haskell でリスト操作に不可欠な、

  • map
  • filter
  • foldr

(cf. Prelude, Data.List)

各々の言語でこれに対応した関数、メソッドを比較すると、

Python map filter reduce 2.1 組み込み関数
Ruby collect, map find_all, select inject Enumerable
Scala map filter foldRight, :\ scala.collection.immutable.List 

これを見ると Haskell の関数名に Scala が一番似ていて、次に Python 。Ruby のメソッド名はかなり異なる。

scala.collection.immutable.List  には、上記以外にも類似したメソッドが見受けられる。もちろん、Python でも関数型プログラミングを意識したモジュールには似た名前の関数が定義されているけれど。

Scala と Haskell の言語設計者は何か関係でもあるんだろうか?

Scala の設計者である Martin OderskyThe Origins of Scala で次のように述べている。

Bill Venners: How did Scala come about? What is its history?

Martin Odersky: Towards the end of my stay in Zurich, around 1988/89, I became very fond of functional programming. So I stayed in research and eventually became a university professor in Karlsruhe, Germany. I initially worked on the more theoretical side of programming, on things like call-by-need lambda calculus. That work was done together with Phil Wadler, who at the time was at the University of Glasgow. One day, Phil told me that a wired-in assistant in his group had heard that there was a new language coming out, still in alpha stage, called Java. This assistant told Phil: "Look at this Java thing. It's portable. It has bytecode. It runs on the web. It has garbage collection. This thing is going to bury you. What are you going to do about it?" Phil said, well, maybe he's got a point there.

The answer was that Phil Wadler and I decided take some of the ideas from functional programming and move them into the Java space. That effort became a language called Pizza, which had three features from functional programming: generics, higher-order functions, and pattern matching. Pizza's initial distribution was in 1996, a year after Java came out. It was moderately successful in that it showed that one could implement functional language features on the JVM platform.

(太字斜体は引用者による)

一緒に仕事したという Philip Wadler と言えば、The Haskell 98 Language Report Preface に名前があるように、Haskell の言語仕様を決める委員会の editor であり、Glasgow Haskell Compiler 発祥の University of Glasgow で教授をしていた人物。「モナドって何?」と調べていれば行き当たるであろう Monads for functional programming の著者。

… ということは、関数名が Haskell に似ていたとしても、あながち偶然でもないということだろうか?

 

b. 他によく似た名前の関数を持つ言語は?

Scala が影響を受けた言語について調べてみると、

Java, Pizza, Haskell … と順にある。

Fold (higher-order function) - Wikipedia には fold 系の関数の各言語の一覧が載せられている。この中で Scala の foldRight に似ている名前を持つ言語は Ocaml と Scheme 。それぞれ関数名は fold_rightfold-right

  • List より : map, filter, fold_right
Scheme は、

うーん、こちらの方が名前は似てるなぁ。結局、どの言語が一番影響を与えて関数名が決まったんだろう???

 

それはさておき、リスト操作における重要な関数である、

で練習した関数を Scala でも手を動かして覚えることに。 (+_+)

 

c. Meadow で Scala のスクリプトを実行

… とその前に、「オブジェクト指向プログラマが次に読む本 -Scalaで学ぶ関数脳入門」には Scala の実行方法として以下の 3 つが挙げられていた。

  • シェルで対話的に実行する
  • スクリプトを記述して実行する
  • プログラムを記述して実行する

対話的というのは REPL のことで、ちょっと試したいときに使う。それ以外は main メソッドを持つシングルトンオブジェクトを書いてコンパイルして実行しないといけない思っていた。その中間に位置する実行方法があったのに気づかず。。 (+_+)  これからはスクリプトとして書いて実行してみよう。

( cf. Getting Started with Scala の Script it! )

以前、エディタで Scala を実行する方法をいくつか見た。

やはり、Meadow を使っていこうかな。

方法は、

  1. REPL を使うときのようにエディタに書いて
  2. M-x scala-run-scala を実行
  3. C-c C-l でスクリプトを実行。

( cf. EmacsでつくるScala開発環境 前編(scala-mode) - tototoshiの日記 )

 

2. map

まずは、リストの要素を 2 倍にすることから。

Haskell で書くなら、

Prelude> let l = [1..5]
Prelude> map (\x –> x * 2) l

セクションを利用すると、

map (*2) l

 

a. map メソッドにおける型変数

map メソッドの型を見ると、(scala.collection.immutable.List)

def map [B] (f: (A) ⇒ B) : List[B]

メソッドで型変数 B が宣言されている。

なぜこの型変数 B が書かれているのかと言えば、List クラスの型変数 A

class List [+A] …

はリストの要素の型を表しており、map メソッドの引数で要素を変換する関数、

f: (A) ⇒ B

により、元の要素の型とは異なる型になる可能性があるため。(型変数の前に `+’ が付いている意味は横に置いておく。)

この辺り、Java で似たような実装を試したときを参考に。

とりあえず、map メソッドを使う分には詳細を理解する必要はない。

 

b. 無名関数と引数の型を省略

Scala で無名関数を定義するときは、以下のような形式。

(引数:型) => 式

例えば、数値が与えられたら 2 倍する関数を定義するなら、

val f = (x:Int) => x * 2

Haskell で

–>

と書くところを

=>

とすることに注意。また、面倒だけれど、無名関数の引数は型を指定しなければならない。

リストの要素を 2 倍するには、

val l = List(1,2,3,4,5)
l.map((x:Int) => x * 2)

(以下、上記変数 l を使う。)

map 関数の引数として無名関数を与えると、引数の型をしなくてもよい。

l.map((x) => x * 2)

これは変数 l が

List[Int]

という型であり、リストの要素が Int 型だとわかることを利用して、先ほどの map メソッドに当てはめると、

def map [B] (f: (Int) ⇒ B) : List[B]

となり、map メソッドの第1引数の関数の引数の型を推論できるため。

ついでに、引数が 1 つだと ( ) も省略できる。

l.map(x => x * 2)

 

c. 無名関数の引数の省略

また、Haskell のセクションに似た書き方も用意されている。無名関数の引数は、

_

で代用できるので、

l.map(_*2)

 

d. メソッド呼び出し . の省略

それから、メソッドの呼び出しである . も省略できる。

l map (_*2)

シンプルに書けたけれど、ここまでの道程が長い。。(+_+)

 

e. 変更可能なリストを利用する場合

ところで、上記のリストは変更不能な (imutable) リストを利用した。 Scala ではこの使用が推奨されている。

これに対して、変更可能なリストを使いたい場合は、

  • scala.collection.mutalble

を使う。

scala.collection.mutable.LinkedList の要素を追加するメソッド は

def :+ (elem: A) : LinkedList[A]

これを利用すると、

import scala.collection.mutable.LinkedList

var result = LinkedList[Int]()
for (e <- l){
  result = result.:+(e * 2)
}
println(result)

変更可能な変数は var を使って宣言。

result 変数を更新している箇所は、以下のように置き換えることができる。

result = result :+ e * 2

更に省略するなら、

result :+= e * 2

慣れないと、なぜこういう風に書けるのかわからなくなってしまうけれど。 ^^;

 

f. シーケンス内包表記

The Scala Language Specification には、

For Comprehension

と書かれており、A Tour of Scala では Sequence Comprehensions とある「シーケンス内包表記」。

List comprehension - Wikipedia に for と yield を使った説明がされていることより、

「リスト内包表記」

と同じような機能を持つようだ。これを使うなら、

for (e <- l) yield e * 2

Ruby の yield や、Python の yield と同じ予約語名なので頭の中が混乱してきた。。 (+_+)

 

3. filter

次に、filter メソッド。型を見ると、引数に述語 (predicate) を与えれば良いことがわかる。

(cf. scala.collection.immutable.List, Haskell の Prelude 散策 (2) - 述語)

def filter (p: (A) ⇒ Boolean) : List[A]

map メソッドと同じように徐々に省略できるところを省いていくと、

val l = List(1,2,3,4,5)
l.filter((x:Int) => x > 3)
l.filter((x) => x > 3)
l.filter(x => x > 3)
l.filter(_>3)
l filter (_>3)

シーケンス内包表記では、

for (e <- l; if e > 3) yield e

( ) を使わずに { } で書くと ; も省略できる。

for { e <- l
      if e > 3
} yield e

他の言語における for とちょっと違うので、混同しないように注意が必要。

 

4. foldRight

最後に、リストの値を集約する foldRight メソッド。(scala.collection.immutable.List)

def foldRight [B] (z: B)(f: (A, B) ⇒ B) : B

map, filter メソッドと違い引数が 2 つ。しかも、それぞれの引数が独立しているように見える括弧の書き方。

以下のようなシグニチャでないことに注意。

def foldRight [B] (z: B, f: (A, B) ⇒ B) : B

各々の引数を括弧で括る書き方は、Scala におけるカリー化の手段。引数のリストがあると言えばいいのかな?

カリー化とは、Chapter 8. Functional Programming in Scala によると、

… currying transforms a function that takes multiple parameters into a chain of functions, each taking a single parameter.

In Scala, curried functions are defined with multiple parameter lists,

def cat(s1: String)(s2: String) = s1 + s2

Of course, we could define more than two parameters on a curried function, if we like.

(太字は引用者による)

メソッドの引数が複数の括弧 ( )  で囲まれている様は、「複数の仮引数のリストを持っている」と言えば良いみたい。つまり、メソッドの引数の括弧が一つというのは、引数の書き方としては特殊な一例に過ぎないと。括弧ごとにメソッドの呼び出しを行うことができ、残りの引数を順に与えていき、最終的に全体の関数と同じ関数の連鎖が生まれる。

foldRight の場合で言えば、

リスト.foldRight(値)

により、foldRight の第 2 引数を受け取る関数が生成される。

では、foldRight を使って、要素を全て足す計算をしてみる。先ほどと同じく、徐々に省略した書き方をすると、

val l = List(1,2,3,4,5)
l.foldRight(0)((x:Int, y:Int) => x + y)
l.foldRight(0)((x,y) => x + y)
l.foldRight(0)(_+_)
(l foldRight 0)(_+_)

 

a. 別名 :\

foldRight と同じ機能のメソッドとして :\ がある。

def :\ [B] (z: B)(op: (A, B) ⇒ B) : B

なぜこんな形のメソッドがわざわざ定義してあるかと言えば、以下の foldr を説明した図を見れば一目瞭然。

http://en.wikipedia.org/wiki/Fold_(higher-order_function)

Fold (higher-order function) - Wikipedia, the free encyclopedia via kwout

上記と同じ計算をするなら、

l.:\(0)(_+_)
(l :\ 0)(_+_)

これまた慣れないと読みずらい。

 

b. foldLeft と /:

foldRight はリストを右 (末尾) から左 (先頭) へと計算を進めるのに対して、foldLeft は左 (先頭) から右 (末尾) へと計算を行なう。

(l foldRight 100)(_-_)    // (1-(2-(3-(4-(5-100))))) => -97 (l foldLeft 100)(_-_)     // (((((100-1)-2)-3)-4)-5) => 85

foldLeft の別名は /: 。foldRight の :\ とは対照的。

(l :\ 100)(_-_)
(100 /: l)(_-_)

: で終わるメソッド名は右結合なので、第1引数を左に置く。この例外的なルールにより、左から計算を畳み込んでいく様をコードから連想できるようになっている。

. によるメソッドの呼び出しで書くなら、

l./:(100)(_-_)

こういう例外的なことが存在することが良いことなのか悪いことなのか。うーん…

 

c. reduceRight とreduceLeft

Haskell では foldr の第2引数がなく、リストの要素のみに二項演算子を適用していく関数名は foldr1 。

Prelude> foldr1 (+) [1..5]
15

scala には foldRight1 というメソッドはなく、reduceRight が代わりに存在する。

l reduceRight (_+_)

当然ながら、対照的な reduceLeft もあり。

l reduceLeft (_+_)

 

d. map , filter を foldRight で書き直し

次に、map メソッドで要素を 2 倍した関数を foldRight で書き直したい。

Haskell では以下のように書ける。

Prelude> foldr ((:).(*2)) [] [1..5]
[2,4,6,8,10]

Scala では空リストを Nil で表現する。

scala.collection.immutable.Nil

object Nil extends List[Nothing] with Product

これを利用して、以下のように書けばいいのかと思いきや、エラーとなる。 (+_+)

(l foldRight Nil)(_*2::_)

エラーがでないようにするには、foldRight の第1引数の型を明示する。

(l foldRight List[Int]())(_*2::_)

理由は、Programming in Scala (p326) によると、

Generally, when tasked to infer the type parameters of a polymorphic method, the type inferencer consults the types of all value arguments in the first parameter list but no arguments beyond that.

(太字は引用者による)

メソッドの型推論は、仮引数のリストのうち、最初の実引数を調べることによって行われるようだ。

foldRight の型で言えば、

def foldRight [B] (z: B)(f: (A, B) ⇒ B) : B

引数 z を元に型推論が行われる。

Nil の型について :: メソッドを使って調べてみると、

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

scala> 1::2::3::Nil
res1: List[Int] = List(1, 2, 3)

scala> "hoge"::"piyo"::"fuga"::Nil
res2: List[java.lang.String] = List(hoge, piyo, fuga)

:: メソッドに与える引数の型により、返り値の型が決まる。

ところで、foldRight の第1引数 z の型変数は B と書かれており、リストの要素から型を決めることができない。仮引数 z に Nil を与えても型が決まらない。だから、型を指定しなければならないということかな。

(l foldRight List[Int]())(_*2::_)

この場合の foldRight の型は以下のような感じ。

def foldRight(z: List[Int])(f: (Int, List[Int]) ⇒ List[Int]) : List[Int]

ごちゃごちゃしていて頭混乱するなぁ~。

 

map の定義

map 関数を foldRight メソッドを使って定義するなら、

def mymap[A,B](l: List[A], f:(A) => B) :List[B] = (l :\ List[B]())(f(_)::_)

Haskell では、型を明示しなくても推論してくれるのでシンプルに書ける。

mymap f = foldr ((:).f) []

 

filter の定義

filter メソッドを使った 3 以上要素を抽出する関数を :\ メソッドで書き直すなら、

(l :\ List[Int]())((x,y) => if (x>3) x::y else y)

filter メソッドを :\ メソッドを使って定義するなら、

def myfilter[A](l: List[A], p: (A) => Boolean) :List[A] = 
  (l :\ List[A]())((x,y) => if (p(x)) x::y else y)

Haskell なら、

myfilter p = foldr (\x y -> if p x then x:y else y) []

 

5. その他

a. Range で数値のリストを生成

ついでなので、範囲を表わす scala.collection.immutable.Range を使い、1 から 10 まで足してみる。

((1 to 10) :\ 0)(_+_)
(1 to 10) reduceRight (_+_)
(1 to 10) sum

 

b. ブロックを使って副作用

純粋関数型でないメリットは、変数の更新や副作用を簡単に書けること。( それがバグの温床にもなるけれど… )

ブロック { } を foldRight で用いると計算途中の値を出力できたりする。ブロックは最後の式の値が返されるので、以下のように書くと、計算途中の変数の値を出力可能。

((1 to 5) :\ 0){ (x,y) =>
  println(y)
  x + y
}

結果は、

0
5
9
12
14
res647: Int = 15

これは Ruby の inject メソッドを書くときに似ている。

puts (1..5).inject(0){ |x,y|
  puts x
  x + y
}

実行すると結果は、

0
1
3
6
10
15

あ~、Ruby の inject メソッドは左から右へと畳み込んでいくんだったか。つまり、Scala で書くと、

(0 /: (1 to 5)){ (x,y) =>
  println(x)
  x + y
}

2008年6月5日木曜日

JavaScript の無名関数に引数を与える

1. 無名関数は名前がないだけの普通の関数

JavaScript で、最初に

「何だ?この書き方は。」

と思ったのが、無名関数を定義し、すぐにその関数を実行する記述。

無名関数と言えば、 JavaScript ではブロックスコープを実現するために用いられる。

 

2. 無名関数の定義

無名関数を定義し、すぐにその関数を呼び出すには、以下のように記述する。

(function(){alert("hoge")})()

無名関数の中で変数を定義すれば、その変数のスコープは関数の中に限定される。そのため、無名関数をブロックと見なすことができる。

上記の無名関数に、引数を与える。

(function(str){alert(str)})("hoge")

引数を二つ与える場合は、

(function(a,b){alert(a+b)})("hoge","piyo")

 

3. 無名関数に引数を与える覚え方

無名関数を定義して、すぐにその関数を呼び出す書き方は一見覚えにくい。しかし、次のようにして頭に入れておくと、忘れることはない。

はじめに () を二つ並べる。

()()
  1. 一つ目の () を関数と見たてる。
  2. 二つ目の () は、その関数の呼出し。引数がある場合、ここに渡す。

次に、無名関数の定義の仕方を思い出す。名前のある関数から、名前を削除すれば、無名関数がの出来上がり。

function(){}

これを最初の () に入れたら、出来上がり。

(function(){})()

 

参考