質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

8回答

19651閲覧

恥ずかしながらクロージャが分かりません。

Mr_Roboto

総合スコア2208

Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

8グッド

19クリップ

投稿2016/07/13 15:33

編集2016/07/22 11:03

それをはじめてみたのはPerlの書籍だったでしょうか。
最初は、クロシージャと読み間違え、プロシージャの誤植だと思ったのですが違いました。
その頃は、そんなもの無くてもプログラミング出来てました。(今もやってますが)

時は流れて、JavaScriptが大流行、Java8がリリースされる頃になって
やたら?たまに聞くようになってとっても気になるクロージャですが、
どうもいつまで経ってもわかった気になりません。

もちろん、色々なサイトや書籍で事あるごとに説明を読むのですが、
私の理解では、ざっくり関数内のstatic変数? くらいの認識までにしか至らずに今まで参りました。

自分のあずかり知らぬところで世の開発者の皆様はクロージャーを日々駆使しているのでは無いかと思うと夜も眠れません。(もちろん嘘ですが)
何かそのせいで、自分のコーディングは日々回り道をしているのではととても気になります。
(Javaのインターフェースが分かった時には、パーン!みたいな感動があったので、もしかしたらそのようなことがあるのではと思ってます)

前置きが長くなりましたが、テラテイルだからこそこのような質問に答えてくれる方がいるに違いないと思い、恥を忍んで質問いたします。

他のサイトや書籍で読める説明的な説明はいりません。
クロージャに関して、以下の様なことを教えて下さい。

  • クロージャってひとことで言うと何? (多少語弊のある表現でも構いません)
  • 分かった!って思った瞬間を覚えていたら、その時のことを教えて下さい。
  • 結局、どんな時に使いますか? 短いコードで表せたら知りたいです。
  • タグに上げた以外でクロージャを使える言語はありますか?
  • もしよろしければ、理解のための例題をくださると嬉しいです。
  • 上記以外で、理解のためのに必要なことがあれば、とにかく教えて下さい。

全てでなくてもいいです、答えられることだけで構いません。

お忙しい中めんどくさい質問ですいませんが、よろしくお願い致します。


追記 2016/07/14 1:00

言語ごとに多少なりとも解釈や用途が違うということがあると思います。
その場合、統括的な解説なのか、言語特有な説明なのか明示して頂けると齟齬もなくなるかと思います。

では、改めてよろしくお願い致します。

Lhankor_Mhy, mpyw, Chironian, rhrslxzp, yoshio197, pashango2, makiikeda1216👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yohhoy

2016/07/13 15:48

この質問は特定のプログラミング言語を前提としていますか?もしくは言語によらない用語の意味でしょうか?(後者だと回答が発散してしまうかもしれませんね)
Mr_Roboto

2016/07/13 15:58

ありがとうございます。 そうですね、Javaでは、若干意味が違うようなことは理解していますが、言語ごとにどのような違いがあるかもよくわかっていないので、明示して頂ければどちらの回答でも助かります。 よろしくお願いします。
guest

回答8

0

ベストアンサー

クロージャをひとことで・・・(その後が長い)

クロージャをひとことで言うと「定義が評価された時の環境を閉じ込めて一緒に包んでしまうこと」です。

ここで一番大事なのは環境です。環境知らずにしてクロージャは語れません。ほとんどの言語において、環境は「静的スコープ(static scope)での変数(または値)の集合体」のことです。静的スコープレキシカルスコープ(lexical scope)とも呼ばれています。構文から静的に決定されるスコープなので構文的(lexical)と呼んでいるだけであり、静的スコープとレキシカルスコープは同じモノです。

※ Emacs Lisp等、動的スコープ(dynamic scope)を採用している言語もありますが、少数です。

クロージャを語るつもりが、環境やら静的スコープとか出てきました。まず、スコープとは何かというと変数が見える範囲(視野)の事です。下記例を見てみましょう。(以下は静的スコープであるJavaScript(ECMAScript 2016)での例になりますので、動的スコープでは全く異なることに注意してください)

JavaScript

1// 大域スコープ ===> 2"use strict"; 3let x = 1; 4const f = function() { 5 // 関数 f スコープ ---> 6 let y = 2; 7 if (true) { 8 // ブロック スコープ ~~~> 9 let z = 3; 10 console.log(x, y, z); // => 1 2 3 11 // <~~~ ブロック スコープ 12 } 13 // <--- 関数 f スコープ 14}; 15const g = function() { 16 // 関数 g スコープ ---> 17 let y = 42; 18 f(); 19 console.log(y) // => 42 20 // <--- 関数 g スコープ 21}; 22g(); 23// <=== 大域スコープ

JavaScriptでは主に大域スコープ(global scope)、関数スコープ(function scope)、ブロックスコープ(block scope)の三つがあります(他にevalスコープ(eval scope)とモジュールスコープ(module scope)がありますが、ここでは省略します。なお、名称はECMAScript 2016仕様書での○○DeclarationInstantiationとなっているものから取ってきています)。変数は宣言された場所のスコープに関連づけられ、そのスコープの範囲内で有効になります(varで宣言された場合のみ特殊で、ブロックスコープ上であっても一番間近の外側にある大域スコープまたは関数スコープに関連づけられます)。上では、xは大域スコープ、yは関数スコープ、zはブロックスコープが有効範囲です。しかし、スコープは外から内へ浸食しており、内側のスコープは外側のスコープを見ることができます。ですので、xyzと同じように一番内側のブロックスコープでもアクセスできます。同時に、並列にあるスコープはそれぞれ独立しています。ですので、関数g内のyは関数f内のyへの代入に影響を受けません。

