2017-09-22 3 views
7

Je suis récemment tombé sur ce morceau de code en Java. Il implique la fonction et l'impression des numéros de fibonacci et cela fonctionne.Comment est-ce morceau de l'appel récursif lambda en Java fonctionne

public class AppLambdaSubstitution { 

public static Function<Integer, Integer> Y(Function<Function<Integer, Integer>, Function<Integer, Integer>> f) { 
    return x -> f.apply(Y(f)).apply(x); 
} 

public static void main(String[] args) { 
    Function<Integer, Integer> fib = Y(
      func -> x -> { 
     if (x < 2) 
      return x; 
     else 
      return func.apply(x - 1) + func.apply(x - 2); 
    }); 

    IntStream.range(1,11). 
    mapToObj(Integer::valueOf). 
    map(fib).forEach(System.out::println); 
    } 
} 

La partie qui m'a confondu est return x -> f.apply(Y(f)).apply(x);. Y(f) n'est pas un appel récursif à la méthode Y? Nous continuons à l'appeler avec la fonction f en tant que paramètre. Pour moi, il n'y a pas de cas de base pour cet appel récursif de retour. Pourquoi n'y a-t-il pas de débordement résultant d'un appel récursif sans fin?

+3

Le 'Y (f)' appel est à l'intérieur d'un lambda , et que lambda n'est exécuté que si 'f' choisit de l'appeler. – 4castle

+4

https://en.wikipedia.org/wiki/Fixed-point_combinator – pvg

Répondre

5

Fondamentalement, il vous manque le point que x -> f.apply(Y(f)).apply(x); n'appellera pas apply, returnFunction.

C'est juste une façon très compliquée (et? Non-intuitif) de montrer curryfication et fonction récursive OMI. Les choses seraient beaucoup plus simples si vous remplaciez un certain nombre de choses et le rendiez un peu plus lisible.

Cette construction:

Function<Function<Integer, Integer>, Function<Integer, Integer>> 

n'est pas nécessaire du tout, car le paramètre gauche n'est pas utilisé du tout. C'est simplement nécessaire pour obtenir le bon. En tant que tel le paramètre left pourrait être quelque chose du tout (je plus tard le remplacer par Supplier - qui n'est pas nécessaire non plus, mais juste pour prouver un point).

En fait, tout ce que vous souciez ici est ce Function qui fait le calcul réel pour chaque élément du Stream:

public static Function<Integer, Integer> right() { 

    return new Function<Integer, Integer>() { 
     @Override 
     public Integer apply(Integer x) { 
      if (x < 2) { 
       return x; 
      } else { 
       return apply(x - 1) + apply(x - 2); 
      } 
     } 
    }; 
} 

Maintenant, vous pouvez écrire cette construction entière avec:

Supplier<Function<Integer, Integer>> toUse =() -> right(); 
Function<Integer, Integer> fib = curry(toUse); 
IntStream.range(1, 11) 
      .mapToObj(Integer::valueOf) 
      .map(fib) 
      .forEach(System.out::println); 

Cette Supplier<Function<Integer, Integer>> toUse =() -> right(); devrait vous faire comprendre pourquoi, dans l'exemple précédent (Function<Function, Function>) la partie gauche était nécessaire - juste pour obtenir une prise de right un.

Si vous regardez encore plus près, vous remarquerez peut-être que le Supplier est pas entièrement nécessaire, vous pouvez donc encore simplifier encore avec:

IntStream.range(1, 11) 
     .mapToObj(Integer::valueOf) 
     .map(right()) 
     .forEach(System.out::println);