lethevert is a programmer このページをアンテナに追加 RSSフィード

22/1/2006 (Sun) 晴れ

[]結論:結局、Javaクロージャを使えるの?

【追記 2008/7/2】とても昔のClosureについて書いた記事が注目を集めてしまったみたいですが、ぜひ、もっと後で書いた次の記事とその関連スレッドの方も確認してみてください。このときよりも、もう少し事情が分かってから書いたものなので、より正確に状況を理解できると思います。それに、私自身、この言葉にはまだ混乱しています・・・

http://d.hatena.ne.jp/lethevert/20070524/p2

----

という問いに対して、そろそろ私の結論を整理しておきます。

これは、将来の自分に対する参照の便のための整理です。

前提知識

前提知識として、こちらの内容を読んで理解しておきます。

Closure & - Wikipedia

id:lethevert:20060119:p1

id:lethevert:20060121:p2

また、Lispという違うパラダイム言語の概念であるものを、適用しようというのだから、それなりに無理があるということも留意しておきます。

クロージャとは?

静的スコープのこと。フリー変数をその変数が字句上で定義された環境に束縛すること。

SchemeがAlgolから取り入れた言語仕様だが、手続きが第一級であるLispにおいて、静的スコープを実現することは他の言語にはない独特の挙動を示すことになり、そのため、特に第一級の手続きが成立している言語における静的スコープのことに限定して、クロージャという用語を使う場合が多い。

(注:以下でクロージャという言葉を使うときは、第一級の手続き、または、第一級のオブジェクトが成立していることを前提とする)

オブジェクト指向との関係

変数や手続きに型を持たない言語において、クロージャオブジェクト等価な機能を持つ。

変数や手続きに型を持つ言語では、等価ではない。なぜなら、オブジェクトのメソッドは異なる型と個数のパラメータと返値を持つことができるが、そのようなものに手続きとして静的な型を与えることは無理であるため。

Javaの内部クラスクロージャか?

静的スコープが成立しているため、クロージャといって差し支えない。

Javaローカルクラスがfinalでない変数アクセスできない制限について

ローカルクラスがfinalでない変数アクセスできないからといって、クロージャではないということにはならない。

ローカルクラスがfinalでない変数アクセスできないのは、ローカルクラスという機能に対する制限であり、静的スコープという機能に対する制限とはいえない。一般に、ソース中のどの場所でどういう種類のシンボルアクセス可能であるかということと、静的スコープが成立するかどうかということは、直接的な関係にはない。

tt 2006/01/23 01:36 1. ↓の説明を見る限り、クロージャ=静的スコープというのは無理があると思います。
http://ja.wikipedia.org/wiki/%E9%9D%99%E7%9A%84%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97
2. Javaの内部クラスは、コンパイラで通常のクラスにコンバートされます。
コンバート後のクラスを他のオブジェクト指向言語で実装すれば、Javaの内部クラスと等価な機能が使用できます。
つまり、Javaの内部クラスがクロージャだというのなら、どんなオブジェクト指向言語でもクロージャを使える事になりませんか。
参考: http://d.hatena.ne.jp/soutaro/20051217/1134852470
3. 型のある言語のクロージャとオブジェクトが等価ではないとありますが、
メソッドの数だけ別々のクロージャを作って返してやれば、オブジェクト相当の機能を持たせる事は可能です。

lethevertlethevert 2006/01/23 08:06
1.については、リンク先にクロージャという言葉はかかれていません。上で紹介している英語のWikipediaの資料を読んでください。それから、Schemeについて、Webや書籍を日本語、英語を問わず、目を通してみることをおすすめします。

2.アセンブラには、関数もオブジェクトもありませんが、「コンバート後」なら何でも使えますよ。手動で変換をかければできるというのは、言語に機能があるということとは違います。

3.それは、型のない言語の場合です。型のある言語の場合、次のような関数の型を決めることが無理です。あと、等価というのは構造体などを使って似せることではありません。
----
makeObject x y
 = aObject
where
 aObject :: String -> (Int -> Int)
 aObject ”multByX” = ¥z = x*z
 
 aObject :: String -> (String -> String)
 aObject ”appendY” = ¥z = y+++z
----

