1. Introduction
1.绪论
Since Java 8, we can define one- and two-parameter functions in Java, allowing us to inject their behaviors into other functions, by passing them in as parameters. But for functions with more parameters, we rely on external libraries like Vavr.
从Java 8开始,我们可以在Java中定义单参数和双参数的函数,允许我们将它们的行为注入到其他函数中,通过将它们作为参数传入。但是对于有更多参数的函数,我们要依靠外部库,如Vavr。
Another option is to use currying. By combining currying and functional interfaces, we can even define easy-to-read builders that force the user to provide all inputs.
另一个选择是使用currying。通过结合currying和functional interfaces,我们甚至可以定义易于阅读的构建器,强制用户提供所有输入。
In this tutorial, we’ll define currying and present its usage.
在本教程中,我们将定义咖喱并介绍其用法。
2. Simple Example
2.简单的例子
Let’s consider a concrete example of a letter with multiple parameters.
让我们考虑一个有多个参数的信件的具体例子。
Our simplified first version needs only a body and a salutation:
我们简化的第一个版本只需要一个正文和一个敬语。
class Letter {
private String salutation;
private String body;
Letter(String salutation, String body){
this.salutation = salutation;
this.body = body;
}
}
2.1. Creation by Method
2.1.按方法创建
Such an object can be easily created with a method:
这样的对象可以用一个方法轻松地创建。
Letter createLetter(String salutation, String body){
return new Letter(salutation, body);
}
2.2. Creation with a BiFunction
2.2.用BiFunction创建
The above method works just fine, but we may need to supply this behavior to something written in the functional style. Since Java 8, we can use BiFunction for this purpose:
上面的方法很好用,但我们可能需要为用函数式写的东西提供这种行为。从Java 8开始,我们可以使用BiFunction来达到这个目的。
BiFunction<String, String, Letter> SIMPLE_LETTER_CREATOR
= (salutation, body) -> new Letter(salutation, body);
2.3. Creation with a Sequence of Functions
2.3.用一个函数序列进行创建
We can also restate this as a sequence of functions each with one parameter:
我们也可以把它重述为一连串的函数,每个函数都有一个参数。
Function<String, Function<String, Letter>> SIMPLE_CURRIED_LETTER_CREATOR
= salutation -> body -> new Letter(salutation, body);
We see that salutation maps to a function. The resulting function maps onto the new Letter object. See how the return type has changed from BiFunction. We’re only using the Function class. Such a transformation to a sequence of functions is called currying.
我们看到,salutation映射到一个函数。结果函数映射到新的Letter对象。看看返回类型是如何从BiFunction改变的。我们只使用了Function类。这种对函数序列的转换被称为currying.。
3. Advanced Example
3.先进的例子
In order to show the advantages of currying, let’s extend our Letter class constructor with more parameters:
为了展示咖喱的优势,让我们用更多的参数来扩展我们的Letter类构造函数。
class Letter {
private String returningAddress;
private String insideAddress;
private LocalDate dateOfLetter;
private String salutation;
private String body;
private String closing;
Letter(String returningAddress, String insideAddress, LocalDate dateOfLetter,
String salutation, String body, String closing) {
this.returningAddress = returningAddress;
this.insideAddress = insideAddress;
this.dateOfLetter = dateOfLetter;
this.salutation = salutation;
this.body = body;
this.closing = closing;
}
}
3.1. Creation by Method
3.1.按方法创建
Like before, we can create objects with a method:
像以前一样,我们可以用一个方法来创建对象。
Letter createLetter(String returnAddress, String insideAddress, LocalDate dateOfLetter,
String salutation, String body, String closing) {
return new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}
3.2. Functions for Arbitrary Arity
3.2.适用于任意Arity的函数
Arity is a measure of the number of parameters a function takes. Java provides existing functional interfaces for nullary (Supplier), unary (Function), and binary (BiFunction), but that’s it. Without defining a new Functional Interface, we can’t provide a function with six input parameters.
Arity是衡量一个函数的参数数量的标准。Java为nullary(Supplier)、unary(Function)和二进制(BiFunction)提供现有的函数接口,但仅此而已。如果不定义一个新的Functional Interface,我们就不能提供一个有六个输入参数的函数。
Currying is our way out. It transforms an arbitrary arity into a sequence of unary functions. So for our example, we get:
咖喱是我们的出路。它将一个任意的算数转化为一连串的单数函数。所以对于我们的例子,我们得到的是:
Function<String, Function<String, Function<LocalDate, Function<String,
Function<String, Function<String, Letter>>>>>> LETTER_CREATOR =
returnAddress
-> closing
-> dateOfLetter
-> insideAddress
-> salutation
-> body
-> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
3.3. Verbose Type
3.3 冗长型
Obviously, the above type is not quite readable. With this form, we use ‘apply’ six times to create a Letter:
很明显,上面的类型不大容易读懂。通过这种形式,我们使用‘apply’六次来创建一个Letter。
LETTER_CREATOR
.apply(RETURNING_ADDRESS)
.apply(CLOSING)
.apply(DATE_OF_LETTER)
.apply(INSIDE_ADDRESS)
.apply(SALUTATION)
.apply(BODY);
3.4. Pre-Filling Values
3.4.预填充值
With this chain of functions, we can create a helper which pre-fills out the first values and returns the function for onward completion of the letter object:
有了这个函数链,我们可以创建一个帮助器,它可以预先填好第一个值并返回函数,以便继续完成字母对象。
Function<String, Function<LocalDate, Function<String, Function<String, Function<String, Letter>>>>>
LETTER_CREATOR_PREFILLED = returningAddress -> LETTER_CREATOR.apply(returningAddress).apply(CLOSING);
Notice, that for this to be useful, we have to carefully choose the order of the parameters in the original function so that the less specific are the first ones.
请注意,要使这一方法有用,我们必须仔细选择原始函数中参数的顺序,使不太具体的参数成为第一个参数。
4. Builder Pattern
4.建设者模式
To overcome the unfriendly type definition and the repeated usage of the standard apply method, meaning you have no clues about the correct order of inputs, we can use the builder pattern:
为了克服不友好的类型定义和重复使用标准的apply方法,也就是说,你没有关于输入的正确顺序的线索,我们可以使用builder模式。
AddReturnAddress builder(){
return returnAddress
-> closing
-> dateOfLetter
-> insideAddress
-> salutation
-> body
-> new Letter(returnAddress, insideAddress, dateOfLetter, salutation, body, closing);
}
Instead of a sequence of functions, we use a sequence of functional interfaces. Notice that the returning type of the above definition is AddReturnAddress. In the following we have only to define the intermediate interfaces:
我们使用的不是一连串的函数,而是一连串的功能接口。注意,上述定义的返回类型是AddReturnAddress。在下文中,我们只需要定义中间的接口。
interface AddReturnAddress {
Letter.AddClosing withReturnAddress(String returnAddress);
}
interface AddClosing {
Letter.AddDateOfLetter withClosing(String closing);
}
interface AddDateOfLetter {
Letter.AddInsideAddress withDateOfLetter(LocalDate dateOfLetter);
}
interface AddInsideAddress {
Letter.AddSalutation withInsideAddress(String insideAddress);
}
interface AddSalutation {
Letter.AddBody withSalutation(String salutation);
}
interface AddBody {
Letter withBody(String body);
}
So using this to create a Letter is quite self-explanatory:
因此,用它来创建一个信件是非常不言自明的:
Letter.builder()
.withReturnAddress(RETURNING_ADDRESS)
.withClosing(CLOSING)
.withDateOfLetter(DATE_OF_LETTER)
.withInsideAddress(INSIDE_ADDRESS)
.withSalutation(SALUTATION)
.withBody(BODY));
Like before, we can pre-fill the letter object:
像以前一样,我们可以预先填入字母对象。
AddDateOfLetter prefilledLetter = Letter.builder().
withReturnAddress(RETURNING_ADDRESS).withClosing(CLOSING);
Notice that the interfaces ensure the filling order. So, we can’t just pre-fill closing.
请注意,接口确保了填充的顺序。所以,我们不能只是预先填充closing。
5. Conclusion
5.总结
We’ve seen how to apply currying, so we’re not constrained by the limited number of parameters supported by the standard Java functional interfaces. In addition, we can easily pre-fill the first few parameters. Furthermore, we’ve learned how to use this to create a readable builder.
我们已经看到了如何应用currying,所以我们不受标准Java函数接口所支持的有限参数数量的限制。此外,我们可以很容易地预先填入前几个参数。此外,我们已经学会了如何利用这一点来创建一个可读的构建器。
As always, the complete code samples are available over on GitHub.
一如既往,完整的代码样本可在GitHub上获得,。