※ スコープの種類や名称は言語によって異なります。
※ これはJavaScriptの場合であって、他の言語でも同じとは限りません。例えばRubyは、メソッドスコープから一番外側のトップスコープにあるローカル変数は見えません。

また、内側のスコープで外側のスコープと同じ名前の変数が宣言された場合は、その内側のスコープでのみ有効になる別の変数になります。

JavaScript

1// 大域スコープ ===> 2"use strict"; 3let x = 0; 4if (true) { 5 // ブロックスコープ ~~~> 6 let x = 1; 7 console.log(x); // => 1 8 // <~~~ ブロックスコープ 9} 10console.log(x); // => 0 11// <=== 大域スコープ

大域スコープのxとブロックスコープのxは別の物であるため、大域スコープのxは変更されません。

このように、各スコープでわかれたそれぞれの場所を環境と言います。それぞれの環境にはそのスコープで宣言された変数が束縛されており、また、外側にどんな環境があるかも知っています(外側の環境をどんどん繋いでいく仕組みをスコープチェーンといいます)。

ここでもう一つ重要な概念を話さなければなりません。それは環境の寿命、つまり、そのスコープにある変数の寿命です。スコープは内側に侵食していますが、外側にはみ出すことはできません。つまり、スコープ内の処理を全て終えれば、そのスコープからしか見えない変数はアクセスができなくなる=破棄してもよいということです。一番最初の例を見てください。ブロックスコープを処理するときは、関数fスコープも大域スコープも終わっていることはありません。ですが、ブロックが終わればzは用済みですし、関数fも終われば、関数fだけ有効なyも用済みです。アクセス不可能な変数をいつまでも保持するのはメモリの無駄ですので、そのスコープの環境が破棄され、全ての変数はいずれGCに回収されます。

※ C言語のstaticローカル変数などスコープが終わっても破棄されず、その変数だけ次回に再利用される場合もあります。

ここまではC言語のようなクロージャが無い言語でも同じ事です。では、クロージャは上のことと何が違うのか?を見ていきます。

JavaScript

1// 大域スコープ ===> 2"use strict"; 3const f = function() { 4 // 関数 f スコープ ---> 5 let x = 0; 6 const g = function() { 7 // 関数 g スコープ ---> 8 x++; 9 return x; 10 // <--- 関数 g スコープ 11 } 12 return g; 13 // <--- 関数 f スコープ 14} 15const c = f(); 16console.log(c()); // => 1 17console.log(c()); // => 2 18console.log(c()); // => 3 19// 大域スコープ ===>

さてxは関数fスコープです。先ほどの話では関数fが終わったらxも破棄されると言うことでした。関数g内でxを見ていますが、最初の例のように、もし関数f内で実行される場合であれば、関数fは終了していないので問題ありませんでした。あれ、でも待ってください。関数fは戻り値として関数gを返していて、定数cに代入してます。つまり、関数g中身は関数fが終わっても、定数cとして使用できると言うことです。後から中身だけ使おうとしたら、xがある外側の環境がすでに破棄されていた…となってしまいます。

そこをうまくしてくれるのが我らがクロージャです。クロージャは関数gが作られるまさにそのときの環境を一緒にくっつけて(閉じ込め、包み込んで)くれます。もともとは、関数fスコープの環境は関数fが終われば全て破棄される予定でしたが、関数g(の中身)がその環境をまだ使うよと言って、破棄するのを防いでくれるようになるのです。こうして、関数fが終わった後も、その中にあったxは破棄されず、関数g中身を受け継いだ定数cが関数として呼ばれるときに、xを正常に使えるようになると言うことです。あ、もちろん、いつまでも保持するわけではありません。定数cが破棄されれば、くっつけていた環境もいらなくなるので破棄されます。

ということで、最初のひとことで言っていた「環境の包み込み」の所に戻るというわけです。そして、包み込んだ環境を一緒に持ち出せる所がクロージャの最も重要な所と言っても過言ではありません。

###JavaScriptでクロージャである関数とクロージャではない関数
結論から言いますと、JavaScriptでの全ての関数(名前有り無しなどに関わらず)はクロージャを必ず含んでいます。理由は二つあります。一つは、JavaScriptは関数定義時にその中身を一切評価しないため、中で外側の環境にある変数を使っているかわからないと言うこと。もう一つは、その関数が現在のスコープが終了した後にも使われる事になるかを定義時に判断できないと言うこと。この二つの理由により、JavaScriptエンジンはその関数がクロージャとして動く必要があるのか、それともクロージャでなくても良いのかを関数が作成される定義時に判断することができないからです。

※ 他の言語も同じとは限りません。他の方が示したようにPHPでは普通の関数をクロージャにするかしないかをuseキーワードで選択できます(ただし無名関数は必ずクロージャになる、というよりClosureのインスタンスを作るのが無名関数であるらしい)。

###JavaScript以外のクロージャ
クロージャも実は色々種類があり、実装も様々です。上のことは他の言語でもだいたい同じように言えますが、細部が異なります。

  • Rubyは無名関数ではなくブロック(do ~ end)がクロージャを作ります。クロージャの和訳が「関数閉包」となっていますが、何かしらの処理を定義された場所の環境と一緒に持ち出すことができるのであれば、関数であるかどうかは関係ありません

Ruby