tt 2006/01/23 09:55 1. WikiPediaの該当部分を見る限り、closure=function+environmentと書いてあるように見えますが。
しかもその最後の部分にJavaでclosureをsimulateするという話が出ているので、
Javaではclosureは(直接は)使えないということではないのですか。
2. オブジェクトとクロージャが等価だという話をしておられたので、
オブジェクトとクロージャが等価→クロージャをオブジェクトで表せる→すべてのオブジェクト指向言語でクロージャが使える
ということを主張しておられるのかと。
そうすると、Javaを特別扱いする理由がわかりません。
3. では、等価というのは何なのでしょうか。Schemeでかかれた以下のコードは、
オブジェクトらしく見せるために余計なコードを追加していますが、
それは「似せる」ことにはあたらないのでしょうか。
(define (makeObject x y)
(lambda (method)
(cond ((eq? method ’multByX) (lambda (z) (* x z)))
((eq? method ’appendY) (lambda (z) (append y z))))))

tt 2006/01/23 10:15 3.について、一応Haskellで実装したものを載せます。これは、あなたの基準ではオブジェクトと等価といえますか。
makeObject :: Int -> String -> (Int -> Int, String -> String)
makeObject x y = (¥z->x * z, ¥z->y++z)

main :: IO ()
main = do { let object = makeObject 2 ”abc”
; print ((fst object) 3)
; print ((snd object) ”def”)
}

lethevertlethevert 2006/01/23 22:15
1.wikipediaでsimulateと書かれている点については、本文にも書いたように、クロージャという用語は関数が第一級であるLispの用語ですので、Javaに対して適用するにはそれなりに無理があるわけで、その意味からsimulateという言葉を使っているのではないかと思います。
a closure is an abstraction that combines a function and a special lexical environment bound to that function (scope).
「クロージャは、関数とその関数に束縛された特別な字句上の環境を結合させた抽象概念です。」というところだと思いますが、これは静的スコープをスコープという言葉を使わずに説明するとこうなるのではないですか?

2.オブジェクト指向言語でクロージャが使えるということではなく、オブジェクトがクロージャと同じ機能を(本質的に)持っているという意味です。ただし、言語によって、オブジェクトやクロージャが本質的に提供可能な全ての機能にアクセスできるわけではないですから、現実の言語で等価になっているかは別問題です。

3.Schemeの方は、オブジェクトと言えますよね。Smalltalk-72を見て、クロージャのようだと言っていたのは、要するにそういうことです。
Haskellの方は、構造体を使わないといっているのですから、Tuppleはダメです。Tuppleを使わずに、Schemeのように記述することはできないから、静的型付け言語では、クロージャとオブジェクトが等価でないと言っているのです。

tt 2006/01/24 00:48 1. 静的スコープをスコープという言葉を用いないで…とありますが、本当にそうなりますか?
静的スコープというのは性質で、クロージャはその性質を満たす「もの」だと思っているので、それらが同じになるとは思えないのですが。
今までの文章で、クロージャを静的スコープに置き換えても意味が通りますか。

2. ではJavaでClosureが使える、というのは、同じ機能を持つという意味ですか、等価という意味ですか。

3. Schemeはメソッドのディスパッチを明示的に書かなければならないですがオブジェクトですか。
タプルは単に複数の関数をまとめているだけで、データを保持している訳ではないですが、それでも駄目ですか。
タプルを使わないで書ければオブジェクト、という基準はどこから来たのですか。
それならこれでいかがですか。(今度はEitherが構造体だと言われるような気がしますが)
data Method = MultByX | AppendY

makeObject :: Int -> String -> (Method -> Either (Int -> Int) (String -> String))
makeObject x _ MultByX = Left (¥z->x*z)
makeObject _ y AppendY = Right (¥z->y++z)

main :: IO ()
main = do { let object = makeObject 2 ”abc”
; let (Left m) = object MultByX in print (m 3)
; let (Right m) = object AppendY in print (m ”def”)
}

あと、私もここに書かれている内容を理解しきれていないところがあると思うので、
どこかにこれらの内容を分かりやすく説明しているWebページがあれば教えて下さい。
特に、「クロージャが静的スコープである」ことについて説明しているベージがあるとありがたいのですが。

tt 2006/01/24 01:03 すみません。lethevertさんに訊くばかりで、自分では全く調べていない事に今気付きました。
ここの文章が理解できないのも私の勉強不足が原因だと思いますので、
自分なりに勉強して理解できるようにしたいと思います。
今までお答えいただき本当にありがとうございました。

