.NET を超えて: Python、Java、C++ における LINQ 相当機能の発見

Microsoft .NET エコシステム内で作業する開発者は、Language Integrated Query (LINQ) に大きく依存していることがよくあります。この強力な機能により、コレクション、データベース、XML など、さまざまなデータソースに対して、C# や VB.NET にネイティブであるかのように感じられる構文を使用してクエリを実行できます。これにより、データ操作は命令的なループから宣言的なステートメントに変換され、コードの可読性と簡潔性が向上します。しかし、開発者が .NET の領域外に出た場合はどうなるでしょうか?Python、Java、C++ などの言語で、プログラマーは同様の表現力豊かなデータクエリ機能を実現するにはどうすればよいでしょうか?幸いなことに、LINQ を支えるコアコンセプトは .NET に限定されたものではなく、プログラミングの世界全体で堅牢な同等機能や代替手段が存在します。

LINQ の簡単な復習

代替案を探る前に、LINQ が何を提供するかを簡単に思い出してみましょう。.NET Framework 3.5 で導入された LINQ は、その起源に関係なくデータをクエリするための統一された方法を提供します。SQL ステートメントに似たクエリ式を言語に直接統合します。主な機能は次のとおりです。

  • 宣言的な構文: データをステップバイステップで取得する方法ではなく、欲しいデータを指定します。
  • 型安全性: クエリは(ほとんどの場合)コンパイル時にチェックされ、実行時エラーを削減します。
  • 標準クエリ演算子: Where (フィルタリング)、Select (射影/マッピング)、OrderBy (ソート)、GroupBy (グループ化)、JoinAggregate など、豊富なメソッドセット。
  • 遅延実行: 通常、クエリは結果が実際に列挙されるまで実行されず、最適化と合成を可能にします。
  • 拡張性: プロバイダーにより、LINQ はさまざまなデータソース(オブジェクト、SQL、XML、エンティティ)で機能します。

var results = collection.Where(x => x.IsValid).Select(x => x.Name); と書ける利便性は否定できません。他の言語が同様のタスクにどのように取り組むかを見てみましょう。

Python における LINQ の取り組み: 内包表記とライブラリ

Python は、慣用的な組み込み機能から専用ライブラリまで、LINQ のような機能を提供するいくつかのメカニズムを提供します。これらのアプローチにより、開発者はフィルタリング、マッピング、集計を簡潔で読みやすい方法で実行できます。

リスト内包表記とジェネレータ式

単純なフィルタリング (Where) とマッピング (Select) を実現するための最も Pythonic な方法は、多くの場合、リスト内包表記またはジェネレータ式を使用することです。

  • リスト内包表記: メモリ内に新しいリストをすぐに作成し、完全な結果セットがすぐに必要な場合に適しています。
    numbers = [1, 2, 3, 4, 5, 6]
    # LINQ: numbers.Where(n => n % 2 == 0).Select(n => n * n)
    squared_evens = [n * n for n in numbers if n % 2 == 0]
    # 結果: [4, 16, 36]
    
  • ジェネレータ式: 要求に応じて値を生成するイテレータを作成し、メモリを節約し、LINQ と同様の遅延実行を可能にします。角括弧の代わりに括弧を使用します。
    numbers = [1, 2, 3, 4, 5, 6]
    # LINQ: numbers.Where(n => n % 2 == 0).Select(n => n * n)
    squared_evens_gen = (n * n for n in numbers if n % 2 == 0)
    # 結果を得るには、反復処理を行います (例: list(squared_evens_gen))
    # 値は反復処理中に必要に応じてのみ計算されます。
    

組み込み関数と itertools

多くの標準 LINQ 演算子には、Python の組み込み関数または強力な itertools モジュールに直接または近い対応物があります。

  • any(), all(): LINQ の Any および All に直接対応し、要素全体の条件をチェックします。
    fruit = ['apple', 'orange', 'banana']
    # LINQ: fruit.Any(f => f.Contains("a"))
    any_a = any("a" in f for f in fruit) # True
    # LINQ: fruit.All(f => f.Length > 3)
    all_long = all(len(f) > 3 for f in fruit) # True
    
  • min(), max(), sum(): LINQ の集計メソッドに似ています。イテラブルに対して直接操作するか、ジェネレータ式を受け取ることができます。
    numbers = [1, 5, 2, 8, 3]
    # LINQ: numbers.Max()
    maximum = max(numbers) # 8
    # LINQ: numbers.Where(n => n % 2 != 0).Sum()
    odd_sum = sum(n for n in numbers if n % 2 != 0) # 1 + 5 + 3 = 9
    
  • filter(), map(): Where および Select の関数型対応物。Python 3 ではイテレータを返し、遅延評価を促進します。
    numbers = [1, 2, 3, 4]
    # LINQ: numbers.Where(n => n > 2)
    filtered_iter = filter(lambda n: n > 2, numbers) # 反復時に 3, 4 を生成
    # LINQ: numbers.Select(n => n * 2)
    mapped_iter = map(lambda n: n * 2, numbers) # 反復時に 2, 4, 6, 8 を生成
    
  • sorted(): OrderBy に対応します。ソート基準を指定するためのオプションの key 関数を受け取り、新しいソート済みリストを返します。
    fruit = ['pear', 'apple', 'banana']
    # LINQ: fruit.OrderBy(f => f.Length)
    sorted_fruit = sorted(fruit, key=len) # ['pear', 'apple', 'banana']
    
  • itertools.islice(iterable, stop) または itertools.islice(iterable, start, stop[, step]): Take および Skip を実装します。イテレータを返します。
    from itertools import islice
    numbers = [0, 1, 2, 3, 4, 5]
    # LINQ: numbers.Take(3)
    first_three = list(islice(numbers, 3)) # [0, 1, 2]
    # LINQ: numbers.Skip(2)
    skip_two = list(islice(numbers, 2, None)) # [2, 3, 4, 5]
    # LINQ: numbers.Skip(1).Take(2)
    skip_one_take_two = list(islice(numbers, 1, 3)) # [1, 2]
    
  • itertools.takewhile(), itertools.dropwhile(): TakeWhile および SkipWhile と同等で、述語に基づいて操作します。
    from itertools import takewhile, dropwhile
    numbers = [2, 4, 6, 7, 8, 10]
    # LINQ: numbers.TakeWhile(n => n % 2 == 0)
    take_evens = list(takewhile(lambda n: n % 2 == 0, numbers)) # [2, 4, 6]
    # LINQ: numbers.SkipWhile(n => n % 2 == 0)
    skip_evens = list(dropwhile(lambda n: n % 2 == 0, numbers)) # [7, 8, 10]
    
  • itertools.groupby(): GroupBy に似ていますが、要素が正しくグループ化されるためには、入力イテラブルが最初にグループ化キーでソートされている必要があります。 (key, group_iterator) のペアを生成するイテレータを返します。
    from itertools import groupby
    
    fruit = ['apple', 'apricot', 'banana', 'blueberry', 'cherry']
    # ほとんどの場合、groupby が期待どおりに機能するためには、まずキーでソートする必要があります
    keyfunc = lambda f: f[0] # 最初の文字でグループ化
    sorted_fruit = sorted(fruit, key=keyfunc)
    # LINQ: fruit.GroupBy(f => f[0])
    grouped_fruit = groupby(sorted_fruit, key=keyfunc)
    for key, group_iter in grouped_fruit:
        print(f"{key}: {list(group_iter)}")
    # 出力:
    # a: ['apple', 'apricot']
    # b: ['banana', 'blueberry']
    # c: ['cherry']
    
  • set(): Distinct に使用できますが、元の順序は保持されません。
    numbers = [1, 2, 2, 3, 1, 4, 3]
    # LINQ: numbers.Distinct()
    distinct_numbers_set = set(numbers) # 順序は保証されない、例: {1, 2, 3, 4}
    distinct_numbers_list = list(distinct_numbers_set) # 例: [1, 2, 3, 4]
    
    # 順序を保持する distinct の場合:
    seen = set()
    distinct_ordered = [x for x in numbers if not (x in seen or seen.add(x))] # [1, 2, 3, 4]
    

py-linq ライブラリ

.NET の LINQ の特定のメソッドチェーン構文と命名規則を好む開発者向けに、py-linq ライブラリが直接的な移植を提供します。インストール (pip install py-linq) 後、コレクションを Enumerable オブジェクトでラップします。

from py_linq import Enumerable

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f'{self.name} ({self.age})'

people = [Person('Alice', 30), Person('Bob', 20), Person('Charlie', 25)]

e = Enumerable(people)

# LINQ: people.Where(p => p.age > 21).OrderBy(p => p.name).Select(p => p.name)
results = e.where(lambda p: p.age > 21)\
             .order_by(lambda p: p.name)\
             .select(lambda p: p.name)\
             .to_list()
# 結果: ['Alice', 'Charlie']

# Count の例
# LINQ: people.Count(p => p.age < 25)
young_count = e.count(lambda p: p.age < 25) # 1 (Bob)

py-linq ライブラリは標準クエリ演算子の大部分を実装しており、.NET 開発から移行する、または .NET 開発と並行して作業する開発者にとって使い慣れたインターフェースを提供します。

その他のライブラリ

pipe ライブラリも別の選択肢であり、パイプ演算子 (|) を使用して操作を連鎖させる関数型アプローチを提供します。一部の開発者は、複雑なデータフローに対してこれが非常に読みやすく表現力豊かであると感じています。

Java Streams: 標準的な LINQ 相当機能

Java 8 以降、Java における主要かつ慣用的な LINQ 相当機能は、間違いなく Streams API (java.util.stream) です。これは、要素のシーケンスを処理するための流れるような宣言的な方法を提供し、LINQ の哲学と機能を密接に反映しており、標準ライブラリ内で LINQ のような機能を現実のものとしています。

Java Streams のコアコンセプト

  • ソース: ストリームは、コレクション (list.stream())、配列 (Arrays.stream(array))、I/O チャネル、ジェネレータ関数 (Stream.iterate, Stream.generate) などのデータソースに対して操作します。
  • 要素: ストリームは要素のシーケンスを表します。それ自体はデータを格納せず、ソースからの要素を処理します。
  • 集計操作: filter (Where)、map (Select)、sorted (OrderBy)、distinctlimit (Take)、skip (Skip)、reduce (Aggregate)、collect (ToList, ToDictionary など) などの豊富な操作セットをサポートします。
  • パイプライン: 中間操作 (例: filter, map, sorted) は新しいストリームを返し、それらを連鎖させてクエリを表すパイプラインを形成できます。
  • 内部反復: 明示的なループでコレクションを反復処理する (外部反復) のとは異なり、ストリームライブラリは、終端操作が呼び出されたときに反復プロセスを内部的に処理します。
  • 遅延評価と短絡評価: 中間操作は遅延的です。計算は終端操作がパイプラインの実行をトリガーするまで開始されません。短絡操作 (例: limit, anyMatch, findFirst) は、結果が決定されるとすぐに処理を停止でき、効率を向上させます。
  • 終端操作: ストリームパイプラインの実行をトリガーし、結果 (例: collect, count, sum, findFirst, anyMatch) または副作用 (例: forEach) を生成します。

Stream 操作の例

Java Streams を使用した LINQ 相当機能を見てみましょう。

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;

// サンプルデータクラス
class Transaction {
    int id; String type; int value;
    Transaction(int id, String type, int value) { this.id = id; this.type = type; this.value = value; }
    int getId() { return id; }
    String getType() { return type; }
    int getValue() { return value; }
    @Override public String toString() { return "ID:" + id + " Type:" + type + " Value:" + value; }
}

public class StreamExample {
    public static void main(String[] args) {
        List<Transaction> transactions = Arrays.asList(
            new Transaction(1, "GROCERY", 50),
            new Transaction(2, "UTILITY", 150),
            new Transaction(3, "GROCERY", 75),
            new Transaction(4, "RENT", 1200),
            new Transaction(5, "GROCERY", 25)
        );

        // --- フィルタリング (Where) ---
        // LINQ: transactions.Where(t => t.getType() == "GROCERY")
        List<Transaction> groceryTransactions = transactions.stream()
            .filter(t -> "GROCERY".equals(t.getType()))
            .collect(Collectors.toList());
        // 結果: ID が 1, 3, 5 のトランザクションを含む

        // --- マッピング (Select) ---
        // LINQ: transactions.Select(t => t.getId())
        List<Integer> transactionIds = transactions.stream()
            .map(Transaction::getId) // メソッド参照を使用
            .collect(Collectors.toList());
        // 結果: [1, 2, 3, 4, 5]

        // --- ソート (OrderBy) ---
        // LINQ: transactions.OrderByDescending(t => t.getValue())
        List<Transaction> sortedByValueDesc = transactions.stream()
            .sorted(comparing(Transaction::getValue).reversed())
            .collect(Collectors.toList());
        // 結果: 値で降順にソートされたトランザクション: [ID:4, ID:2, ID:3, ID:1, ID:5]

        // --- 操作の組み合わせ ---
        // 食料品トランザクションの ID を、値で降順にソートして検索
        // LINQ: transactions.Where(t => t.getType() == "GROCERY").OrderByDescending(t => t.getValue()).Select(t => t.getId())
        List<Integer> groceryIdsSortedByValueDesc = transactions.stream()
            .filter(t -> "GROCERY".equals(t.getType()))           // Where
            .sorted(comparing(Transaction::getValue).reversed()) // OrderByDescending
            .map(Transaction::getId)                             // Select
            .collect(Collectors.toList());                       // 実行して収集
        // 結果: [3, 1, 5] (値 75, 50, 25 に対応する ID)

        // --- その他の一般的な操作 ---
        // AnyMatch
        // LINQ: transactions.Any(t => t.getValue() > 1000)
        boolean hasLargeTransaction = transactions.stream()
            .anyMatch(t -> t.getValue() > 1000); // true (RENT トランザクション)

        // FindFirst / FirstOrDefault 相当
        // LINQ: transactions.FirstOrDefault(t => t.getType() == "UTILITY")
        Optional<Transaction> firstUtility = transactions.stream()
            .filter(t -> "UTILITY".equals(t.getType()))
            .findFirst(); // ID:2 のトランザクションを含む Optional を返す

        firstUtility.ifPresent(t -> System.out.println("Found: " + t)); // 存在する場合、見つかったトランザクションを出力

        // Count
        // LINQ: transactions.Count(t => t.getType() == "GROCERY")
        long groceryCount = transactions.stream()
            .filter(t -> "GROCERY".equals(t.getType()))
            .count(); // 3

        // Sum (効率のため特殊化された数値ストリームを使用)
        // LINQ: transactions.Sum(t => t.getValue())
        int totalValue = transactions.stream()
            .mapToInt(Transaction::getValue) // IntStream に変換
            .sum(); // 1500

        System.out.println("Total value: " + totalValue);
    }
}

Parallel Streams (並列ストリーム)

Java Streams は、.stream().parallelStream() に置き換えるだけで、マルチコアプロセッサでの潜在的なパフォーマンス向上のために簡単に並列化できます。Streams API は、タスクの分解とスレッド管理を内部的に処理します。

// 例: 並列フィルタリングとマッピング
List<Integer> parallelResult = transactions.parallelStream() // 並列ストリームを使用
    .filter(t -> t.getValue() > 100) // 並列処理
    .map(Transaction::getId)         // 並列処理
    .collect(Collectors.toList());   // 結果を結合
// 結果: [2, 4] (コレクション前の順序はシーケンシャルストリームと比較して異なる場合がある)

並列化にはオーバーヘッドが伴い、特に単純な操作や小さなデータセットでは必ずしも高速になるとは限らないことに注意してください。ベンチマークが推奨されます。

その他の Java ライブラリ

Java 8 Streams は Java における標準であり、一般的に推奨される LINQ 相当機能ですが、他のライブラリも存在します。

  • jOOQ: Fluent API を使用して Java で型安全な SQL クエリを構築することに焦点を当てており、LINQ to SQL を模倣したデータベース中心の操作に優れています。
  • Querydsl: jOOQ と同様に、JPA、SQL、NoSQL データベースを含むさまざまなバックエンド向けの型安全なクエリ構築を提供します。
  • joquery, Lambdaj: LINQ のような機能を提供する古いライブラリですが、Java 8 以降は組み込みの Streams API に大部分が取って代わられています。

C++ における LINQ スタイルクエリへのアプローチ

C++ には、.NET の LINQ や Java の Streams に直接匹敵する言語統合クエリ機能はありません。しかし、C++ LINQ 相当機能や C++ で LINQ パターンを実装する方法を探している開発者は、標準ライブラリ機能、強力なサードパーティライブラリ、およびモダン C++ の慣用句を組み合わせることで、同様の結果を得ることができます。

標準テンプレートライブラリ (STL) アルゴリズム

<algorithm> および <numeric> ヘッダーは、イテレータ範囲 (begin, end) で動作する関数の基本的なツールキットを提供します。これらは C++ におけるデータ操作の構成要素です。

#include <vector>
#include <numeric>
#include <algorithm>
#include <iostream>
#include <string>
#include <iterator> // std::back_inserter のため

struct Product {
    int id;
    double price;
    std::string category;
};

int main() {
    std::vector<Product> products = {
        {1, 10.0, "A"}, {2, 25.0, "B"}, {3, 5.0, "A"}, {4, 30.0, "A"}
    };

    // --- フィルタリング (Where) ---
    // LINQ: products.Where(p => p.category == "A")
    std::vector<Product> categoryA;
    std::copy_if(products.begin(), products.end(), std::back_inserter(categoryA),
                 [](const Product& p){ return p.category == "A"; });
    // categoryA には ID 1, 3, 4 の製品が含まれる

    // --- マッピング (Select) ---
    // LINQ: products.Select(p => p.price)
    std::vector<double> prices;
    prices.reserve(products.size()); // 領域を予約
    std::transform(products.begin(), products.end(), std::back_inserter(prices),
                   [](const Product& p){ return p.price; });
    // prices には [10.0, 25.0, 5.0, 30.0] が含まれる

    // --- ソート (OrderBy) ---
    // LINQ: products.OrderBy(p => p.price)
    // 注意: std::sort は元のコンテナを変更する
    std::vector<Product> sortedProducts = products; // ソート用にコピーを作成
    std::sort(sortedProducts.begin(), sortedProducts.end(),
              [](const Product& a, const Product& b){ return a.price < b.price; });
    // sortedProducts は次のようになる: [ {3, 5.0}, {1, 10.0}, {2, 25.0}, {4, 30.0} ]

    // --- 集計 (Sum) ---
    // LINQ: products.Where(p => p.category == "A").Sum(p => p.price)
    double sumCategoryA = std::accumulate(products.begin(), products.end(), 0.0,
        [](double current_sum, const Product& p){
            return (p.category == "A") ? current_sum + p.price : current_sum;
        });
    // sumCategoryA = 10.0 + 5.0 + 30.0 = 45.0

    // --- 検索 (FirstOrDefault 相当) ---
    // LINQ: products.FirstOrDefault(p => p.id == 3)
    auto found_it = std::find_if(products.begin(), products.end(),
                                 [](const Product& p){ return p.id == 3; });
    if (found_it != products.end()) {
        std::cout << "Found product with ID 3, price: " << found_it->price << std::endl;
    } else {
        std::cout << "Product with ID 3 not found." << std::endl;
    }

    return 0;
}

強力で効率的ですが、STL アルゴリズムを直接使用すると冗長になる可能性があります。操作を連鎖させるには、多くの場合、中間コンテナを作成するか、より複雑なファンクタ合成テクニックを採用する必要があります。

Range ベースのライブラリ (例: range-v3, C++20 std::ranges)

Eric Niebler の range-v3 (C20 で導入された標準の std::ranges に大きな影響を与えた) のようなモダン C ライブラリは、LINQ や Java Streams の精神にはるかに近い、合成可能なパイプベースの構文 (|) を提供します。

#include <vector>
#include <string>
#include <iostream>
#ifdef USE_RANGES_V3 // range-v3 を使用する場合はこれを定義、それ以外の場合は std::ranges を使用
#include <range/v3/all.hpp>
namespace ranges = ::ranges;
#else // <ranges> をサポートする C++20 以降を想定
#include <ranges>
#include <numeric> // ranges で accumulate を使用するため
namespace ranges = std::ranges;
namespace views = std::views;
#endif

// 前の例の Product 構造体を想定...

