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

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

ただいまの
回答率

87.91%

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

解決済

回答 8

投稿 編集

  • 評価
  • クリップ 18
  • VIEW 14K+

score 2084

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

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

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

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

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

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

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

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

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


追記 2016/07/14 1:00

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • yohhoy

    2016/07/14 00:48

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

    キャンセル

  • Mr_Roboto

    2016/07/14 00:58

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

    キャンセル

回答 8

checkベストアンサー

+14

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

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

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

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

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

// 大域スコープ ===>
"use strict";
let x = 1;
const f = function() {
  // 関数 f スコープ --->
  let y = 2;
  if (true) {
    // ブロック スコープ ~~~>
    let z = 3;
    console.log(x, y, z); // => 1 2 3
    // <~~~ ブロック スコープ
  }
  // <--- 関数 f スコープ
};
const g = function() {
  // 関数 g スコープ --->
  let y = 42;
  f();
  console.log(y) // => 42
  // <--- 関数 g スコープ
};
g();
// <=== 大域スコープ

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は、メソッドスコープから一番外側のトップスコープにあるローカル変数は見えません。

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

// 大域スコープ ===>
"use strict";
let x = 0;
if (true) {
  // ブロックスコープ ~~~>
  let x = 1;
  console.log(x); // => 1
  // <~~~ ブロックスコープ
}
console.log(x); // => 0
// <=== 大域スコープ

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

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

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

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

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

// 大域スコープ ===>
"use strict";
const f = function() {
  // 関数 f スコープ --->
  let x = 0;
  const g = function() {
    // 関数 g スコープ --->
    x++;
    return x;
    // <--- 関数 g スコープ
  }
  return g;
  // <--- 関数 f スコープ
}
const c = f();
console.log(c()); // => 1
console.log(c()); // => 2
console.log(c()); // => 3
// 大域スコープ ===>

さて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)がクロージャを作ります。クロージャの和訳が「関数閉包」となっていますが、何かしらの処理を定義された場所の環境と一緒に持ち出すことができるのであれば、関数であるかどうかは関係ありません
// Proc.new は無名関数を作る構文ではなく、Procを作るただのメソッド呼び出し
def f
  x = 0
  g = Proc.new do
    x += 1
    x
  end
  g
end

c = f
puts c.call
puts c.call
puts c.call
  • Javaのラムダおよび無名クラスはfinalまたは実質的finalしか内部で使えず、値または参照値をコピーしているだけであり、環境を含んでいるわけではありません。つまり、Javaは本当のクロージャを実装していません
  • C++はキャプチャによって外側のスコープの変数を見ることができますが、各変数のコピーまたは参照を渡しているだけであり、環境を含んでいるわけではありません。特に参照キャプチャは外側のスコープが終了すると寿命が尽きるようなローカル変数に使った場合、容易に壊れます。つまり、C++は本当のクロージャを実装していません
// やってはいけない例
#include <functional>
#include <iostream>

std::function<int(void)> f()
{
    int x = 0;
    auto g = [&x]() {
        x++;
        return x;
    };
    return g;
}

int main()
{
    auto c = f();
    std::cout << c() << std::endl;
    std::cout << c() << std::endl;
    std::cout << c() << std::endl;
    return 0;
}
  • Haskellは遅延評価という仕組みであるため、束縛している変数が実際に評価されるときにはスコープが終了した後になる場合があります。そのため、そのときの各変数のコピーでは無く、環境自体を含んでいる必要があります。つまり、Haskellは本当のクロージャを実装しています。ただし、immutableという性質上、JavaScriptのカウンタの例のようなことはできません。
  • Pythonはちょっと特殊っぽいです。調査中。
  • Schemeはクロージャの元祖らしいです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/15 10:15

    ご回答ありがとうございます。

    む、難しいですね ^_^;
    というより、その辞書のような知識はどこから仕入れてるのですか?
    そっちの方が気になりました。

    分かってきた気もしてきたのですが、ますます分からなくなったような・・・

    しかしそれなりに時間をかけて書いていただいた回答だと思いますので、
    咀嚼できるよう自分用の教科書だと思ってじっくり読ませて頂きます。

    キャンセル

  • 2016/07/19 16: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

    引き続き時間を見て頑張ります。

    キャンセル

  • 2016/07/23 23:28

    みなさん、素晴らしい回答を沢山頂きありがとうございました。
    まだ、半分も理解できてないような気もしますが放置するのも何なので閉めます。

    他の回答も引き続き頂ければありがたいです。

    ベストアンサーは、どなたにしようか非常に悩みましたが、
    一番詳しい解説を頂いたraccyさんにします。
    (当初想定したざっくりした解説とはだいぶ違いましたが ^_^;)

    他の方もそれぞれ評価あげてありますので、今回はそれでご容赦をば。

    また、スキル不足な質問すると思いますが、気が向いたらお付き合いください。

    キャンセル

