5

如果我创建一个功能接口:

@FunctionalInterface
public class Consumer2<T1, T2> {
    void accept(T1 t1, T2 t2);

    default Consumer1<T2> curry(T1 t1) {
        return (t2) -> accept(t1, t2);
    }
}

现在,如果我有一堂课:

public class MyClass {
    public void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }
}

MyClass myClass = new MyClass();

现在,如果我想使用我的功能界面,我可以:

Consumer2<String, String> printString = myClass::printStrings;
printString.curry("hello").accept("world");

但我不能做这样的事情:

myClass::printStrings.curry("hello").accept("world");

这是有道理的,因为 Java 无法知道myClass::printStrings可以应用于函数式接口Consumer2。为此,我创建了一个实用程序类:

public class F {
    public static <T1, T2> Consumer2<T1, T2> c2(Consumer2<T1, T2> fn) {
        return fn;
    }
}

然后我可以:

F.c2(myClass::printStrings).curry("hello").accept("world");

甚至,这将起作用:

((Consumer2<String, String>)myClass::printStrings).curry("hello").accept("world");

在这种情况下,只要 Java 8 有某种方法可以理解该函数类型。所以,问题是,最好的方法是什么,同时又可能避免使用样板文件?

4

2 回答 2

4

您不是在柯里化,而是在执行部分功能应用程序。这些操作是相关的,但并不完全相同。Currying 意味着将你Consumer2<T1, T2>转换为Function<T1,Consumer1<T2>>. 当将该 curried 函数应用于一个T1值时,您将获得您的方法有效执行的操作。

使用已建立的名称更容易,bind因为将值绑定到函数的参数是每个开发人员都可以理解的,而无需深入研究函数式编程的世界。

也就是说,最好记住 now interfaces 可以有static方法,所以不需要这样的实用程序类。此外,static仅返回其参数的方法本身几乎没有用处,因此您可以将它与它应该服务的后续方法融合在一起。然后,它实现了与实例方法相同的目的,并且可以作为简单的重载提供:

@FunctionalInterface
public interface Consumer2<T1, T2> {
    void accept(T1 t1, T2 t2);

    default Consumer1<T2> bind(T1 t1) {
        return bind(this, t1);
    }
    static <T,U> Consumer1<U> bind(Consumer2<? super T, ? super U> c, T t) {
        return u -> c.accept(t, u);
    }
}
public interface Consumer1<T1> extends Consumer<T1> {}

public class MyClass {
    public static void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }

    public static void main(String[] args) {
        Consumer2.bind(MyClass::printStrings, "hello").accept("world");
    }
}

另一方面,当您使用现有的标准时interfaceConsumerBiConsumer别无选择,只能在与这些标准不同的类中提供实用方法interface。但好消息是,很容易使解决方案保持一致,因为您只能提供一种static方法:

class FunctionUtil {
    static <T,U> Consumer<U> bind(BiConsumer<? super T, ? super U> c, T t) {
        return u -> c.accept(t, u);
    }
}
public class MyClass {
    public static void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }

    public static void main(String[] args) {
        FunctionUtil.bind(MyClass::printStrings, "hello").accept("world");
    }
}
于 2015-09-16T09:40:50.703 回答
2

您涉及F.c2方法的解决方案很有趣,但是您的示例太人为了。如果你问,如何更好地编写这段代码

F.c2(myClass::printStrings).curry("hello").accept("world");

那我肯定会建议你这样写:

myClass.printStrings("hello", "world");

如果您想询问如何将预定义参数绑定到方法引用,我建议您改用 lambda 函数:

Consumer1<String> fn = str -> myClass.printStrings("hello", str);
fn.accept("world");

可能您想考虑在编译时您的函数未知的情况。在这种情况下,它要么从另一个方法返回,要么作为方法参数传递给当前方法,或者存储在变量/字段中。在所有这些情况下,它已经是功能接口,您可以curry直接使用:

Consumer2<String, String> getConsumer2() { return myClass::printStrings; }

getConsumer2().curry("hello").accept("world");

因此,总的来说,我在这里看不到问题。如果你仍然认为对方法引用应用currying很有用,我会在接口中创建一个静态curry方法(虽然我猜,它实际上是一个“bind”,而不是“curry”)Consumer1

static <T1, T2> Consumer1<T2> curry(Consumer2<T1, T2> c2, T1 t1) {
    return c2.curry(t1);
}

并像这样使用它:

Consumer1.curry(myClass::printStrings, "hello").accept("world");
于 2015-09-16T07:33:53.057 回答