lethevertlethevert 2006/01/24 08:18
1.実は、記事の本文で、「オブジェクト指向との関係」のところだけ、クロージャを「クロージャが成立する関数オブジェクト」の意味で使っています。それ以外は静的スコープと置き換えても問題ないです。
ところで、LispだけでなくSmalltalkでも、BlockContextとBlockClosureを区別していることを考えると、Lispと同じようなクロージャ概念を持っているようです。
http://www.akademia.co.jp/Smalltalk/SML/archives/SRA.archives/1998-July/002602.html

2.JavaでClosureが使えるというか、Javaの内部クラスがクロージャの性質を満たしているというほうが私の言いたいことに対して適切な表現かな?
いずれにしても、Javaには第一級の関数がないので、Schemeと全く同じクロージャの実装はないのですよ。(もっとも、それはSmalltalkやRubyにしても同じことですけど)

3.クロージャとオブジェクトが等価というのは、機能的に同じことが実現できますねということなので、文法が等しいというわけではないです。
ところで、SICPの第2章の始めの方を読んでみると分かると思いますが、型付けを考えなければ、データ構造と手続きは等価になります。たとえば、cons, car, cdrはクロージャのあるSchemeでは特別なデータ構造を用意しなくても、手続きだけで作り出すことができます。
しかし、型付けを考えると、何らかのデータ構造を定義することなしに、オブジェクトをクロージャで表現することはできなくなります。構造体を使ってはいけませんよというのは、そういうことを言っています。

ところで、クロージャという言葉は、Landinさんが使い始めた言葉なんですかね。
http://www.ice.nuie.nagoya-u.ac.jp/~h003149b/lang/p/funarg/funarg.html
には、以下のように書かれています。
----
LISPでは、関数をFUNCTIONで束縛するかQUOTEで束縛するかで、ユーザが束縛環境か活動環境かを選べるようにしている。 LISPでのFUNCTIONとQUOTEの違いについては、次のように考えるのが有用な比喩になる。 QUOTEは多孔的または開放的に関数をおおっていて、自由変数は現在の環境へと脱出できる。 FUNCTIONは閉鎖的または非多孔的に関数をおおっている (このことから、Landinはクロージャ(閉包)という用語を使っている)。だから、「開」ラムダ式と「閉」ラムダ式と呼ぶ (LISPの関数は通例ラムダ式である)。
----

tt 2006/01/26 12:53 いろいろ調べましたが、どうしても「クロージャ=静的スコープ」だけは納得いきません。
私が探した限りでは、そのような説明をしているところはここしかありませんでした。
「クロージャ=静的スコープ」という説明があるWebページが他にあれば教えていただけませんか。
それが無理ならせめてどの分野でそのような意味として使っているのかを教えていただけませんか。

あと、Martin Fowler氏は、SmalltalkとRubyにはClosureがあるが、Javaの無名クラスはClosureではない、という意見のようです。
http://capsctrl.que.jp/kdmsnr/wiki/bliki/?Closure

lethevertlethevert 2006/01/27 08:38
その前に、静的スコープという言葉をどういう風に理解して、クロージャをどういう風に理解していますか?
どのように納得しないのかを説明していただかないと、解決のヒントを差し上げることも難しいかと思います。

それから、何度も言いますけど、Javaの内部クラスがクロージャかどうかを考えるということは、そもそも無理のある話ですから、さまざまな解釈があるのは当然です。
それから、そのMartinさんのクロージャの理解は、微妙です。あちこちで生まれる誤解の源泉ではないかと思っています。

tt 2006/01/28 10:55 とりあえず私の理解は以下の通りです。
(最初に挙げた静的スコープの説明は、私の考えより範囲が広すぎました。
その点に関しては訂正してお詫びします。)

静的スコープ : 関数内の自由変数の値が、その関数が呼ばれた位置ではなく、定義された位置から決定される、ということ。
例えば、以下のCのプログラムは”1”を表示するので、Cは静的スコープである。
int a = 1;
int func(void) {
return a;
}
int main() {
int a = 2;
printf(”%d¥n”, func());
return 0;
}

クロージャ : 関数のコードと関数を定義した時点の環境を組にしたデータ構造。

静的スコープはコンパイラ言語では普通に使われているので、
lethevertさんの理屈だと、それらの言語はすべてクロージャという事になります。
また、lethevertさんのクロージャの説明に、
「SchemeがAlgolから取り入れた言語仕様だが」とありますが、
この説明だとAlgolでもクロージャが使えてしまう事になります。
私がlethevertさんの説明に違和感を感じるのはここです。

