Java 8 What's wrong: local vs. closures

Source: Internet
Author: User

"Editor's note" the author of this article is dedicated to natural language processing for many years Pierre-yves Saumont,pierre-yves has more than 30 lectures on Java software development books, since 2008 began to work in Alcatel-lucent company, as a software development engineer.

This paper mainly introduces the closure and local application function in Java 8, which is presented by the domestic ITOM management platform OneAPM.

There are many misconceptions about Java 8. For example, consider Java 8 as a closure feature of Java. The idea is wrong because the closure feature has existed since the beginning of Java. Closures, however, are flawed. Although Java 8 seems to favor functional programming, we should try to avoid using Java closures. However, Java 8 does not provide much help in this regard.

We know that parameter evaluation time is a significant difference between using a method and using a function. In Java, we can write a method with parameters and a return value. But can this be called a function? Of course not. Method can only be manipulated by a call, which means that its parameters are evaluated before the method executes. This is the result of parameters passed by value in Java.

The function is different from the other. When manipulating a function, we can not evaluate the parameters and have absolute control over when the parameters are evaluated. Also, if a function has more than one parameter, they can take a different value at the same time. This can be done by applying local. But first, we'll consider how closures are implemented.

Closure examples

For functions, closures can fetch content in the context of encapsulation. In functional programming, the result of a function should be determined only by its parameters. It is clear that closures have broken the rule.

Take a look at the example in Java 5/6/7:

private Integer b = 2;     List list = Arrays.asList(1, 2, 3, 4, 5);     System.out.println(calculate(list.stream(), 3).collect(toList()));     private Stream calculate(Stream stream, Integer a) {       return stream.map(new Function() {         @Override         public Integer apply(Integer t) {           return t * a + b;         }       });     }     public interface Function<T, U> {       U apply(T t);     }

The above code will produce the following results:

[5, 8, 11, 14, 17]

The resulting result is a function f (x) = x * 3 + 2 for [1, 2, 3, 4, 5] The mapping of the column. There is no problem in this step. But can 3 and 2 be replaced with other values? In other words, is it not a function f (x, A, b) = x * A + b for the mapping of the column?

Yes, it's not. The reason for this is that both A and B are implicitly defined final keywords, so they participate in the calculation as constants when the function is evaluated. But of course, their values are also subject to change. Their final properties (implicitly defined in Java 8, which are explicitly defined in previous versions) are just one way for the compiler to optimize the compilation process. The compiler does not care about any potential change values. It only cares if the reference has not changed, that is, it wants to make sure that Integer the integer object a and b the reference does not change, but does not care about their value. This feature can be seen in the following code:

 private Integer b = 2;     private Integer getB() {       return this.b;     }     List list = Arrays.asList(1, 2, 3, 4, 5);     System.out.println(calculator.calculate(list.stream(), new Int(3)).collect(toList()));     private Streamcalculate00(Streamstream, final Int a) {       return stream.map(new Function() {         @Override         public Integer apply(Integer t) {           return t * a.value + getB();         }       });     }     -     static private class Int {       public int value;       public Int(int value) {         this.value = value;       }      }

Here, we use mutable objects a (which belong to Int classes, not immutable Integer classes), and a method to get them b . Now let's simulate a function with three variables, but still use a function with only one variable, and use closures instead of the other two variables. Obviously, this is non-functional because it breaks down the criteria that depend only on function parameters.

One result is that, despite the need, we cannot reuse this function elsewhere because it relies on context and not just on parameters. We want to replicate this code to make it reusable. Another result is that because it requires context to run, we cannot perform function tests alone.

So, should we use a function with three parameters? We may think that this cannot be achieved. Because the specific implementation process is related to when the three parameters are evaluated. They all value in different places. If we had just used a function with three parameters, they would have to take a value at the same time. The mapping method only maps functions with one parameter to the stream, and it is not possible to map a function with three parameters. Therefore, the remaining two parameters must already be evaluated when the function is bound (that is, when it is passed to the map). The workaround is to first value the remaining two parameters.

We can also use closures to implement this function, but the resulting code is not testable and there may be overlap.

Using the syntax of Java 8 (lambdas) does not change this situation:

private Integer b = 2;     private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {       return stream.map(t -> t * a + b);     }

What we need is a method of acquiring three parameters at different times--currying (locally applied, also called the curry function, although it is actually invented by Moses Sh?nfinkel).

Using local closures

A local closure is a function parameter value, each step generates a new function with fewer parameters. For example, if we have the following function:

f (x, y, Z) = x * y + Z

We can take the parameter value as 2,4,5 and get the following equation:

F (3, 4, 5) = 3 * 4 + 5 = 17

We can also take only one parameter of 3 to get the following equation:

F (3, y, Z) = g (Y, Z) = 3 * y + Z

Now, we get the new function g with only two parameters. Then apply the function locally and assign the value 4 to Y:

G (4, Z) = h (z) = 3 * 4 + Z

The order in which the parameters are assigned has no effect on the result of the calculation. Here, we are not adding locally, (if we add it locally, we also have to consider operator precedence.) Instead, a partial application of the function is performed.

So, how do we implement this approach in Java? The following are the applications in JAVA5/6/7:

private static List<Integer> calculate(List<Integer> list, Integer a) {       return list.map(new Function<Integer, Function<Integer, Function<Integer, Integer>>>() {         @Override         public Function<Integer, Function<Integer, Integer>> apply(final Integer x) {           return new Function<Integer, Function<Integer, Integer>>() {             @Override             public Function<Integer, Integer> apply(final Integer y) {               return new Function<Integer, Integer>() {                 @Override                 public Integer apply(Integer t) {                   return x + y * t;                 }               };             }           };         }       }.apply(b).apply(a));     }

The above code can do exactly what you need, but it's hard to convince developers to write code in this way! Fortunately, the lambda syntax of Java 8 provides the following implementations:

private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {      return stream.map(((Function<Integer, Function<Integer, Function<Integer, Integer>>>)                            x -> y -> t -> x + y * t).apply(b).apply(a));     }

What do you think? Or, is it possible to write a little bit simpler:

private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {       return stream.map((x -> y -> t -> x + y * t).apply(b).apply(a));     }

Yes, but Java 8 cannot judge the parameter type by itself, so we must use the manifest type to help confirm (manifest means explicit in the Java specification). To make the code look neater, we can use some tips:

interface F3 extends Function<Integer, Function<Integer, Function<Integer, Integer>>> {}     private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {       return stream.map(((F3) x -> y -> z -> x + y * z).apply(b).apply(a));     }

Now, let's name the function and reuse it if necessary:

private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {       F3 calculation = x -> y -> z -> x + y * z;       return stream.map(calculation.apply(b).apply(a));     }

We can also declare the calculation function as a static member of an auxiliary class and use static import to further simplify the code:

public class Functions {       static Function<Integer, Function<Integer, Function<Integer, Integer>>> calculation =            x -> y -> z -> x + y * z;         }         ...         import static Functions.calculation;         private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {           return stream.map(calculation.apply(b).apply(a));         }

Unfortunately, Java 8 encourages the use of closures. Otherwise, I'll introduce more functional syntactic sugars that make it easier to use locally. For example, in Scala, the above example can be rewritten like this:

stream.map(calculation(b)(a))

Although we can't write this in Java. However, with the following static method, we can achieve similar effects:

static Function<Integer, Function<Integer, Function<Integer, Integer>>> calculation         = x -> y -> z -> x + y * z;     static Function<Integer, Integer> calculation(Integer x, Integer y) {       return calculation.apply(x).apply(y);     }

Now, we can write:

 private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {       return stream.map(calculation(b, a));     }

Note that it calculation(b, a) is not a function with two parameters. It is only a method that, after calling two parameters locally to a function with three parameters, returns a function with one parameter that can be passed to the mapping function.

Now, the calculation method can be tested separately.

Automatic Local invocation

In the previous example, we have already practiced the partial invocation in our own hands. However, we can write programs to automate the invocation process. We can write a method that receives a function with two parameters and returns the local invocation version of the function. It's very simple to write:

public <A, B, C> Function<A, Function<B, C>> curry(final BiFunction<A, B, C> f) {       return (A a) -> (B b) -> f.apply(a, b);     }

If necessary, we can also write a method to reverse the process. This procedure accepts a function function as a parameter, returns a function function that returns C, and finally returns a A/b bifunction function that returns C.

public <A, B, C> BiFunction<A, B, C> uncurry(Function<A, Function<B, C>> f) {       return (A a, B b) -> f.apply(a).apply(b);     }
Other applications for partial invocation

There are many more ways to apply local calls. The most important application is to simulate multi-parametric functions. The single-parameter function ( java.util.functions.Function ) and the double-argument function () are provided in Java 8 java.util.functions.BiFunction . But it does not provide functions that exist in three, four, five, or even more parameters in other languages. In fact, there is no such function is not important. They are just the syntactic sugars that are applied when all parameters are evaluated at the same time in a particular case. In fact, this is also the reason for the BiFunctin existence of Java 8: the common use of functions is to simulate a two-tuple operator, (note that there is an interface in Java 8 BinaryOperator , but it is used only for two parameters and a special case where the return value belongs to the same type.) We will discuss this in the next article. )

Local calls are very useful when each parameter of a function needs to be valued in different places. With a local call, we can take a value in one component and pass the result of the calculation to another component to value the other parameters, so repeatedly, until all the parameter values are taken.

Summary

Java 8 is not a functional language (and may never be). However, we can still use the functional paradigm in Java (even in versions prior to Java 8). It does have a slight cost. But the cost has been drastically reduced in Java 8. Still, developers who want to write functional code will have to take their brains to master the paradigm. Using local calls is one of the intellectual achievements.

Please remember:

(A, B, C), D

Can always be replaced by the following methods:

D, C, B, A

Even if Java 8 cannot judge the type of the expression, you can simply specify its type yourself. This is a partial call, which is always more secure than a closure.

OneAPM can provide you with an end-to-end Java application Performance solution, and we support all common Java frameworks and application servers to quickly discover system bottlenecks and pinpoint the root cause of the anomalies. Minute-level deployment, instant experience, Java monitoring has never been easier. To read more technical articles, please visit the OneAPM Official technology blog.
This article was transferred from OneAPM official blog
Compiled from: Https://dzone.com/articles/whats-wrong-java-8-currying-vs

Java 8 What's wrong: local vs. closures

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.