Lambda capture

Content:

This is inspired by a post on LinkedIn, but I am going way beyond the content in that. It is all about lambdas and capture by reference versus capture by value.

  • LinkedIn post was about C#, so I will start with C#.
  • using System;
    using System.Collections.Generic;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            IList<Func<int>> fa = new List<Func<int>>();
            for(int i = 0; i < 5; i++)
            {
                fa.Add(() => i);
            }
            foreach(Func<int> f1 in fa)
            {
                Console.WriteLine(f1());
            }
        }
    }
    
    5
    5
    5
    5
    5
    

    may surprise some that are not aware of the capture by reference concept. To make it work as some would expect use:

    using System;
    using System.Collections.Generic;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            IList<Func<int>> fa = new List<Func<int>>();
            for(int i = 0; i < 5; i++)
            {
                int j = i;
                fa.Add(() => j);
            }
            foreach(Func<int> f1 in fa)
            {
                Console.WriteLine(f1());
            }
        }
    }
    
    0
    1
    2
    3
    4
    

    It is documented to work that way, so as such it is OK, but beginners get tricked by this all the time, so I don't think it is good.

    Java does not have the problem - Java only allow capture of final or effective final. Then it does not matter how it is passed.

    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Supplier;
    
    public class Lambda1 {
        public static void main(String[] args) {
            List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
            for(int i = 0; i < 5; i++) {
                fa.add(() -> i);
            }
            for(Supplier<Integer> f1 : fa) {
                System.out.println(f1.get());
            }
        }
    }
    
    Lambda1.java:9: error: local variables referenced from a lambda expression must be final or effectively final
                            fa.add(() -> i);
                                         ^
    1 error
    

    is a clear error message. And the fix is clearly hinted at:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Supplier;
    
    public class Lambda2 {
        public static void main(String[] args) {
            List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
            for(int i = 0; i < 5; i++) {
                final int j = i;
                fa.add(() -> j);
            }
            for(Supplier<Integer> f1 : fa) {
                System.out.println(f1.get());
            }
        }
    }
    
    0
    1
    2
    3
    4
    

    Note that the rule is final or effective final, which means that the final is optional:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Supplier;
    
    public class Lambda3 {
        public static void main(String[] args) {
            List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
            for(int i = 0; i < 5; i++) {
                int j = i;
                fa.add(() -> j);
            }
            for(Supplier<Integer> f1 : fa) {
                System.out.println(f1.get());
            }
        }
    }
    
    0
    1
    2
    3
    4
    

    But as soon as the variable is changed then the error comes back:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Supplier;
    
    public class Lambda4 {
        public static void main(String[] args) {
            List<Supplier<Integer>> fa = new ArrayList<Supplier<Integer>>();
            for(int i = 0; i < 5; i++) {
                int j = i;
                fa.add(() -> j);
                j++;
            }
            for(Supplier<Integer> f1 : fa) {
                System.out.println(f1.get());
            }
        }
    }
    
    Lambda4.java:10: error: local variables referenced from a lambda expression must be final or effectively final
                            fa.add(() -> j);
                                         ^
    1 error
    

    I like the Java way. The beginner developer is prevented from making mistakes.

    C++ is doing it the C++ way:

    #include <iostream>
    #include <vector>
    #include <functional>
    
    using namespace std;
    
    int main()
    {
        vector<function<int()>> fa;
        for(int i = 0; i < 5; i++)
        {
            fa.push_back([=]() { return i; });
        }
        for(function<int()> f1 : fa)
        {
            cout << f1() << endl;
        }
        return 0;
    }
    
    0
    1
    2
    3
    4
    
    #include <iostream>
    #include <vector>
    #include <functional>
    
    using namespace std;
    
    int main()
    {
        vector<function<int()>> fa;
        for(int i = 0; i < 5; i++)
        {
            fa.push_back([&]() { return i; });
        }
        for(function<int()> f1 : fa)
        {
            cout << f1() << endl;
        }
        return 0;
    }
    
    5
    5
    5
    5
    5
    

    I am actually OK with the C++ approach as well. The developer picked a language prioritizing control. And C++ delivered.

    Comments: