Concepts fonctionnels

simulation en Java

Cet article est une suite à l’article sur les , suggérée par @pcolomb. Comme je n’ai pas encore assez regardé les lambda dans Java 8 pour ajouter des exemples dans l’article, j’ai pensé présenter un moyen de simuler ces concepts en pur Java.

La version proposée n’est évidemment pas très utilisable, et probablement pas performante, mais elle peut aider à saisir le fonctionnement de cette machinerie (et en plus c’est rigolo).

Lambda

Comme présenté dans , les lambda sont des fonctions anonymes, qui peuvent être crée à la demande. Nous allons créer ici une classe permettant de simuler une telle fonction unaire.

On veut donc réer une classe générique qui simule la création d’une fonction anonyme de signature A → B. Ceci peut s’exprimer comme suit :

Premier essai (Lambda.java)
    1 
    2 abstract class Function<A,R> {
    3     public abstract R call(A arg);
    4 }
    5 
    6 class Lambda {
    7     public static void main(String[] args) {
    8 
    9         Function<Integer,Integer> doubler = new Function<Integer,Integer>(){
   10             public Integer call(Integer arg) { return 2*arg; }};
   11 
   12         System.out.println(doubler.call(21));
   13     }
   14 }

Ça marche ! Par soucis d’intégration, il serait plus pratique que notre « fonction » implémente Callable, ce qui permettrait de l’utiliser dans un contexte où cette interface standard est requise. Essayons de modifier notre classe en conséquence. L’argument ne pouvant être passé à notre méthode call, le seul moyen est de le garder dans l’état interne de notre objet, sous forme d’un attribut privé.

Lambda Callable (LambdaCallable.java)
    1 import java.util.concurrent.Callable;
    2 
    3 class NotBoundFunctionException extends Exception { }
    4 
    5 abstract class Function<A,R> implements Callable<R> {
    6     private A _arg;
    7     public A arg() { return this._arg;}
    8     public void arg(A arg) { this._arg = arg; }
    9 
   10     public Function() {}
   11     public Function(A arg) { this.arg(arg); }
   12 
   13     public Function<A,R> bind(A arg) { this.arg(arg); return this; }
   14 
   15     protected abstract R callMe();
   16     public R call() throws Exception {
   17         if ( this._arg == null) { throw new NotBoundFunctionException(); }
   18         return this.callMe();
   19     }
   20     public R call(A arg) throws Exception {
   21         return this.bind(arg).call();
   22     }
   23 }
   24 
   25 class LambdaCallable {
   26     public static void main(String[] args) {
   27 
   28          Function<Integer,Integer> doubler = new Function<Integer,Integer>(){
   29             protected Integer callMe() { return 2*this.arg(); }};
   30 
   31         try {
   32 
   33             doubler.arg(21);
   34             System.out.println(doubler.call());
   35 
   36             System.out.println(doubler.bind(12).call());
   37 
   38             System.out.println(doubler.call(21));
   39 
   40         } catch (Exception e) { System.exit(1); }
   41     }
   42 }

Notez la méthode Function<A,R> bind(A arg), qui à un petit goût de fluent interface, qui nous permet de facilement chaîner les méthodes. Cette méthode bind revient à effectuer une partialisation de notre fonction, qui peut donc être appelée sans argument, et répond donc à Callable. Cette application partielle correspond à une fermeture, la variable close étant ici stockée dans un attribut privé de notre objet fonction. Enfin, un peu de surcharge de call peut rendre tout ça plus facile d’utilisation, tout en maintenant la compatibilité avec l’interface (attention toutefois à l’effet de bord interne, puisque l’état de notre objet est modifié).

On vient donc de simuler une fermeture nous permettant de faire une application partielle.

Fonctions polyadiques et curryfication

Notre objet Function nous permet pour l’instant de créer des fonctions à un seul argument, ce qui est relativement limité. Comment peut on alors définir des fonctions à plusieurs arguments ? On pourrait créer une classe générique pour chaque arité de fonction, mais il y a une réponse plus simple : créer des fonctions curryfiées, c’est-à-dire une fonction d’un seul argument retournant une fonction d’un seul argument retournant une fonction d’un seul argument, etc.

On peut d’ores et déjà le faire « à la main » avec ce dont on dispose. Créons ainsi une fonction add: Interger → Integer → Integer avec notre première version de lambda.

Fonction polyadique par curryfication manuelle (Curry.java)
    1 
    2 abstract class Function<A,R> {
    3     public abstract R call(final A arg);
    4 }
    5 
    6 class Curry {
    7     public static void main(String[] args) {
    8         Function<Integer,Function<Integer,Integer>> add = new Function<Integer,Function<Integer,Integer>>(){
    9             public Function<Integer,Integer> call(final Integer a) {
   10                 return new Function<Integer,Integer>(){
   11                     public Integer call(final Integer b) {
   12                         return a + b;
   13                     }
   14                 };
   15             }
   16         };
   17         try {
   18             System.out.println(add.call(21).call(21));
   19 
   20             Function<Integer,Integer> partialAdd = add.call(21);
   21             System.out.println(partialAdd.call(21));
   22 
   23         } catch (Exception e) { System.exit(1); }
   24     }
   25 }

