【JavaScript, php, Python, Swift】クロージャーの概念を理解し使いこなす

クロージャーとは

「クロージャー」(クロージャ)とは、プログラミングにおいて、

『関数とその外側の設定した変数との関係を表す概念で、関数内部の関数を外部から使えるようにしてしまう記法』の一つです。

クロージャーの最も嬉しい効果として、意図しない変数へのアクセスを防ぐことができます。

これにより、変数の誤使用によるバグを減らすことができます。

クロージャーは言葉で説明するより、実際のコードでクロージャーを使わない時と使った時で比較すると、とても理解がしやすいです。

そしてクロージャーは使える場面が多く、覚えるとおすすめです。

この記事ではJavascriptで説明しますが、記事後半で、php、python、swift、go、ruby の各言語でのクロージャーの書き方をご紹介します。

まだ知らない方は、少ない学習コストで一段階レベルがあがりますので、是非覚えてみましょう。

記事を丁寧に読めば、プログラミング初心者でも覚えられる内容となっています。

目次

クロージャーを知るために、クロージャーを使わない例を先に

まず例として、クロージャーを使わない数をカウントする簡単なプログラムを書きます。

下記のような一般的なパターンの内容です。

  1. 変数を用意する
  2. 関数を定義する
  3. 関数を利用する

実際のプログラムは下記のようになります。

// 1.まず変数を宣言して初期化する
let count = 0;

// 2.数をカウントする関数を定義する
function counter() {
 //関数内で外部の変数(count)を使用する
  count = count + 1;
  console.log(count);
}

// 3.関数を利用する
counter(); // 1が出力される
counter(); // 2が出力される
counter(); // 3が出力される

このプログラムの特徴として、count変数はcounter関数の外側で定義しないと、count変数の値を保つことができません。

デメリットとしては下記のとおりです。

  • count変数とcounter関数が離れた場所にあると、関係性がわからなくなりコードが読みにくくなる
  • count変数はcounter関数以外からも簡単に書き換えができてしまうリスクがある
  • 別の数を数えたくなった時に、使いまわしができず、同じプログラムを用意することになる

プログラムとしては動くので、さほど問題はないです。

クロージャーを使った場合の例

次に同じプログラムをクロージャーを使って書き直します。

// 関数の外に設定していた変数はなくなりました

function counter() {
  // 関数内に変数を宣言し用意する
  let count = 0;

  // 関数内にcount変数を利用する関数を定義する
  function returnCounter() {
    count = count + 1;
    console.log(count);
  }

  // リターンとして、returnCounter関数の定義を返す。あくまで『定義』。
  return returnCounter;
}

// counter()を実行して、リターンとしてreturnCounter関数の『定義』を受け取ります。
const myCounter = counter();

// myCounterに()をつけて『returnCounter()』として実行しています。
myCounter(); // 1が出力される
myCounter(); // 2が出力される
myCounter(); // 3が出力される

いろいろと相違点が多いです。

クロージャーを使用したら何が変わったのかを注意深くみてみましょう。

1.外部変数がなくなり、counter関数のローカル変数となる

counter関数の外にあった、count変数がなくなりました。

そしてcount変数はcounter関数の中に入り、ローカル変数となってcounter関数の中でのみ利用することなりました。

これにより、変数と関数がセットで移動できるようになったので、もうコード上で遠く離ればなれになることはありません。

2.関数内にcount変数を利用する関数を定義する

関数内部の関数(ここではreturnCounter関数と名付けましたが、名称はなんでもいいです)からcount変数を書き換えられるようにします。

count変数はローカル変数としてcounter関数内にあるので、counter関数内でしか利用することができません。

他のどこからもアクセスできなくなっており、唯一、returnCounter関数だけがcount変数を書き換えることができます。

よって、count変数は関数内の外部からアクセスすることができない安全な変数となりました。

オブジェクト指向を知っている方にとっては、この場合においてのクロージャーの機能はクラスのプライベートプロパティにも似ていると思うかもしれません。

まさにどちらも、変数やデータのカプセル化を目的としています。

クロージャーはクラスを作成することなく、関数のみで実現できます。

3.リターンにcounter関数内のreturnCounter関数の定義を返す

counter関数が実行された時、何をリターンする(返す)のかというと、returnCounter関数の『定義』です。

もう少しわかりやすく解説できるとすれば

counter();をしたら

function returnCounter() {
count = count + 1;
console.log(count);
}

をという内容の関数の定義を返すだけとなり、関数の実行はしません

4.関数の定義をうけとるmyCounter

const myCounter = counter();として、myCounterreturnCounterの定義を代入しています。

これにより、myCounterにはreturnCounter関数の『定義』が入っています。

このmyCounterに『()』をつけると、myCounter()となり、returnCounter関数を実行することと同じ意味になります。

これにより、関数の外部から内部の関数を使うことができるようになります。

returnCounter関数はもともとcounter関数の内部の関数です。

counter関数の外部から利用できる不思議な関数に変わります。

もうひとつ注目するポイントがあります。

  • count変数が定義されるのは、counter関数の実行時のみです。

myCounter();を何度実行しても、returnCounterが実行されるだけなので、let count = 0;の処理は実行されません。

よって、myCounter();をするたびに、count = 0 に初期化されることはありません。

これにより、初回定義時からcount変数が値を保ったまま存在し続けてくれます。

また、myCounter2 = counter();などとすれば独立した複数のカウンターを作成できます。

それぞれのmyCounter1、myCounter2にはそれぞれのcount変数が保持されているので、互いにcount変数の影響を与えません。

下記のようにそれぞれのカウンターは独立した状態を持つことができます。

const myCounter1 = createCounter();
const myCounter2 = createCounter();

myCounter1(); // 1
myCounter1(); // 2
myCounter1(); // 3

myCounter2(); // 1

すごいぞクロージャー

最初にあげたクロージャーを使わずに書いた以下のデメリットをすべて解決してくれました。

  • count変数とcounter関数が離れた場所にあると、関係性がわからなくなりコードが読みにくくなる
  • count変数はcounter関数以外からも簡単に書き換えができてしまうリスクがある
  • 別の数を数えたくなった時に、使いまわしができず、同じプログラムを用意することになる

クロージャーの概念は、1960年代にラムダ計算という数学的理論から生まれたらしく、1970年代に開発されたプログラミング言語Lispにおいて、最初に実装されたのことです。

関数の使い方が巧妙すぎてすごいです。

クロージャーが学べるおすすめのUdemy講座

僕がはじめてクロージャーを理解したのはUdemy講座にある【codeMafia(コードマフィア)】さん【JS】ガチで学びたい人のためのJavaScriptメカニズム講座でした。

動画ではクロージャーも取り扱っており、わかりやすく解説しています。

また、クロージャー使った動的な関数作成がこの記事よりも詳しいと思います。

用語の前提知識も必要となってしまいますが、どこよりもわかりやすかったです。

📝詳細はこちら
【JS】ガチで学びたい人のためのJavaScriptメカニズム

ちなみにこのJavascript講座はJSのバイブル的な存在で、常に手元にあると便利でおすすめです。

また、内容は初心者にとっては難しめですので、この講座を基準にして、わからない場合は、より簡単な例、より簡単な解説、簡単な参考書、などレベルを下げて学習するといいと思います。

そして理解できてから再びこの講座を見ると、最初はわからなかった概念などが腑に落ちるようにわかるようになります。

この講座は初心者から中級者への架け橋となるようなとてもいい講座です。

また、「30日間の返金保証」があるので、もし気になったら一度受講を検討してみてください。

各言語でのクロージャー記法をご紹介

クロージャーはJavaScriptだけではなく、様々な言語でも書くことができます。

言語特有の書き方が違うだけで、考え方とプログラムの組み立て方はJavascriptと同じです。

以下ではphp, Python, Swift, go, Rubyでの書き方をご紹介いたします。

PHPで書くクロージャー

<?php

function counter() {
    $count = 0;

    return function() use (&$count) {
        $count += 1;
        echo $count . PHP_EOL;
    };
}

$myCounter = counter();

$myCounter(); // 1 が出力される
$myCounter(); // 2 が出力される
$myCounter(); // 3 が出力される

?>

このPHPのコードは、JavaScriptのコードと同様に、counter 関数内でカウント変数を定義し、return で無名関数(定義)を返しています。

