Lambdas et closures

Mise à jour le 14 juil. 2022.

Dans la page web Lambda, Martin Fowler décrit les lambdas comme un concept informatique appelés également Closures, Fonctions Anonymes ou Blocs.
Un lambda est un bloc de code pouvant être passé en tant que valeur comme paramètre dans un appel de fonction ; cette valeur peut aussi être exécutée à la demande.
On parle de closure si un lambda fait référence à des variables en dehors du contexte dans lequel il est créé.

Dans ce qui suit, vous trouverez la définition des lambdas et leurs syntaxes dans différents langages de programmation, en commençant par le langage Java.
Le même exemple complet, au travers d'une classe Main (sauf pour le langage C#, où l'on a la classe Program), est repris dans les différents langages.

Expressions lambda en Java

En Java, une expression lambda est une expression fournissant une implémentation anonyme d'une interface fonctionnelle, c'est-à-dire d'une interface ayant une unique méthode abstraite (Single Abstract Method).
Une telle interface peut être annotée avec @FunctionalInterface.

Syntaxe

(paramètres ou aucun paramètre) -> instruction ou { instructions }

Voici un exemple d'expression lambda :

Operation add = (x, y) -> x + y;

Ici, les types des paramètres sont inférés par rapport à l'interface fonctionne Operation qui est définie ainsi:

1
2
3
4
@FunctionalInterface
public interface Operation {
    long apply(long x, long y);
}
Exemple d'utilisation d'une interface fonctionnelle

Poursuivons avec un exemple utilisant l'interface fonctionnelle javafx.event.EventHandler<T extends Event> de JavaFX ; en réponse au clic sur un bouton dans une scène JavaFX, on utiliserait une classe anonyme ainsi :

1
2
3
4
5
button.setOnAction(new EventHandler<ActionEvent>() {
    public void handler(ActionEvent event) {
        // ...
    }
})

De manière plus concise et lisible, une expression lambda peut être utilisée à la place de la création de la classe anonyme :

1
button.setOnAction(event -> { /* ... */ });
Exemple Main

L'exemple de code ci-dessous, volontairement compact, démontre l'utilisation d'expressions lambda :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.function.Function;

public class Main {

    @FunctionalInterface
    public interface Operation {
        long apply(long x, long y);
    }

    public long process(long x, long y, Operation operation) {
        return operation.apply(x, y);
    }

    public long adder(long x, long y) {
        return x + y;
    }

    // Returns a lambda (a closure here)
    public static Function<Long, Long> times(int n) {
        return (Long x) -> n * x;
    }

    public static void main(String[] args)
    {
        // Using a lambda expression
        Operation add = (x, y) -> x + y;

        // Invoke interface method
        long result = add.apply(10, 5);

        Main main = new Main();
        result = main.process(10, 5, add);

        // Passing lambda in-line
        result = main.process(10, 5, (x, y) -> x + y);

        // Using method reference
        result = main.process(10, 5, main::adder);

        // Using times method to get a new closure for triple a number
        Function<Long, Long> triple = times(3);
        System.out.println(triple.apply(2L)); // displays 6
    }
}
Références

Annotation Type FunctionalInterface
Lambda Expressions

Closures Groovy

En langage Groovy, une closure est une instance de la classe groovy.lang.Closure que l'on peut définir avec la syntaxe donnée ci-dessous.
Une closure peut aussi être utilisée là où dans le code, une expression lambda Java est attendue (voir Closure to type coercion).
De plus, les closures Groovy comprennent un concept de délégation pouvant aider à concevoir des Domain Specific Languages élégants.

Syntaxe

{ [paramètre ou aucun paramètre ->] instruction(s) }

Dans l'exemple de code ci-dessous, la variable add est une closure :

def add = { long x, long y -> x + y }

Exemple Main

L'exemple de code ci-dessous démontre l'utilisation de closures Groovy, dans lequel on a nul besoin d'interface fonctionnelle comme en Java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Main {

    // This method accepts a closure as last parameter
    long process(long x, long y, Closure operation) {
        return operation(x, y)
    }

    long adder(long x, long y) {
        x + y
    }

    // times returns a closure
    Closure times(int n) {
        { long x -> n * x }
    }

    static void main(String[] args)
    {
        def add = { x, y -> x + y } // add's type is Closure

        // Invoke add closure
        def result = add(10, 5)

        Main main = new Main()
        result = main.process(10, 5, add)

        // Passing closure in-line
        //result = main.process(10, 5, { x, y -> x + y }) ou
        result = main.process(10, 5) {
            x, y -> x + y
        }

        // Using Method pointer operator
        result = main.process(10, 5, main.&adder)

        // Using times method to get a new closure for triple a number
        def triple = main.times(3)
        println triple(2) // displays 6
    }
}
Références

Closures
Closure to type coercion

Expressions lambda en Kotlin

En langage Kotlin, une expression lambda est un littéral de type fonction, autrement dit une expression définissant une fonction anonyme ayant un type fonction.

Syntaxe

{ [paramètre ou aucun paramètre ->] instruction(s) }

Dans l'exemple de code ci-dessous, la variable add est de type (Long, Long) -> Long :

val add = { x: Long, y: Long -> x + y }

Exemple Main