Pas très lisible, mais ça marche. Notez que les arguments (au moins a) doivent être finaux, puisqu’ils sont utilisés dans une classe interne anonyme ; mais c’est pas grave, on fait du fonctionnel non ?

Utilisons la version Callable de notre Function :

Fonction polyadique Callable par curryfication manuelle (CurryCallable.java)
    1 import java.util.concurrent.Callable;
    2 class CurryCallable {
    3     public static void main(String[] args) {
    4         Function<Integer,Function<Integer,Integer>> add = new Function<Integer,Function<Integer,Integer>>() {
    5             protected Function<Integer,Integer> callMe() {
    6                 final Integer first = this.arg();
    7                 return new Function<Integer,Integer>(){
    8                     private Integer curry = first;
    9                     protected Integer callMe() {
   10                         return this.arg() + curry;
   11                     }
   12                 };
   13             }
   14         };
   15         try {
   16 
   17             System.out.println(add.call(21).call(21));
   18 
   19             Function<Integer,Integer> partialAdd = add.call(21);
   20             System.out.println(partialAdd.call(21));
   21 
   22             Callable<Integer> callableAdd = add.call(37).bind(1300);
   23             System.out.println(callableAdd.call());
   24 
   25         } catch (Exception e) { System.exit(1); }
   26     }
   27 }

Ouch ! Ça pique un peu, mais ça marche, et on récupère les fonctionnalités précédentes.

Et si l’on veut les deux ? La relative « facilité » de définition de la première approche, avec la plus grande flexibilité de la seconde ? En objet, on ferait de la délégation, en fonctionnel, on parle de composition, mais le principe est strictement le même. Nous allons créer une « fonction », qui prendra une fonction et en retournera une application partielle, augmentée des méthodes de la deuxième approche. Cela correspond au patron décorateur d’un point de vue fonctionnel (c’est d’ailleurs le nom que ce type d’approche porte en Python). On reprend donc la même approche, mais au lieu de redéfinir callMe, on définie un attribut fun contenant une fonction qui sera appelée avec la valeur stockée dans arg (d’où la délégation).

Fonction polyadique Callable automatique par composition (CurryComposed.java)
    1 import java.util.concurrent.Callable;
    2 
    3 class NotBoundFunctionException extends Exception { }
    4 class FunctionUndefinedException extends Exception { }
    5 
    6 abstract class Function<A,R> {
    7     public abstract R call(final A arg);
    8 }
    9 
   10 class CallableFunction<A,R> implements Callable<R> {
   11     private A _arg;
   12     public A arg() { return this._arg;}
   13     public void arg(A arg) { this._arg = arg; }
   14 
   15     private Function<A,R> _fun;
   16     public Function<A,R> fun() { return this._fun; }
   17     public void fun(Function<A,R> fun) { this._fun = fun; }
   18 
   19     public CallableFunction() { }
   20     public CallableFunction(A arg) { this.arg(arg); }
   21     public CallableFunction(Function<A,R> fun) { this.fun(fun); }
   22     public CallableFunction(Function<A,R> fun, A arg) {
   23         this.fun(fun);
   24         this.arg(arg);
   25     }
   26 
   27     public Callable<R> bind(A arg) { this.arg(arg); return this; }
   28 
   29     public R call() throws Exception {
   30         if ( this.arg() == null) { throw new NotBoundFunctionException(); }
   31         if ( this.fun() == null) { throw new FunctionUndefinedException(); }
   32         return this.fun().call(this.arg());
   33     }
   34     public R call(A arg) throws Exception {
   35         return this.bind(arg).call();
   36     }
   37 }
   38 
   39 
   40 class CurryComposed {
   41     public static void main(String[] args) {
   42         Function<Integer,Function<Integer,Integer>> add = new Function<Integer,Function<Integer,Integer>>(){
   43             public Function<Integer,Integer> call(final Integer a) {
   44                 return new Function<Integer,Integer>(){
   45                     public Integer call(final Integer b) {
   46                         return a + b;
   47                     }
   48                 };
   49             }
   50         };
   51 
   52         try {
   53 
   54             System.out.println(add.call(21).call(21));
   55 
   56             Function<Integer,Integer> partialAdd = add.call(21);
   57             System.out.println(partialAdd.call(21));
   58 
   59             CallableFunction<Integer,Integer> callableAdd = new CallableFunction<Integer,Integer>(partialAdd);
   60             System.out.println(callableAdd.call(21));
   61 
   62             System.out.println(callableAdd.bind(1316).call());
   63 
   64             System.out.println((new CallableFunction<Integer,Integer>(add.call(668))).bind(669).call());
   65 
   66         } catch (Exception e) { System.exit(1); }
   67     }
   68 }