コンテンツにスキップ

プログラミング/クロージャ

出典: フリー教科書『ウィキブックス(Wikibooks)』

クロージャとは

[編集]

クロージャ(Closure)は、その定義環境のスコープ内の変数にアクセスできる関数オブジェクトです。本質的に、クロージャは「周囲の状態を記憶する関数」と言えます。関数と、その関数が生成された環境を一緒にパッケージ化する仕組みです。

クロージャの主な特徴

[編集]
  • 変数の保持: 関数が作成された環境の変数にアクセス可能
  • 状態のカプセル化: プライベートな状態を安全に管理
  • 関数の動的生成: 実行時に関数を動的に生成可能
  • 高度な関数型プログラミング技法

クロージャの基本的な仕組み

[編集]

JavaScript

[編集]
function createCounter() {
    let count = 0;  // クロージャによってキャプチャされる変数
    return function() {
        return ++count;  // 外部スコープの変数にアクセス
    };
}

const counter = createCounter();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3

Ruby

[編集]
def create_counter
  count = 0
  lambda do
    count += 1  # 外部スコープの変数をキャプチャ
  end
end

counter = create_counter()
puts counter.call  # 1
puts counter.call  # 2
puts counter.call  # 3

Rust

[編集]
fn create_counter() -> impl Fn() -> i32 {
    let mut count = 0;
    move || {
        count += 1;
        count
    }
}

fn main() {
    let counter = create_counter();
    println!("{}", counter());  // 1
    println!("{}", counter());  // 2
    println!("{}", counter());  // 3
}

クロージャの高度な利用

[編集]

関数のカスタマイズ

[編集]

Swift

[編集]
func multiplier(factor: Int) -> (Int) -> Int {
    return { number in
        return number * factor
    }
}

let doubler = multiplier(factor: 2)
let tripler = multiplier(factor: 3)

print(doubler(5))  // 10
print(tripler(5))  // 15

プライベート変数のシミュレーション

[編集]

Kotlin

[編集]
class BankAccount {
    private var balance = 0

    fun deposit(amount: Int): () -> Int {
        return {
            balance += amount
            balance
        }
    }
}

fun main() {
    val account = BankAccount()
    val deposit100 = account.deposit(100)
    println(deposit100())  // 100
    println(deposit100())  // 200
}

イテレータの実装

[編集]

Scala

[編集]
def infiniteSequence(): () => Int = {
    var current = 0
    () => {
        current += 1
        current
    }
}

val generator = infiniteSequence()
println(generator())  // 1
println(generator())  // 2
println(generator())  // 3

クロージャのユースケース

[編集]

コールバックと非同期処理

[編集]

TypeScript

[編集]
function fetchData(url: string) {
    return new Promise<string>((resolve) => {
        setTimeout(() => {
            resolve(`データ: ${url}`);
        }, 1000);
    });
}

async function processUrls(urls: string[]) {
    const results = await Promise.all(
        urls.map(url => {
            // クロージャによってurlをキャプチャ
            return fetchData(url);
        })
    );
    console.log(results);
}

メモ化と計算の最適化

[編集]

Go

[編集]
func memoizedFibonacci() func(int) int {
    cache := make(map[int]int)
    return func(n int) int {
        if val, exists := cache[n]; exists {
            return val
        }
        var result int
        if n <= 1 {
            result = n
        } else {
            result = memoizedFibonacci()(n-1) + memoizedFibonacci()(n-2)
        }
        cache[n] = result
        return result
    }
}

部分適用と関数の変換

[編集]

Haskell

[編集]
multiply :: Int -> Int -> Int
multiply x y = x * y

-- 部分適用によるクロージャの利用
double :: Int -> Int
double = multiply 2

main :: IO ()
main = do
    print (double 5)  // 出力: 10
    print (double 7)  // 出力: 14

クロージャの落とし穴と注意点

[編集]

メモリリークの可能性

[編集]

クロージャは外部変数への参照を保持するため、不適切に使用するとメモリリークを引き起こす可能性があります。

パフォーマンスへの影響

[編集]
  • 頻繁な変数キャプチャはメモリとパフォーマンスに影響
  • 大量のクロージャ生成は慎重に行う必要がある

クロージャのベストプラクティス

[編集]
  • 状態の管理に活用
  • 関数の動的生成
  • 不変性を重視
  • メモリ使用に注意
  • シンプルで明確な実装を心がける

まとめ

[編集]

クロージャは、関数型プログラミングにおける強力で柔軟な概念です。状態をカプセル化し、関数に高い表現力を与える重要な技術です。