L'exemple de code ci-dessous démontre l'utilisation des lambdas Kotlin dans notre exemple principal :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Main {

    // This method accepts a lambda as last parameter
    fun process(x: Long, y: Long, operation: (Long, Long) -> Long): Long {
        return operation(x, y)
    }

    // You can also write
    //fun process(x: Long, y: Long, operation: (Long, Long) -> Long): Long = operation(x, y)

    fun adder(x: Long, y: Long): Long = x + y

    // times returns a lambda (here a closure)
    fun times(n: Int): (Long) -> Long = { n*it }

    companion object {

        @JvmStatic
        fun main(args: Array<String>) {
            // Defining add lambda
            val add = { x: Long, y: Long ->  x + y }

            // Calling lambda
            var result = add(10, 5)

            val main = Main()
            result = main.process(10, 5, add)

            // Passing lambda in-line
            result = main.process(10, 5, { x, y -> x + y })

            // Using method reference
            result = main.process(10, 5, main::adder)

            // Using times method to get a new closure for triple a number
            val triple = (main::times)(3)
            println(triple(2)) // displays 6
        }
    }
}
Référence

Higher-Order Functions and Lambdas

Closures Scala

En langage Scala, une closure est une expression définissant une fonction anonyme ayant donc une signature précise.

Syntaxe

(paramètres ou aucun paramètre) => instruction ou { instructions }

Dans l'exemple de code ci-dessous, la variable add est de type (Long, Long) => Long :

val add = (x: Long, y: Long) => x + y

Exemple Main

L'exemple de code ci-dessous démontre l'utilisation des closures en Scala en reprenant l'exemple Main :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
object Main {

    // This method accepts a closure as last parameter
    def process(x: Long, y: Long, operation: (Long, Long) => Long): Long = operation(x, y)

    def adder(x: Long, y: Long): Long = x + y

    // times returns a closure
    def times(n: Int): (Long) => Long = { n*_ }

    def main(args: Array[String]): Unit = {
        // Defining add closure
        val add = (x: Long, y: Long) => x + y

        // Calling closure
        var result = add(10, 5)

        result = this.process(10, 5, add)

        // Passing closure in-line
        result = this.process(10, 5, (x, y) => x + y)

        // Using an existing method
        result = this.process(10, 5, adder)

        // Using times method to get a new closure for triple a number
        val triple = times(3)
        println(triple(2)) // displays 6
    }
}
Référence

Anonymous Functions

Expressions lambda en C#

En langage C#, une expression lambda est une fonction anonyme que l'on peut utiliser pour créer des types délégués (un délégué est un type encapsulant une méthode) ou des types d'arborescence d'expression.
Les expressions lambda sont particulièrement utiles pour écrire des expressions de requête LINQ.

Syntaxe

(paramètres ou aucun paramètre) => instruction ou { instructions }

Voici un exemple d'expression lambda :

Operation add = (x, y) => x + y;

Ici, les types des paramètres sont inférés par rapport au type délégué Operation :

delegate long Operation(long x, long y);

Exemple Main

L'exemple de code ci-dessous démontre l'utilisation des closures en C# en reprenant l'exemple principal :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
namespace closures_csharp
{
    class Program
    {
        delegate long Operation(long x, long y);

        // This method accepts a delegate as last parameter
        long process(long x, long y, Operation operation)
        {
            return operation(x, y);
        }

        long adder(long x, long y)
        {
            return x + y;
        }

        // Returns a lambda (a closure here)
        Func<long, long> times(int n)
        {
            return (x) => n*x;
        }

        static void Main(string[] args)
        {
            // Using a lambda expression
            Operation add = (x, y) => x + y;

            // Invoke delegate
            var result = add(10, 5);

            var program = new Program();

            // Passing add to process method
            result = program.process(10, 5, add);

            // Using a lambda expression directly
            result = program.process(10, 5, (x, y) => x + y);

            // Using an existing method as delegate
            result = program.process(10, 5, program.adder);

            // Using times method to get a new closure for triple a number
            var triple = program.times(3);
            Console.WriteLine(triple(2L)); // displays 6
        }
    }
}
Référence

Expressions lambda (Guide de programmation C#)

Fonctions anonymes en Dart

En langage Dart, une fonction anonyme, appelée parfois lambda ou closure, est une instance de la classe Function que l'on peut définir avec la syntaxe donnée ci-dessous, où codeBlock représente le corps de la fonction.

Syntaxe
1
2
3
([[Type] param1[, ...]]) {
  codeBlock;
};

Dans l'exemple de code ci-dessous, la variable f est de type Function :

Function f = (int a, int b) { return a + b; };

Ce qui permet permet d'invoquer f comme un fonction nommée (par exmple f(1, 2);).

Si le corps de la fonction peut se réduire à une expression, on peut aussi appliquer l'opérateur fat arrow => :

Function f = (int a, int b) => a + b;

Exemple Main

L'exemple de code ci-dessous démontre l'utilisation des fonctions anonymes Dart dans notre exemple principal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// This function accepts a function as last parameter
process(int a, int b, Function operation) => operation(a, b);
// Example of operation that be passed to process call
int adder(int a, int b) => a + b;

// This function returns a Function object
// We could also write Function times(int n) => (int x) => n * x;
Function times(int n) {
  return (int x) => n * x;
}


void main() {
  // A local function definition; this can ba also written as add(int a, int b) { return a + b; }
  add(int a, int b) => a + b;

  // Call add
  assert(add(1, 2) == 3);

  // Call process using add function
  assert(process(1, 2, add) == 3);

  // Call process using an inline function
  assert(process(1, 2, (a, b) => a * b) == 2);

  // Call process using a non-local function
  assert(process(1, 2, adder) == 3);
  
  // Using times function to get a new function to triple a number        
  var triple = times(3);
  assert(triple(3) == 9);
}
Références

Functions