int main() {
    std::vector<Product> products = {
        {1, 10.0, "A"}, {2, 25.0, "B"}, {3, 5.0, "A"}, {4, 30.0, "A"}
    };

    // LINQ: products.Where(p => p.category == "A").Select(p => p.price).Sum()
    auto categoryAView = products
        | ranges::views::filter([](const Product& p){ return p.category == "A"; })
        | ranges::views::transform([](const Product& p){ return p.price; });

    #ifdef USE_RANGES_V3
        double sumCategoryA_ranges = ranges::accumulate(categoryAView, 0.0);
    #else // C++20 std::ranges は accumulate に明示的な begin/end が必要
        double sumCategoryA_ranges = std::accumulate(categoryAView.begin(), categoryAView.end(), 0.0);
    #endif

    std::cout << "Sum Category A (ranges): " << sumCategoryA_ranges << std::endl; // 45.0

    // LINQ: products.Where(p => p.price > 15).OrderBy(p => p.id).Select(p => p.id)
    // 注意: ranges でのソートは通常、最初にコンテナに収集するか、
    // 利用可能で適切な特定の range アクション/アルゴリズムを使用する必要がある。
    auto expensiveProducts = products
        | ranges::views::filter([](const Product& p){ return p.price > 15.0; });

    // ソートするためにベクターに収集
    std::vector<Product> expensiveVec;
    #ifdef USE_RANGES_V3
        ranges::copy(expensiveProducts, std::back_inserter(expensiveVec));
    #else
        ranges::copy(expensiveProducts.begin(), expensiveProducts.end(), std::back_inserter(expensiveVec));
    #endif

    ranges::sort(expensiveVec, [](const Product& a, const Product& b){ return a.id < b.id; }); // ベクターをソート

    auto ids_expensive_sorted = expensiveVec
        | ranges::views::transform([](const Product& p){ return p.id; }); // ID のビューを作成

    std::cout << "Expensive Product IDs (Sorted): ";
    for(int id : ids_expensive_sorted) { // 最終的なビューを反復処理
        std::cout << id << " "; // 2 4
    }
    std::cout << std::endl;

    return 0;
}

Range ライブラリは、従来の STL アルゴリズムと比較して、表現力、遅延性 (ビュー経由)、および合成可能性が大幅に向上しており、C++ における LINQ 相当機能として強力な候補となっています。

専用の C++ LINQ ライブラリ

いくつかのサードパーティライブラリは、C++ で LINQ 構文を直接模倣することを具体的に目指しています。

  • cpplinq: 使い慣れたメソッドチェーンまたはクエリ構文スタイルで多くの LINQ 演算子 (from, where, select, orderBy など) を提供するヘッダーオンリーライブラリ。
  • その他: GitHub 上のさまざまなプロジェクトが、機能の完全性が異なる C++ LINQ 相当機能のさまざまな実装を提供しています。

これらのライブラリは、すでに C# LINQ に慣れている開発者にとって魅力的ですが、外部依存関係を導入し、必ずしも標準の C++ プラクティスとシームレスに統合したり、標準アルゴリズムや確立された Range ライブラリと同じ潜在的なパフォーマンス最適化を提供したりするとは限りません。

他の言語での簡単な言及

コレクションを宣言的にクエリするという基本的な概念は広く普及しています。

  • JavaScript: モダン JavaScript は、filter()map()reduce()sort()find()some()every() などの強力な配列メソッドを提供し、LINQ に似た関数型スタイルの連鎖可能な操作を可能にします。 lodash のようなライブラリは、さらに広範なユーティリティを提供します。
  • Perl: grep (フィルタリング用) や map (変換用) などのコア関数は、不可欠なリスト処理機能を提供します。
  • PHP: 配列関数 (array_filter, array_map, array_reduce) やオブジェクト指向コレクションライブラリ (例: Laravel Collections, Doctrine Collections) は、同様の宣言的なデータ操作機能を提供します。
  • 関数型言語 (F#, Haskell, Scala): これらの言語は、多くの場合、第一級の概念として強力で深く統合されたシーケンス処理機能を持っています。LINQ 自体は関数型プログラミングからインスピレーションを得ました。 .NET 言語である F# には、C# と非常によく似た独自のネイティブクエリ式構文さえあります。

まとめ

LINQ のコア原則である、宣言的なデータクエリ、関数型変換、遅延評価、および合成可能性は、.NET に限定されていません。Java は Streams API を介して堅牢な標準ソリューションを提供します。Python 開発者は、組み込みの内包表記、itertools モジュール、および py-linq のようなライブラリを活用します。C++ プログラマーは、STL アルゴリズム、モダンな Range ライブラリ (std::ranges, range-v3)、または専用の LINQ エミュレーションライブラリを利用できます。

真の価値は構文にあるのではなく、これらの概念をクリーンで効率的なデータ処理のための普遍的なツールキットとして認識することにあります。一度理解すれば、Java、Python、C++、または宣言的なパラダイムを採用する他のどの言語でコーディングしていても、それらは転用可能になります。

関連ニュース

関連記事