+5

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

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

function outer() {
  var foo = "outerスコープの変数"
  function inner() {
    console.log(foo);
  }
}

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

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

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

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

function createCounter() {
  var count = 0;

  return function() {
    return ++count; 
  };
};

var counter = createCounter();
console.log(counter()); // 1と表示
console.log(counter()); // 2と表示
console.log(counter()); // 3と表示


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

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

function() {
  return ++count; 
}


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

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

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

function createCounter(start) {
  return function() {
    return start++; 
  };
};

counter1 = createCounter(1);
console.log(counter1()); // 1と表示
console.log(counter1()); // 2と表示
console.log(counter1()); // 3と表示

counter2 = createCounter(10);
console.log(counter2()); // 10と表示
console.log(counter2()); // 11と表示
console.log(counter2()); // 12と表示


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

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

var Counter = function(start) {
  this.get = function() {
    return start++; 
  }
}

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

var counter = new Counter(10);
console.log(counter.get()); // 10と表示
console.log(counter.get()); // 11と表示
console.log(counter.get()); // 12と表示

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/14 14:03

    ご回答ありがとうございます。

    なるほど、かなり分かりやすいですね ^^
    ちょっと今時間ないですが、改めて手を動かして自分で確認してみます。

    そういえば、Eclipseでエンクロージャって出てきてなんだこれ?
    って思ってスルーしてから10年以上経ってる気がします w

    キャンセル

  • 2016/07/14 14:11

    エンクロージャのインスタンスがないので、インナークラスにアクセスはできませんよ?
    って感じのメッセージですね。

    class Outer {
    class Inner {
    }
    }

    エンクロージャである Outer のインスタンスなしで
    new Inner() はできませんよってことですね。
    僕も昔わっけ分かりませんでした。

    キャンセル