1// Proc.new は無名関数を作る構文ではなく、Procを作るただのメソッド呼び出し 2def f 3 x = 0 4 g = Proc.new do 5 x += 1 6 x 7 end 8 g 9end 10 11c = f 12puts c.call 13puts c.call 14puts c.call
  • Javaのラムダおよび無名クラスはfinalまたは実質的finalしか内部で使えず、値または参照値をコピーしているだけであり、環境を含んでいるわけではありません。つまり、Javaは本当のクロージャを実装していません
  • C++はキャプチャによって外側のスコープの変数を見ることができますが、各変数のコピーまたは参照を渡しているだけであり、環境を含んでいるわけではありません。特に参照キャプチャは外側のスコープが終了すると寿命が尽きるようなローカル変数に使った場合、容易に壊れます。つまり、C++は本当のクロージャを実装していません

C++

1// やってはいけない例 2#include <functional> 3#include <iostream> 4 5std::function<int(void)> f() 6{ 7 int x = 0; 8 auto g = [&x]() { 9 x++; 10 return x; 11 }; 12 return g; 13} 14 15int main() 16{ 17 auto c = f(); 18 std::cout << c() << std::endl; 19 std::cout << c() << std::endl; 20 std::cout << c() << std::endl; 21 return 0; 22}
  • Haskellは遅延評価という仕組みであるため、束縛している変数が実際に評価されるときにはスコープが終了した後になる場合があります。そのため、そのときの各変数のコピーでは無く、環境自体を含んでいる必要があります。つまり、Haskellは本当のクロージャを実装しています。ただし、immutableという性質上、JavaScriptのカウンタの例のようなことはできません。
  • Pythonはちょっと特殊っぽいです。調査中。
  • Schemeはクロージャの元祖らしいです。

投稿2016/07/14 14:33

編集2016/07/14 22:25
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/15 01:15

ご回答ありがとうございます。 む、難しいですね ^_^; というより、その辞書のような知識はどこから仕入れてるのですか? そっちの方が気になりました。 分かってきた気もしてきたのですが、ますます分からなくなったような・・・ しかしそれなりに時間をかけて書いていただいた回答だと思いますので、 咀嚼できるよう自分用の教科書だと思ってじっくり読ませて頂きます。
Mr_Roboto

2016/07/19 07:08

とりあえず、一番ソースの短いRubyを書き換えてみて ちょっと分かったような気もします。 def f x = 0 g = Proc.new do x += 1 x end g end c1 = f puts c1.call puts c1.call puts c1.call c2 = f puts c2.call puts c2.call puts c2.call puts f.call #ここでなるほどと思った。 puts f.call puts f.call puts c1.call puts c2.call 結果: 1 2 3 1 2 3 1 ここ 1 1 4 4 引き続き時間を見て頑張ります。
Mr_Roboto

2016/07/23 14:28

みなさん、素晴らしい回答を沢山頂きありがとうございました。 まだ、半分も理解できてないような気もしますが放置するのも何なので閉めます。 他の回答も引き続き頂ければありがたいです。 ベストアンサーは、どなたにしようか非常に悩みましたが、 一番詳しい解説を頂いたraccyさんにします。 (当初想定したざっくりした解説とはだいぶ違いましたが ^_^;) 他の方もそれぞれ評価あげてありますので、今回はそれでご容赦をば。 また、スキル不足な質問すると思いますが、気が向いたらお付き合いください。
guest

0

多少語弊のある表現でも構いません

とあるので、砕けて言ってみるとすごく単純なもので、
関数の中にある関数がクロージャです。

Javascript

1function outer() { 2 var foo = "outerスコープの変数" 3 function inner() { 4 console.log(foo); 5 } 6}

内側のinner関数がクロージャです。
外側のouter関数はエンクロージャと言います(enclose = "囲む" という意味)。
クロージャ自体の意味はそれだけです。

そして、クロージャの外に宣言されている foo 変数をクロージャ内でも参照できます
ということ。一見普通のようなことです。

クロージャの理解をややこしくしているのは、クロージャの用途が良く分からないからです。
「で、これの何が嬉しいの?」となるからです。
そこは他の回答者の方々の例が参考になると思います。

使いどころは、変数をprivateにできるということだと思います。
Javascriptなどは private というものがありませんので、
private を実現しようと思うと、クロージャを使う以外方法がありません。

Javascript

1function createCounter() { 2 var count = 0; 3 4 return function() { 5 return ++count; 6 }; 7}; 8 9var counter = createCounter(); 10console.log(counter()); // 1と表示 11console.log(counter()); // 2と表示 12console.log(counter()); // 3と表示

こうするとcreateCounter内のcount変数はprivateになるわけです。
この変数にアクセスできるのは、createCounter内にあるクロージャだけです。
createCounterは、そのクロージャ関数を戻り値としています。

ここで奇妙なことが起きています。
上記の、var counter = createCounter(); の部分。
counter変数が参照しているのは

Javascript

1function() { 2 return ++count; 3}

という関数のはずですが、countってどのcount変数?ということになります。
当然、createCounterにあるcountです。
クロージャはエンクロージャ内のローカル変数の参照も含んだものになります。

一番最初の例で、クロージャの外に宣言されている変数をクロージャ内でも参照できます。
と説明しましたが、これはクロージャ関数をreturnして外に放り出しても同じことになります。
ややこしいですねぇ。。。

これを利用するとこんなこともできます。

Javascript

1function createCounter(start) { 2 return function() { 3 return start++; 4 }; 5}; 6 7counter1 = createCounter(1); 8console.log(counter1()); // 1と表示 9console.log(counter1()); // 2と表示 10console.log(counter1()); // 3と表示 11 12counter2 = createCounter(10); 13console.log(counter2()); // 10と表示 14console.log(counter2()); // 11と表示 15console.log(counter2()); // 12と表示

start変数はエンクロージャの物ですが、クロージャ内にちゃんと組み込まれてますね。

Javaに精通されているようですので、これをJavaっぽく書いてみると
すごく普通に見えます。

Javascript

1var Counter = function(start) { 2 this.get = function() { 3 return start++; 4 } 5} 6 7var counter = new Counter(1); 8console.log(counter.get()); // 1と表示 9console.log(counter.get()); // 2と表示 10console.log(counter.get()); // 3と表示 11 12var counter = new Counter(10); 13console.log(counter.get()); // 10と表示 14console.log(counter.get()); // 11と表示 15console.log(counter.get()); // 12と表示

今まので例は、このコードを以下のようにしてるようなものです。

Javascript

1var counter = new Counter(1).get; 2console.log(counter()); // 1と表示 3console.log(counter()); // 2と表示 4console.log(counter()); // 3と表示

投稿2016/07/14 04:01

編集2016/07/14 04:20
root_jp

総合スコア4666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/14 05:03

ご回答ありがとうございます。 なるほど、かなり分かりやすいですね ^^ ちょっと今時間ないですが、改めて手を動かして自分で確認してみます。 そういえば、Eclipseでエンクロージャって出てきてなんだこれ? って思ってスルーしてから10年以上経ってる気がします w
root_jp

2016/07/14 05:11

エンクロージャのインスタンスがないので、インナークラスにアクセスはできませんよ? って感じのメッセージですね。 class Outer { class Inner { } } エンクロージャである Outer のインスタンスなしで new Inner() はできませんよってことですね。 僕も昔わっけ分かりませんでした。
guest

0

maisumakunさんの回答につけたコメントの補足をこちらに書いてみます.PHPのクロージャはやはり少し特殊なので,JavaScriptと対比します.

最新の外部の変数の値を常に参照する

これが一番わかりやすいですね.

php

1<?php 2$value = 1; 3$closure = function () use (&$value) { 4 ++$value; 5 echo "Inside: value is $value\n"; // 1, 2 6}; 7--$value; 8$closure(); 9$closure(); 10echo "Outside: value is $value\n"; // 2

javascript

1var value = 1; 2var closure = function () { 3 ++value; 4 console.log('Inside: value is ' + value); // 1, 2 5}; 6--value; 7closure(); 8closure(); 9console.log('Outside: value is ' + value); // 2
最新の外部の変数の値を参照するが,内部で起こった変更は無視する

この場合には,クロージャの呼び出しごとにクロージャを内部的に作って実行する必要があります.作ってすぐに使い捨ての形で実行しているので,この形を即時実行といいます.その際,現在のvalueの値を引数として毎回与える必要があります.あくまで値として渡す引数なので,これに対する変更は外部や次の自分自身の実行には影響を与えません.

php

1<?php 2$value = 1; 3$closure = function () use (&$value) { 4 return (function ($value) { 5 ++$value; 6 echo "Inside: value is $value\n"; // 1, 1 7 })($value); // この書き方はPHP7.x以降 8}; 9--$value; 10$closure(); 11$closure(); 12echo "Outside: value is $value\n"; // 0

JavaScript

1var value = 1; 2var closure = function () { 3 return (function (value) { 4 ++value; 5 console.log('Inside: value is ' + value); // 1, 1 6 })(value); 7}; 8--value; 9closure(); 10closure(); 11console.log('Outside: value is ' + value); // 0
クロージャが作られたときの外部の変数の値を使用するが,内部で起こった変更は保持する

さっきのパターンの逆で,最初の1回だけクロージャを即時実行し,そのクロージャが作られたときのvalueの値を保持しようというものです.JavaScriptでは引数として渡すしかありませんが,PHPではuseで渡すこともできるので,今回はこちらで書いてみました.内部のクロージャはそのvalueに対しての参照を持つので,自分自身の次の実行には影響を与えます.

php

1<?php 2$value = 1; 3$closure = (function () use ($value) { 4 return function () use (&$value) { 5 ++$value; 6 echo "Inside: value is $value\n"; // 2, 3 7 }; 8})(); // この書き方はPHP7.x以降 9--$value; 10$closure(); 11$closure(); 12echo "Outside: value is $value\n"; // 0

javascript

1var value = 1; 2var closure = (function (value) { 3 return function () { 4 ++value; 5 console.log('Inside: value is ' + value); // 2, 3 6 }; 7})(value); 8--value; 9closure(); 10closure(); 11console.log('Outside: value is ' + value); // 0
クロージャが作られたときの外部の変数の値を常に使用する

これまでの応用です.PHPの場合はあっさり書けますが,JavaScriptがかなりややこしいですね…

php

1<?php 2$value = 1; 3$closure = function () use ($value) { 4 ++$value; 5 echo "Inside: value is $value\n"; // 2, 2 6}; 7--$value; 8$closure(); 9$closure(); 10echo "Outside: value is $value\n"; // 0

JavaScript

1var value = 1; 2var closure = (function (value) { 3 return function () { 4 return (function (value) { 5 ++value; 6 console.log('Inside: value is ' + value); // 2, 2 7 })(value); 8 }; 9})(value); 10--value; 11closure(); 12closure(); 13console.log('Outside: value is ' + value); // 0

投稿2016/07/14 02:52

編集2016/07/14 03:48
mpyw

総合スコア5223

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/14 03:19

ご回答ありがとうございます。 えっと・・・これをささっと書いたとするとちょっと驚異的な速度ですね。 (レッドコーダーの方ですか?) すぐには多分理解できないので、ちょっとゆっくり読ませて頂きます。 みなさんのおかげで、なんか今日一日でだいぶパワーアップした気がします。 多分気のせいですがw (テラテイル2周年ということで半分ネタというか馬鹿にされるつもりで質問したのですが) 改めてありがとうございます。 ここに書いてしまいますが、引き続き別の視点からの解説もお待ちしております。 (特にざっくりした感じで分かった気になるようなのを)
mpyw

2016/07/14 04:30

「関数の中にある関数がクロージャです。」との回答がついたので,語弊のないように自分の回答に関して更に補足します. 私が書いたクロージャは,確かに「関数の中にある関数」ではないものもあります.しかし,外部の値を$GLOBALSやglobal宣言でグローバル変数として参照しているわけではありません.スコープはきちんと守られています. なので,より正確に言い換えれば「グローバル変数を使わずとも外部の変数へアクセスできる」のがクロージャです.
guest

0

解説する上でわかりやすいので、あえてPHPで説明します(基本的な文法構造はC言語系で、変数名はドルで始まります)。

まず、PHP 5.3以降では、無名関数を作って変数に代入することができるようになりました。

php

1$func = function($arg1, $arg2){ 2 return $arg1 * $arg2; 3};

これはただの無名関数ですが、useという構文を使って無名関数が宣言された環境の変数を取り込むことができます。

php

1// 説明の都合上、PHP 5.3前提のコードです 2$some_array = array(); 3$closure = function($arg1, $arg2) use (&$some_array){ 4 $some_array[$arg1] = $arg2; 5 return $arg1 * $arg2; 6};

このようにしてから、$closureだけほかのスコープに持ち出しても、$some_arrayにはアクセスできるままとなります。このように、「別のものへの参照を組み込んだ」関数がクロージャです。

なお、PHPやC++では明示的に外側への参照をする必要がありますが、他の多くの言語では外側の変数を勝手に参照してくれるので、概念説明という意味では逆にわかりづらいです。

投稿2016/07/14 01:15

maisumakun

総合スコア145184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mpyw

2016/07/14 02:02 編集

注意すべきなのは ・useがあってもなくても名前無しのfunction式(名前有りのfunction宣言文ではない)は必ずClosureクラスのインスタンスになる.概念的には不適切だが,実装の都合上わざわざuseが無い場合を特別視する必要はないから. ・&つきでuseした場合,他の言語と同じように外側の変数を参照するようになる. ・&なしでuseした場合,インスタンスが作成されたタイミングのその変数の値で常に実行が開始される.内部での変更は外部に影響を与えないし,外部での変更は内部に影響を与えない.また,内部での変更は次の自分自身の実行にも影響を与えない. というところですかね.
maisumakun

2016/07/14 02:00

あと、PHP 5.4ではメソッド内でクロージャを作ると自動で$thisを参照する、ということを書き忘れてました。
Mr_Roboto

2016/07/14 02:40

ご回答ありがとうございます。 PHPは、まぁまぁわかります。 無名関数作れるようになったんですね、全然知りませんでした。 というか、普段無名関数ほとんど(いや全然)使わないんです。 ぶら下がってるコメントの内容も若干高度で???ですが で、文法的な話になってしまいますが、すいません。 以下の部分なのですが、 $closure = function($arg1, $arg2) use (&$some_array){ 何故この位置に use (&$some_array) が来るかPHPのマニュアルを見てもよくわからないのですが、こうじゃないの? $closure = function($arg1, $arg2) { use (&$some_array) と思ってしまいます。 (JavaScriptでいう、varを付けない変数と同じ?) 位置がここで合ってるとして複数ある場合は、どう書くの?と 質問の主旨と外れますが、ご回答くださると嬉しいです。 みなさんの回答をみて、だんだん理解できてきた気がしますが、 結局なくても別にいいんじゃない?ってまだ思ってしまいます ^_^; つまり 「別のものへの参照を組み込んだ」関数がクロージャ ということですが、一般的に変数のスコープはできるだけ短い(狭い)方がいいと 思ってるのですが、これでは台無しでは?と もしくは、再帰のような魔力があるものなのでしょうか (勘違いしてたらご指摘ください)
mpyw

2016/07/14 03:00

>> $closure = function($arg1, $arg2) { >> use (&$some_array) >> と思ってしまいます。 useは関数ではなく,あくまでfunction式のオプション的位置づけです. function (...) use (...) { } で1つの文法です. >> 一般的に変数のスコープはできるだけ短い(狭い)方がいいと >> 思ってるのですが、これでは台無しでは?と 確かに一理あるのですが,多数の変数を別の関数から参照したいとき,毎回引数としてすべてを渡すのは面倒なときもあります.そういう際に,PHP以外の他の言語では自動的にすべての変数の参照をとってくれるので,クロージャは便利といえば便利です.内部から外部は参照できますが,外部から内部が参照できないので,何でもかんでも一箇所に突っ込むグローバル変数よりは規律が守れていると思います. ただ,PHPの場合は使う変数を全部use内に書く必要があるので,この旨味は半減しますね.毎回は渡さなくていいですが,クロージャを作るときにだけは変数名を羅列する必要があります.
mpyw

2016/07/14 03:06

PHPは5.2の頃とは何もかも違って劇的な変化を遂げているので,再入門されてみるのもいいかもしれません.とくに5.3,5.4,7.0での変更が大きいです. http://jp2.php.net/manual/ja/appendices.php
Mr_Roboto

2016/07/14 09:18

PHPに関して、そんなに変わってるものなのですか。 PHP3の時代からのユーザーなのですっかり知識が古くなってるようです。 気づかせてくれてありがとうございます、7もリリースされてますしねぇ 再度、勉強します。
guest

0

クロージャの解説を読んでも理解しにくいのは、おそらくたいていの解説では「クロージャをたかだか1つしか生成していない」せいではないかと思います。

クロージャは、1つだけ生成してもあまり有難みがわかりません。
これはオブジェクト指向でも同じで、クラスから1つだけオブジェクトを生成してもピンとこないのではないでしょうか。一度クラスを定義したら、クラスからいくつも同じ性質のオブジェクトを生成できるのがオブジェクト指向の利点です。
クロージャもそれは同じで、一度元になる関数(というべきか?)を定義してしまえば、同じ性質のクロージャをいくつも生成できるところに利点があります。

単純なカウンタを例に見てみましょう。

まずはじめにグローバル変数を参照する例を見てみます。

javascript:グローバル変数を利用した例

1var globalValue = 0; 2 3function globalCounter(){ 4 function inner(){ 5 return globalValue++; 6 } 7 return inner; 8} 9 10var g1 = globalCounter(); 11g1(); // -> 0 12g1(); // -> 1 13 14var g2 = globalCounter(); 15g2(); // -> 2 16g2(); // -> 3

この例では、関数g1も関数g2も同じglobalValueを参照しているので、g2()を呼び出したとき、以前にg1()がカウントした値の続きからはじまっています。

次にクロージャを利用した例です。

javascript:クロージャを利用した例

1function counter(){ 2 var value = 0; 3 function inner(){ 4 return value++; 5 } 6 return inner; 7} 8 9var f1 = counter(); 10f1(); // -> 0 11f1(); // -> 1 12f1(); // -> 2 13 14var f2 = counter(); 15f2(); // -> 0 16f2(); // -> 1

関数f1,f2はそれぞれ独立したvalueという変数を参照しているので、f1とf2は独立したカウンタとして動作しています。
変数valueは、f1とf2それぞれに独立した変数としてf1,f2の中に閉じ込められているのです。

このようにクロージャを使うことにより、状態を内部にもったオブジェクトを作成することができます。

あえて「オブジェクト」という用語を使いましたが、関数型言語におけるクロージャは、オブジェクト指向におけるオブジェクトと考えることが可能です。
上記の例でいえば、関数counterがクラス、関数f1,f2がそれぞれオブジェクトと考えられます。

投稿2016/07/19 06:01

編集2016/07/19 06:04
JunSuzukiJapan

総合スコア310

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/19 06:12

ご回答有り難うございます。 なるほど、そのように説明されるとすごく簡単なもののように感じますね。 あーそうか、クラスのメンバー変数みたいなことなのかな。 外側のfunctionがクラスになれば、ほとんど同じですね。 (曲解してますか?^_^;) でもそうなると今度は、オブジェクトとの使い分けは?と新たな疑問が湧いてきますね。 でもみなさんのおかげで随分と分かってきたように思います。 ちなみにベストアンサー付けてしまうと、読まなくなるので咀嚼できるまで、わざと付けずにおいているのです。 (今しばらくお時間を・・・)
JunSuzukiJapan

2016/07/19 06:43

>外側のfunctionがクラスになれば、ほとんど同じですね。 (曲解してますか?^_^;) その理解であっています。 >でもそうなると今度は、オブジェクトとの使い分けは?と新たな疑問が湧いてきますね。 最近の言語は、オブジェクト指向言語と関数型言語の特徴との両方を兼ね備えた言語が多いので誤解があるのかもしれませんが、元々オブジェクト指向言語と関数型言語は別々に発展してきたものです。 オブジェクト指向もクロージャも「グローバル変数ではないところに状態を保っていたい」という目的は同じだったので、2つの考え方が混ざって出来た言語は、同じ目的を2種類の方法で表現できてしまうのです。 ぶっちゃけ個人的には、オブジェクト指向があればクロージャを使う必要はないと思っています(クロージャを使いたいと思ったことがないので)。
Mr_Roboto

2016/07/19 06:52

> ぶっちゃけ個人的には、オブジェクト指向があればクロージャを使う必要はない なるほど、そこに落ち着きますかw (長いこと悩んできたんだけどなぁ・・・) まぁ、いずれにしてもそれを踏まえて、頭のすっきりしている時に もう一度他の方の回答をもう一度読みなおしてみますね。 改めてのご回答ありがとうございました ^^
退会済みユーザー

退会済みユーザー

2016/07/22 14:05

>でもそうなると今度は、オブジェクトとの使い分けは?と新たな疑問が湧いてきますね。 クロージャの場合変数を閉じ込めておくために使います。setter,getterというのがjavascriptの新しい仕様にあるので正直あまり使いませんが。。。 オブジェクトの場合は同じ種類のもの(自動車の部品ならタイヤ、ボンネット等です)を集めるために使ったり(name space的な使い方)、クラスを作るときに使ったりするのでクロージャーとオブジェクトはかなり別物だと思われます。
Mr_Roboto

2016/07/23 03:15

> pasuwardoさん ありがとうございます。 > クロージャーとオブジェクトはかなり別物だと思われます。 そこの理解は大丈夫です。 以下の時、 function counter(){ ← ここがクラス宣言と見立てて var value = 0; ← ここだけ見るとメンバー変数にとても似ているということです。 function inner(){ return value++; } return inner; }
guest

0

クロージャってひとことで言うと何? (多少語弊のある表現でも構いません)

相当に語弊のある表現ですが、クロージャとは「レキシカルなスコープを生成する機能」だと理解しています。

分かった!って思った瞬間を覚えていたら、その時のことを教えて下さい。

↓これです!
javascriptに関する質問です。 a=1; function hoge(){ var a=2; … - 人力検索はてな

結局、どんな時に使いますか? 短いコードで表せたら知りたいです。

他の方の回答例のほかに、javascriptだとthisを束縛する時とかに使えないこともないです。

javascript

1function Person() { 2 var self = this; // `self` の代わりに `that` を選ぶこともあります。 3 // どちらかに統一するようにしましょう。 4 self.age = 0; 5 6 setInterval(function growUp() { 7 // このコールバックは、期待されるオブジェクトの値を 8 // `self` 変数で参照します。 9 self.age++; 10 }, 1000); 11}

アロー関数 - JavaScript | MDN

タグに上げた以外でクロージャを使える言語はありますか?

Pythonです!
pythonと関数型プログラミング - 愚鈍人

もしよろしければ、理解のための例題をくださると嬉しいです。
上記以外で、理解のためのに必要なことがあれば、とにかく教えて下さい。

↓これが分かりやすいのではないかな、と。

束縛変数は関数内部でしか使用されないため、そのすべての出現箇所の名前を変えても関数の意味は変わりません。
自由変数は束縛変数のように勝手に名前を変更することはできません。
開いた関数が環境から自由変数の値を取り込むことで閉じた関数となるというところがクロージャを理解する上でのキーとなります。

クロージャ再考 - Qiita

投稿2016/07/14 03:15

Lhankor_Mhy

総合スコア36115

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/14 04:01

設問に沿ったご回答ありがとうございます。 「レキシカルなスコープを生成する機能」 このレキシカルで、いつも思考停止するんですよねぇ (もちろん調べるのですが、あと高階関数とか) Qiitaの記事は読んだことがあるのですが、 私には???????でした。 気力のある時に改めてまた頑張って読んでみます^_^; よくいう「5歳の子供でも分かるように」というような説明が無いのか といつも思うのですが、クロージャに関してはなかなか難しいものなのですかね。 (ちなみにインターフェースは、コンセントだと思ってますが) 専門用語を使わず、人がやる行動として何が近いのかというような回答が欲しいのかも知れません。 とはいえ、参考になる部分はたくさんありました、ありがとうございます ^^
yohhoy

2016/07/14 10:59

横槍ですが クロージャ=レキシカルスコープの生成 なのはJavaScript特有の事情だと思います。
Mr_Roboto

2016/07/17 06:55

yohhoyさん、コメントありがとうございます。 私には、それだけではまだよく理解できないので、 よろしければ解説いただけると助かります。
yohhoy

2016/07/19 01:00

改めて見直すと、私のコメントも正確ではないですね… このコメントを書いたときは、「JavaScript言語はブロックスコープを提供しないため、関数(≒クロージャ)構文を用いて同等機能を実現する」というニュアンスで解釈していました。 少なくとも用語の問題だけを扱うと、レキシカルスコープは"生成"されるものではなく、その言語におけるルールとして"採用"するモノです。(対義語としてダイナミックスコープという考え方も存在します) JavaScriptの場合、「クロージャ内部から参照する/できる変数は、レキシカルスコープに基づいて決定される」となります。
Mr_Roboto

2016/07/23 14:19

解説ありがとうございます。 私にはまだちょっと???ですが ^_^;
guest

0

クロージャと言うからには「何か」を閉じているはずです。
ということは「何か」が開いていて、それが閉じます。
そして「何か」は関数です。

関数内部に、外部で定義された変数がある関数を「開いている」と表現します。
ない関数を「閉じている」と表現します。

fo() = return a + 1 // 開いている fc(x) = return x + 1 // 閉じている

開いている関数は、外部で変数が定義された文脈でしか実行できません。

fo() = return a + 1 y = fo() // エラー

開いている関数に、文脈を持たせてやると、閉じることができます。

close_fo() = a = 1 // 文脈 fo() = return a + 1 // 開いている関数 return fo f = close_fo() // 閉じた関数がもらえる

この、「開いている関数に実行文脈をつけて閉じたもの」をクロージャと呼びます。

クロージャの使いみちによくカウンタが例に出てきます。

make_counter() = count = 0 f() = print (count) count = count + 1 return return f

これはクロージャを使わずとも、

count = 0 f() = print (count) count = count + 1 return

と書けます。
しかし、countに他からアクセスし放題ですし、
カウンタが複数必要な時、名前が衝突して困ります。

count1 = 0 f1() = print (count1) count1 = count1 + 1 return count2 = 0 f2() = print (count2) count2 = count2 + 1 return

クロージャなら

make_counter() = count = 0 f() = print (count) count = count + 1 return return f f1 = make_counter() f2 = make_counter()

投稿2016/07/14 00:27

編集2016/07/14 02:20
ozwk

総合スコア13528

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/14 01:50

ご回答有り難うございます。 最初のところがいきなり???なのですが、 fo() = a + 1 fc(x) = x + 1 というのは、何の言語かわからないのですがRubyでしょうか? (Rubyは、タグに入れたのですが、あまり詳しくありません) これは、関数を定義しているのか、代入しているのか分からず私の理解では int a fo() { a + 1 } fc(x) { x + 1 } のように受け取りましたがあっていますでしょうか? 閉じるというのは、なんとなく分かったのですが、これを実現しているのは、 f1 = make_counter() の部分、つまり関数ポインタ?を使うから可能という理解でよろしいですか? (Zuishinさんの例で言うデリゲート?) なかなか理解が進まず申し訳ありませんがご回答いただけたら幸いです。
ozwk

2016/07/14 02:17

すみません模擬コードです。 ちょっと曖昧だったので直します。
ozwk

2016/07/14 02:32

クロージャを受け取るには 関数を返す関数が必要で、 関数ポインタだったりデリゲートだったり言語によって仕組みの呼び方が違いますが、 つまり関数を普通の値と同じように扱う仕組みが必要です。 (変数に代入したり、引数にしたり、戻り値にしたり)
guest

0

クロージャという言葉ではあまり表現しませんが、C# のラムダ式もそうです。

それが作られた環境をそのまま持って行く式とでも言いましょうか。

例えばローカル変数 a が存在する関数内で別の関数 f を宣言します。f 内では a を使用します。
f を関数ポインタとして他の関数に渡します。
通常であれば、f を他の場所で実行する時には変数 a は消滅しているので使用できません。しかし、f がクロージャであればこれを使用できます。
作られたときにあった a をそのまま持って行っているからです。
###追記
タグに無いので適切ではないかもしれませんが、C# の例です。

C#

1/// ボタンが押されたときに実行 2private void button1_Click(object sender, EventArgs e) 3{ 4 // クロージャ取得 5 var mul = createMul(2); 6 7 // mul(3) を実行し、それをデバッグコンソールに書き出す。 8 // 作られたときの 2 が保存されているので、この結果は 6 となる。 9 Debug.WriteLine(mul(3)); 10 11 mul = createMul(5); 12 Debug.WriteLine(mul(3)); // この結果は 15 になる。 13} 14 15// クロージャを作って返すメソッド 16private Func<int, int> createMul(int a) 17{ 18 // f(b) とした時に a * b を返すクロージャを返す 19 return b => a * b; 20}

投稿2016/07/13 22:08

編集2016/07/13 22:38
Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Mr_Roboto

2016/07/14 01:09

ご回答ありがとうございます。 例を読んでいるのですが、まだちょっと理解が難しいですね。 別に質問していこうかと思っていましたが、実はラムダ式とデリゲートの理解もまだちょっと怪しいのです ^_^; private Func<int, int> createMul(int a) の Func<int, int> は、デリゲートを返しているという理解でよろしいでしょうか? return b => a * b; の b => a * b はラムダ式ですよね? それぞれ違ったらご指摘頂けると幸いです。 あと質問と外れる部分ですが、Func<int, int> となっているのになぜ mul(3) と呼び出せるのか???となってしまいます。 結局、createMul(2) としたときの2が mul(3) とした時に保持(クローズ?)されているということがクロージャということなのでしょうか? 普通に引数渡して計算するのと比べて何が嬉しいのか、 いまいちピンと来ないのですが、その辺りもご解説いただけると嬉しいです。
Zuishin

2016/07/14 01:21

Func<int, int> は int f(int a); という型のデリゲートを表します。Func<int, int> の最初の int は引数の型で、次の int は戻り値の型です。 ラムダ式はあくまで式であり、実行できません。これをコンパイルして初めて実行できるようになりますが、デリゲートにキャストすることでコンパイルが行われます。 この場合の普通に引数渡して計算するのと比べての利点は、ozwk さんのおっしゃるように変数を別に保持しておかなくて良いことです。 クラスを作ってオブジェクトにしてしまってもいいわけですが、それより記述ははるかに簡単になります。
Zuishin

2016/07/14 01:31

誤解を与えかねなかったので追記します。ラムダ式のコンパイルが行われるのは実行時ではなく C# のコンパイル時です。 ラムダ式をデリゲートにキャストしない事例としては、LINQ to SQL があります。この場合、ラムダ式はメソッドではなく SQL にコンパイルされます。
Zuishin

2016/07/14 01:42

オブジェクト指向言語では、アルゴリズムとデータの組み合わせをオブジェクトとして表現します。 関数型言語では、アルゴリズムとデータをセットで扱うため、クロージャという概念が生まれました。 オブジェクトの持つ多態性などはありませんが、ものすごく大雑把に言えば、いわゆるオブジェクト、構造体などの関数型言語的表現と言えないこともないのではないかと思います。
Mr_Roboto

2016/07/14 02:55

あー、なるほど最後のコメントでとても理解が進みました。 (ような気がします) > 構造体などの関数型言語的表現と言えないこともない は、ほほーと思いました。 理解が進まなかった理由として、 C言語があまり分からないので関数ポインタがピンと来ていない。 (デリゲートもピンと来ない、VB、Java辺りから入った人なので) クロージャという概念がどこから来たのかというのが謎だった。 (関数型というとLispとかHaskellとかですよね) この辺が鍵になりそうですね。 Haskellは、文法があまりに・・・だったので一度挫折したのですが、 また、ちょっと勉強してみるといいのかな。 (そういえばラムダ式も関数型から来てるものですよね) ご丁寧な回答ありがとうございます。 サンプルコードよりむしろその後のコメントの方が分かりやすかったです。 質問にも書きましたが多少語弊があってもざっくりとした理解がしたかったものですから助かりました。 (ちなみに別件ですが、某質問ではちょっと反論のような回答をしましたが悪意はございませんので、ご理解を^_^;)
Zuishin

2016/07/14 03:04 編集

そうでしたっけ? あちこち思いついたことを書き散らして言葉も選ばないもので反論はよくされますが、しつこく付け回された人以外誰にされたかをよく覚えていないもので逆にすみません。 どうせ私が失礼なことを言ったんだと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問