lethevertlethevert 2006/01/28 12:29
まず、(レキシカルクロージャの略称としての)クロージャという言葉が、手続きが第一級であるLispで最初に使われた言葉だということを念頭においておく必要があります。C言語やAlgolは手続きが第一級ではないので、クロージャという言葉を使うことは(通常は)ありません。
そういう意味で、静的スコープとクロージャという言葉は、適用領域に差があることは間違いないと思います。つまり、静的スコープの方が適用領域が広い。(確か、以前、Delphiの関数内関数はクロージャなのかということをちょっと書いた気がしますが、あれも同じことです)

しかし、クロージャという言葉が使用される言語(ここでは、LispとSmalltalk)を考えると、結局、静的スコープとクロージャの意味内容にはほとんど差がないことになります。

クロージャ : 関数のコードと関数を定義した時点の環境を組にしたデータ構造。

この説明は、Lispのレキシカルクロージャの説明としては使えるかもしれませんが、Smalltalkのブロッククロージャの説明に使うにはちょっと難しいのではないかと思います。そもそもSmalltalkは全てオブジェクトで、関数という概念は持っていないはずなので。
それから、データ構造というのがどういうレベルのことを言っているのかが曖昧です。もし言語の表層(?)レベルでのデータ構造という意味ならば、関数のコードや関数を定義した環境に対して個々にアクセスできなければデータ構造とはいえないです。また、(VMなどの)実行モデルレベルでのデータ構造という意味ならば、LispとSmalltalkのような大きく異なる言語で同じ実装をしているとは考えにくいと思います。(もちろん、最初にLandinが使った時のSECDマシンとも違うでしょうし)
(なお、時点という言葉も曖昧です。関数を動的に生成しない言語では、関数を定義した「時点」というのがいつなのかを決めることは難問です)

Lispでの開ラムダ式とダイナミッククロージャとクロージャ(レキシカルクロージャ)の差や、Smalltalkでのブロックコンテキストとブロッククロージャの差、それからLispのクロージャとSmalltalkのブロッククロージャの共通点を抜き出していけば、以下のような抽象的な定義に落ち着きます。

クロージャ:オブジェクト内のフリーシンボルを字句上の環境と結びつけること。(また、それにより作られたオブジェクト。)

ちなみに、この「オブジェクト」はオブジェクト指向のオブジェクトではなく、第一級オブジェクトなどという言葉で使われる方ですので、誤解のないように。クロージャの対象となるオブジェクトは、関数オブジェクトであったりブロックオブジェクトであったりするのですが、そのオブジェクトが何であるのかをクロージャの定義の中に含めてはいないということです。
このように抽象的に定義することで、Rubyのブロックはクロージャだよとか、Javaの無名クラスはクロージャか、というような、Landinのクロージャの定義では意味をなさないような命題にも意味を与えることができるようになっているのです。
(ちなみに、字句上の環境と結び付けられるのは、変数だけとは限りません。先日紹介したHaskellにおけるクロージャの説明を読めば、データ型やクラス名に対しても適用していることが確認できます。なお、Haskellのクラスはオブジェクト指向のそれとは違うので注意してください。)

また、逆に、静的スコープを考える際に、その対象となるオブジェクトが存在しないということはあまり考えられません。サブルーチンのない言語で静的スコープを議論することを考えて見てください。
また、オブジェクトを関数に限定するべきでもありません。たとえばクラスに対して静的スコープを議論することは十分にありえます。
そういうことを考えると、静的スコープは、以下のような抽象的な定義に落ち着きます。

静的スコープ:オブジェクト内のフリーシンボルを字句上の環境と結びつけること。

結局、適用範囲にこそ違いを持っているものの、意味上は「静的スコープ=クロージャ」が成立しているということになります。

tt 2006/01/28 14:46 クロージャ:オブジェクト内のフリーシンボルを字句上の環境と結びつけること。(また、それにより作られたオブジェクト。)
この説明ですが、クロージャは「結びつけること」であるという意味になってしまう理由が分かりません。
括弧内まで含めて、クロージャは「オブジェクト」であるという意味なら納得できるのですが。