+2

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

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

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


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

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


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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/14 12:00

    >> $closure = function($arg1, $arg2) {
    >> use (&$some_array)
    >> と思ってしまいます。

    useは関数ではなく,あくまでfunction式のオプション的位置づけです.

    function (...) use (...) { }

    で1つの文法です.

    >> 一般的に変数のスコープはできるだけ短い(狭い)方がいいと
    >> 思ってるのですが、これでは台無しでは?と

    確かに一理あるのですが,多数の変数を別の関数から参照したいとき,毎回引数としてすべてを渡すのは面倒なときもあります.そういう際に,PHP以外の他の言語では自動的にすべての変数の参照をとってくれるので,クロージャは便利といえば便利です.内部から外部は参照できますが,外部から内部が参照できないので,何でもかんでも一箇所に突っ込むグローバル変数よりは規律が守れていると思います.

    ただ,PHPの場合は使う変数を全部use内に書く必要があるので,この旨味は半減しますね.毎回は渡さなくていいですが,クロージャを作るときにだけは変数名を羅列する必要があります.

    キャンセル

  • 2016/07/14 12:06

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

    http://jp2.php.net/manual/ja/appendices.php

    キャンセル

  • 2016/07/14 18:18

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

    キャンセル

+2

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

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

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

<?php
$value = 1;
$closure = function () use (&$value) {
    ++$value;
    echo "Inside: value is $value\n"; // 1, 2
};
--$value;
$closure();
$closure();
echo "Outside: value is $value\n"; // 2
var value = 1;
var closure = function () {
    ++value;
    console.log('Inside: value is ' + value); // 1, 2
};
--value;
closure();
closure();
console.log('Outside: value is ' + value); // 2
 最新の外部の変数の値を参照するが,内部で起こった変更は無視する

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

<?php
$value = 1;
$closure = function () use (&$value) {
    return (function ($value) {
        ++$value;
        echo "Inside: value is $value\n"; // 1, 1
    })($value); // この書き方はPHP7.x以降
}; 
--$value;
$closure();
$closure();
echo "Outside: value is $value\n"; // 0
var value = 1;
var closure = function () {
    return (function (value) {
        ++value;
        console.log('Inside: value is ' + value); // 1, 1
    })(value);
};
--value;
closure();
closure();
console.log('Outside: value is ' + value); // 0
 クロージャが作られたときの外部の変数の値を使用するが,内部で起こった変更は保持する

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

<?php
$value = 1;
$closure = (function () use ($value) {
    return function () use (&$value) {
        ++$value;
        echo "Inside: value is $value\n"; // 2, 3
    };
})(); // この書き方はPHP7.x以降
--$value;
$closure();
$closure();
echo "Outside: value is $value\n"; // 0
var value = 1;
var closure = (function (value) {
    return function () {
        ++value;
        console.log('Inside: value is ' + value); // 2, 3
    };
})(value);
--value;
closure();
closure();
console.log('Outside: value is ' + value); // 0
 クロージャが作られたときの外部の変数の値を常に使用する

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

<?php
$value = 1;
$closure = function () use ($value) {
    ++$value;
    echo "Inside: value is $value\n"; // 2, 2
};
--$value;
$closure();
$closure();
echo "Outside: value is $value\n"; // 0
var value = 1;
var closure = (function (value) {
    return function () {
        return (function (value) {
            ++value;
            console.log('Inside: value is ' + value); // 2, 2
        })(value);
    };
})(value);
--value;
closure();
closure();
console.log('Outside: value is ' + value); // 0

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/14 12:19

    ご回答ありがとうございます。

    えっと・・・これをささっと書いたとするとちょっと驚異的な速度ですね。
    (レッドコーダーの方ですか?)

    すぐには多分理解できないので、ちょっとゆっくり読ませて頂きます。

    みなさんのおかげで、なんか今日一日でだいぶパワーアップした気がします。
    多分気のせいですがw
    (テラテイル2周年ということで半分ネタというか馬鹿にされるつもりで質問したのですが)

    改めてありがとうございます。

    ここに書いてしまいますが、引き続き別の視点からの解説もお待ちしております。
    (特にざっくりした感じで分かった気になるようなのを)

    キャンセル

  • 2016/07/14 13:30

    「関数の中にある関数がクロージャです。」との回答がついたので,語弊のないように自分の回答に関して更に補足します.

    私が書いたクロージャは,確かに「関数の中にある関数」ではないものもあります.しかし,外部の値を$GLOBALSやglobal宣言でグローバル変数として参照しているわけではありません.スコープはきちんと守られています.

    なので,より正確に言い換えれば「グローバル変数を使わずとも外部の変数へアクセスできる」のがクロージャです.

    キャンセル

+1

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

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

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

追記

タグに無いので適切ではないかもしれませんが、C# の例です。

/// ボタンが押されたときに実行
private void button1_Click(object sender, EventArgs e)
{
    // クロージャ取得
    var mul = createMul(2);

    // mul(3) を実行し、それをデバッグコンソールに書き出す。
    // 作られたときの 2 が保存されているので、この結果は 6 となる。
    Debug.WriteLine(mul(3));

    mul = createMul(5);
    Debug.WriteLine(mul(3)); // この結果は 15 になる。
}

// クロージャを作って返すメソッド
private Func<int, int> createMul(int a)
{
    // f(b) とした時に a * b を返すクロージャを返す
    return b => a * b;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/14 10:42

    オブジェクト指向言語では、アルゴリズムとデータの組み合わせをオブジェクトとして表現します。
    関数型言語では、アルゴリズムとデータをセットで扱うため、クロージャという概念が生まれました。

    オブジェクトの持つ多態性などはありませんが、ものすごく大雑把に言えば、いわゆるオブジェクト、構造体などの関数型言語的表現と言えないこともないのではないかと思います。

    キャンセル

  • 2016/07/14 11:55

    あー、なるほど最後のコメントでとても理解が進みました。
    (ような気がします)

    > 構造体などの関数型言語的表現と言えないこともない

    は、ほほーと思いました。

    理解が進まなかった理由として、
    C言語があまり分からないので関数ポインタがピンと来ていない。
    (デリゲートもピンと来ない、VB、Java辺りから入った人なので)

    クロージャという概念がどこから来たのかというのが謎だった。
    (関数型というとLispとかHaskellとかですよね)
    この辺が鍵になりそうですね。

    Haskellは、文法があまりに・・・だったので一度挫折したのですが、
    また、ちょっと勉強してみるといいのかな。
    (そういえばラムダ式も関数型から来てるものですよね)
    ご丁寧な回答ありがとうございます。

    サンプルコードよりむしろその後のコメントの方が分かりやすかったです。
    質問にも書きましたが多少語弊があってもざっくりとした理解がしたかったものですから助かりました。

    (ちなみに別件ですが、某質問ではちょっと反論のような回答をしましたが悪意はございませんので、ご理解を^_^;)

    キャンセル

  • 2016/07/14 12:03 編集

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

    キャンセル

+1

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

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

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 10:50

    ご回答有り難うございます。

    最初のところがいきなり???なのですが、
    fo() = a + 1
    fc(x) = x + 1
    というのは、何の言語かわからないのですがRubyでしょうか?
    (Rubyは、タグに入れたのですが、あまり詳しくありません)
    これは、関数を定義しているのか、代入しているのか分からず私の理解では

    int a
    fo() {
    a + 1
    }

    fc(x) {
    x + 1
    }

    のように受け取りましたがあっていますでしょうか?

    閉じるというのは、なんとなく分かったのですが、これを実現しているのは、

    f1 = make_counter()

    の部分、つまり関数ポインタ?を使うから可能という理解でよろしいですか?
    (Zuishinさんの例で言うデリゲート?)
    なかなか理解が進まず申し訳ありませんがご回答いただけたら幸いです。

    キャンセル

  • 2016/07/14 11:17

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

    キャンセル

  • 2016/07/14 11:32

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

    キャンセル

+1

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

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

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

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

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

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

function Person() {
  var self = this; // `self` の代わりに `that` を選ぶこともあります。
                   // どちらかに統一するようにしましょう。
  self.age = 0;

  setInterval(function growUp() {
    // このコールバックは、期待されるオブジェクトの値を
    // `self` 変数で参照します。
    self.age++;
  }, 1000);
}


アロー関数 - JavaScript | MDN

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/17 15:55

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

    キャンセル

  • 2016/07/19 10:00

    改めて見直すと、私のコメントも正確ではないですね…

    このコメントを書いたときは、「JavaScript言語はブロックスコープを提供しないため、関数(≒クロージャ)構文を用いて同等機能を実現する」というニュアンスで解釈していました。

    少なくとも用語の問題だけを扱うと、レキシカルスコープは"生成"されるものではなく、その言語におけるルールとして"採用"するモノです。(対義語としてダイナミックスコープという考え方も存在します)

    JavaScriptの場合、「クロージャ内部から参照する/できる変数は、レキシカルスコープに基づいて決定される」となります。

    キャンセル

  • 2016/07/23 23:19

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

    キャンセル

+1

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

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

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

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

var globalValue = 0;

function globalCounter(){
    function inner(){
        return globalValue++;
    }
    return inner;
}

var g1 = globalCounter();
g1(); // -> 0
g1(); // -> 1

var g2 = globalCounter();
g2(); // -> 2
g2(); // -> 3

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

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

function counter(){
    var value = 0;
    function inner(){
        return value++;
    }
    return inner;
}

var f1 = counter();
f1(); // -> 0
f1(); // -> 1
f1(); // -> 2

var f2 = counter();
f2(); // -> 0
f2(); // -> 1

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/19 15:52

    > ぶっちゃけ個人的には、オブジェクト指向があればクロージャを使う必要はない

    なるほど、そこに落ち着きますかw
    (長いこと悩んできたんだけどなぁ・・・)

    まぁ、いずれにしてもそれを踏まえて、頭のすっきりしている時に
    もう一度他の方の回答をもう一度読みなおしてみますね。

    改めてのご回答ありがとうございました ^^

    キャンセル

  • 2016/07/22 23:05

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

    キャンセル

  • 2016/07/23 12:15

    > pasuwardoさん
    ありがとうございます。

    > クロージャーとオブジェクトはかなり別物だと思われます。

    そこの理解は大丈夫です。

    以下の時、
    function counter(){ ← ここがクラス宣言と見立てて
    var value = 0; ← ここだけ見るとメンバー変数にとても似ているということです。
    function inner(){
    return value++;
    }
    return inner;
    }



    キャンセル

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

  • ただいまの回答率 87.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る