使用Java 8实现递归lambda函数

use*_*835 54 java recursion lambda java-8

Java 8引入了lambda函数,我想实现像factorial这样的东西:

 IntToDoubleFunction fact = x -> x == 0 ? 1 : x * fact.applyAsDouble(x-1);
Run Code Online (Sandbox Code Playgroud)

编译返回

  error: variable fact might not have been initialized
Run Code Online (Sandbox Code Playgroud)

我怎样才能参考功能本身.类是匿名的但是实例存在:它被调用fact.

And*_*zov 44

我通常使用(一次为所有功能接口定义)通用助手类,它包装了功能接口类型的变量.这种方法解决了局部变量初始化的问题,并使代码看起来更清晰.

如果出现此问题,代码将如下所示:

// Recursive.java
// @param <I> - Functional Interface Type
public class Recursive<I> {
    public I func;
}

// Test.java
public double factorial(int n) {

    Recursive<IntToDoubleFunction> recursive = new Recursive<>();
    recursive.func = x -> (x == 0) ? 1 : x * recursive.func.applyAsDouble(x - 1);

    return recursive.func.applyAsDouble(n);
}
Run Code Online (Sandbox Code Playgroud)

  • 我已经创建了一个实用程序类,因此您可以传递一个递归闭包并获得一个预定义的函数类型:https://github.com/claudemartin/Recursive您总是有一个名为"self"的附加参数来执行递归.有些甚至有一个带缓存的版本,而Demo.class显示了如何获得相当快速的Fibonacci函数版本. (3认同)

rat*_*lis 18

一种方法是编写一个辅助函数,helper它将函数和数字作为参数,然后编写你真正想要的函数,fact = helper(helper,x).

像这样:

BiFunction<BiFunction, Double, Double> factHelper =
        (f, x) -> (x == 0) ? 1.0 : x*(double)f.apply(f,x-1);
Function<Double, Double> fact =
        x -> factHelper.apply(factHelper, x);
Run Code Online (Sandbox Code Playgroud)

在我看来,这比依赖于角点案例语义更优雅,例如捕获对可变结构的引用的闭包,或者允许自引用以及"可能未初始化"的可能性的警告.

但是,由于Java的类型系统,它不是一个完美的解决方案 - 泛型不能保证f,参数factHelper类型与factHelper(即相同的输入类型和输出类型)的类型相同,因为那将是无限嵌套的泛型.

因此,更安全的解决方案可能是:

Function<Double, Double> fact = x -> {
    BiFunction<BiFunction, Double, Double> factHelper =
        (f, d) -> (d == 0) ? 1.0 : d*(double)f.apply(f,d-1);
    return factHelper.apply(factHelper, x);
};
Run Code Online (Sandbox Code Playgroud)

factHelper现在,在lambda中包含了由不太完美的泛型类型引起的代码气味(或者,我敢说,封装),确保factHelper永远不会在不知不觉中调用.


new*_*cct 13

本地和匿名类以及lambdas 在创建时按值捕获局部变量.因此,他们不可能通过捕获局部变量来引用自己,因为在创建它们时,指向它们的值还不存在.

本地和匿名类中的代码仍然可以引用自己使用this.但是,this在lambda中并不是指lambda; 它指的是this来自外部的范围.

您可以捕获可变数据结构,如数组,而不是:

IntToDoubleFunction[] foo = { null };
foo[0] = x -> { return  ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};
Run Code Online (Sandbox Code Playgroud)

虽然不是优雅的解决方案.


Ian*_*son 8

如果你发现自己经常需要做这种事情,另一种选择是创建一个辅助接口和方法:

public static interface Recursable<T, U> {
    U apply(T t, Recursable<T, U> r);
}

public static <T, U> Function<T, U> recurse(Recursable<T, U> f) {
    return t -> f.apply(t, f);
}
Run Code Online (Sandbox Code Playgroud)

然后写:

Function<Integer, Double> fact = recurse(
    (i, f) -> 0 == i ? 1 : i * f.apply(i - 1, f));
Run Code Online (Sandbox Code Playgroud)

(虽然我通常使用引用类型执行此操作,但您也可以创建特定于原始的版本).

这借鉴了The Little Lisper中的一个老技巧,用于制作未命名的功能.

我不确定我是否会在生产代码中这样做,但它很有意思......

  • 作为参考,这种方法在功能编程圈中通常被称为“定点组合器”,并且已被广泛使用。 (2认同)

Aru*_*dev 5

答案是:您必须在名称变量调用 applyAsDouble 函数之前使用this:-

IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);
Run Code Online (Sandbox Code Playgroud)

如果你使事实最终也将起作用

final IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);
Run Code Online (Sandbox Code Playgroud)

我们可以在这里使用函数式接口UnaryOperator。始终返回其输入参数的一元运算符。

1)只需添加这个。在函数名之前,如:

UnaryOperator<Long> fact = x -> x == 0 ? 1  : x * this.fact.apply(x - 1 );
Run Code Online (Sandbox Code Playgroud)

这将有助于避免“在定义字段之前无法引用字段”

2) 如果您更喜欢静态字段,只需将“ this ”替换为类名:

static final UnaryOperator<Long> fact = x -> x== 0? 1: x * MyFactorial.fact.apply(x - 1 );
Run Code Online (Sandbox Code Playgroud)


use*_*835 3

一种解决方案是将此函数定义为 INSTANCE 属性。

import java.util.function.*;
public class Test{

    IntToDoubleFunction fact = x -> { return  ( x == 0)?1:x* fact.applyAsDouble(x-1);};

    public static void main(String[] args) {
      Test test = new Test();
      test.doIt();
    }

    public void doIt(){
       System.out.println("fact(3)=" + fact.applyAsDouble(3));
    }
}
Run Code Online (Sandbox Code Playgroud)