クロージャは「結びつけること」であるという意味は、以下の文章のどの比較の結果出てきたものなのでしょうか。
Lispでの開ラムダ式とダイナミッククロージャとクロージャ(レキシカルクロージャ)の差や、Smalltalkでのブロックコンテキストとブロッククロージャの差、それからLispのクロージャとSmalltalkのブロッククロージャの共通点を抜き出していけば、以下のような抽象的な定義に落ち着きます。

あと、Smalltalkのブロックコンテキストとブロッククロージャの差がクロージャの定義にどのように関わってくるのかが分からないのですが教えていただけませんか。

lethevertlethevert 2006/01/28 19:24 反射的に質問するより、一度じっくり考えてみる方が勉強になりますよ。
あと、自分の考えを述べる際には、推論や証拠を一緒に述べる方が、建設的になりやすいですよ。

tt 2006/01/28 21:40
> Lispでの開ラムダ式とダイナミッククロージャとクロージャ(レキシカルクロージャ)の差や、Smalltalkでのブロックコンテキストとブロッククロージャの差、それからLispのクロージャとSmalltalkのブロッククロージャの共通点を抜き出していけば、以下のような抽象的な定義に落ち着きます。

上の文章で比較している言葉は、すべて(クロージャの定義に現れるところの)オブジェクトですよね。
比較対象がすべてオブジェクトなのに、その比較結果として現れるクロージャの定義で

> クロージャ:オブジェクト内のフリーシンボルを字句上の環境と結びつけること。(また、それにより作られたオブジェクト。)

のように、オブジェクトである事がオプショナルになってしまうのが理解できません。

あと、ブロックコンテキストとブロッククロージャについての質問は、意味が分かったので取り下げさせていただきます。

tt 2006/01/28 21:59 ようやく分かりました。lexical closureは静的スコープと同様の意味で使われる場合があったのですね。
クロージャが現れる言語、という適用範囲であれば、同じ意味であるという説明にようやく納得がいきました。
いままで長々とおつきあいいただきありがとうございました。

lethevertlethevert 2006/01/28 22:47
納得いただいたようで何よりです。お疲れ様でした。

ちなみに、開ラムダ式とダイナミッククロージャとクロージャ(レキシカルクロージャ)の説明は
http://d.hatena.ne.jp/lethevert/20060128/p1
でも書いていますが、

開ラムダ式:フリー変数を結びつける環境を持っていない関数
ダイナミッククロージャ:フリー変数が関数を束縛(生成)する環境に結び付けられた関数
レキシカルクロージャ:フリー変数が関数の字句上の環境に結び付けられた関数

ということなので、差分を取ると、レキシカルクロージャとして本質的な部分は「字句上の環境」という部分、文意を補うと、「フリー変数を関数を定義した環境に結びつける」ということなるということです。

そして、さまざまな言語に横断的に適用すると、「変数」や「関数」では定義が狭すぎることになって、「シンボル」や「オブジェクト」と表現する方が汎用的になり、さらに、そのような汎用的な使い方では、文脈によっては「モノ」的な色彩を失って単に「コト」として使われることもあるので、もっとも汎用的な意味まで含めると、下のような定義に落ち着くわけです。

クロージャ:オブジェクト内のフリーシンボルを字句上の環境と結びつけること。(また、それにより作られたオブジェクト。)

もっとも、通常は、クロージャはモノ的な使われ方をしますし、静的スコープはコト的に使いますね。
また、クロージャは、「レキシカルクロージャ + フリー変数を含まないオブジェクト」の意味で使っていることもしばしばあります。

paellapaella 2008/07/01 15:33 今更ですが、先のMartin FowlerがJavaにClosureを載せるに当たって考えたことをまとめた記事が、彼のブログにありました。

その記事を拙訳したものをここに残しておきます。

http://www23.atwiki.jp/selflearn/pages/34.html

wasisanwasisan 2008/07/28 16:29 さらに今更ですが、私が書いていた記事と内容がだぶっていたので。

まとまっていない未熟な内容ですが少しでも参考になれば、と。
http://d.hatena.ne.jp/wasisan/20080220/p1
リンク先に書いた内容は、
「(オブジェクト指向言語における)オブジェクトで、クロージャを実現する」
場合の話だけですが。

上の記事では、クロージャとオブジェクトの等価性ということから
「クロージャで、(オブジェクト指向言語における)オブジェクトを実現する」
という話も混ざっていて、これが混乱気味になっている原因な気がしました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証