無名関数は use キーワードを使って、counter 関数内の $count 変数を参照しています。

&$count& 記号は、参照渡しを示します。

これにより、$count 変数の値がカウンターとして機能するようになります。

$myCounter 変数に counter() 関数の戻り値(無名関数)を代入し、その後 $myCounter() を実行することで、カウントが増加していくことが確認できます。

Pythonで書くクロージャー

def counter():
    count = 0

    def return_counter():
        nonlocal count
        count += 1
        print(count)

    return return_counter

my_counter = counter()

my_counter()  # 1が出力される
my_counter()  # 2が出力される
my_counter()  # 3が出力される

このPythonのコードは、JavaScriptのコードと同様に、counter 関数内でカウント変数を定義し、return で内部関数 return_counter を返しています。

内部関数は nonlocal キーワードを使って、counter 関数内の count 変数を参照しています。

これにより、count 変数の値がカウンターとして機能するようになります。

my_counter 変数に counter() 関数の戻り値(内部関数)を代入し、その後 my_counter() を実行することで、カウントが増加していくことが確認できます。

Swiftで書くクロージャー

func counter() -> () -> Void {
    var count = 0

    func returnCounter() {
        count += 1
        print(count)
    }

    return returnCounter
}

let myCounter = counter()

myCounter()  // 1が出力される
myCounter()  // 2が出力される
myCounter()  // 3が出力される

このSwiftのコードは、JavaScriptのコードと同様に、counter 関数内でカウント変数を定義し、return で内部関数 returnCounter を返しています。

内部関数は、counter 関数内の count 変数を参照しています。

これにより、count 変数の値がカウンターとして機能するようになります。

myCounter 変数に counter() 関数の戻り値(内部関数)を代入し、その後 myCounter() を実行することで、カウントが増加していくことが確認できます。

Goで書くクロージャー

package main

import "fmt"

func counter() func() {
    count := 0

    returnCounter := func() {
        count += 1
        fmt.Println(count)
    }

    return returnCounter
}

func main() {
    myCounter := counter()

    myCounter()  // 1が出力される
    myCounter()  // 2が出力される
    myCounter()  // 3が出力される
}

このGoのコードは、JavaScriptのコードと同様に、counter 関数内でカウント変数を定義し、return で無名関数(クロージャー)を返しています。無名関数は、counter 関数内の count 変数を参照しています。これにより、count 変数の値がカウンターとして機能するようになります。

myCounter 変数に counter() 関数の戻り値(無名関数)を代入し、その後 myCounter() を実行することで、カウントが増加していくことが確認できます。

Rubyで書くクロージャー

def counter
  count = 0

  return lambda do
    count += 1
    puts count
  end
end

my_counter = counter

my_counter.call  # 1が出力される
my_counter.call  # 2が出力される
my_counter.call  # 3が出力される

このRubyのコードは、JavaScriptのコードと同様に、counter メソッド内でカウント変数を定義し、return でラムダ(クロージャー)を返しています。

ラムダは、counter メソッド内の count 変数を参照しています。

これにより、count 変数の値がカウンターとして機能するようになります。

my_counter 変数に counter() メソッドの戻り値(ラムダ)を代入し、その後 my_counter.call を実行することで、カウントが増加していくことが確認できます。

クロージャーのまとめ

クロージャーは、関数とその関数がアクセスできる外部変数の組み合わせで、プログラミングにおいて非常に有用な概念です。

クロージャーにより、変数のスコープを制限し、カプセル化を実現できるため、コードの可読性や保守性が向上します。

また、クロージャーは様々なプログラミング言語で実装されており、それぞれの言語で独自の文法や記法が提供されています。

この記事を通じて、クロージャーの基本的な概念と使用方法について理解を深めることができたことでしょう。

これからは、プログラムの設計や機能追加において、クロージャーを活用することで、より効率的かつ安全なコードを書くことができるでしょう。

僕は食わず嫌いでクロージャーを覚えるのを後回しにしていました。

はじめて覚えた時はもっとこの概念を早く知っておけばと思いました。

最後までお付き合いいただきありがとうございました。

ブログ、ツイッター、Qiita、卒論など、お気軽にリンクしてください。それではまた!