Home > Enterprise >  Why casting is required with compose method but not with andThen Method
Why casting is required with compose method but not with andThen Method

Time:04-03

I have following expression that gets executed successfully:

Function<Long,Long> y = ((Function<Long,Long>)(x -> x*x)).andThen(x -> x 1).andThen(x -> x 2);

I understand why casting is required with the first lambda expression here. But following lambda gives error that "x 1" is not a valid operation for the second compose lambda expression

Function<Long,Long> y = ((Function<Long,Long>)(x -> x*x)).compose(x -> x 1).compose(x -> x 2);

I was able to resolve the above error using casting with compose:

Function<Long,Long> y = ((Function<Long,Long>)(x -> x*x)).compose((Function<Long,Long>)x -> x 1).compose(x -> x 2);

I have following questions:

  1. Why do we need casting with compose calls but not with andThen calls?
  2. Why do we need casting with intermediate compose calls but not with terminal compose calls?

CodePudding user response:

The reason is the behavior of Function.compose and Function.andThen being non identical and non swappable.

If you run the following code.

    Function<Long,Long> y1 = ((Function<Long,Long>)(x -> x*x)).andThen(x -> x 1).andThen(x -> x 2);

    System.out.println(y1.apply(10l));

    Function<Long,Long> y2 = ((Function<Long,Long>)(x -> x*x)).compose((Long x) -> x 1).compose(x -> x 2);

    System.out.println(y2.apply(10l));

Even though we run both functions with same values (10) it returns different values. Where andThen is used it returns 103 (10x10 (1 2)) and where compose is used it returns 169 (10 1 2, 13x13). Thus compose is called before the multiplication lambda applies and compose gets a Function<Object, Long> as the parameter instead of Function<Long, Long> compose has no visibility as to any lambda that happened prior because it will be first to be called.

Since there is no context at the time calling compose we need to either cast to Function<Long, Long> or use type in the lambda itself as I have done. Hope this helps.

CodePudding user response:

Why do we need casting with compose calls but not with andThen calls?

The two methods are different. compose() takes a function whose input is of a type that is not necessarily the same as the current function's parameter type. Here's a slightly modified example to show that the compiler did not have to assume Long:

Function<Long, Long> f = (x -> x * x);
Function<String, Long> g = f.compose(Long::parseLong);

You can observe that f.compose() has a type argument of type String. In the above code, it's inferred from the assignment context (i.e., the compiler knows the input is String-typed because the resulting function is being assigned to a Function<String, Long> variable).

When it comes to .andThen(), however, things are simpler for the compiler : the type parameter <V> is for the output of the given function (not for the input, as is the case for compose). And because it already knows the input type, it has all the information: .andThen(x -> x 1) can only have Long as output type, because Long int will produce long, boxed to Long. The end.

Why do we need casting with intermediate compose calls but not with terminal compose calls?

Now, think about it, what happens if I wrote this?

Function<String, Long> g = f.compose(Long::parseLong).compose(Long::parseLong);

What happens is that the compiler is ready to infer the <V> of the last .compose() to String because of the assignment context (see above).
Question is: Should it assume String for the intermediate .compose()? The answer is Yes in this case* (because Long.parseLong only takes a string, there's no overload), but the compiler doesn't do that; it's a known limitation.

I can get it to work with f.<String>compose(Long::parseLong).compose(Long::parseLong); (which of course breaks my last .compose() call for obvious reasons, but you get the idea.

In other words, you can fix it with

A type witness

...<Long>compose(x -> x   1).compose(x -> x   2)

An explicit parameter type (my preferred option)

    ...compose((Long x) -> x   1).compose(x -> x   2)

*I say "yes in this case" because you cannot expect the compiler to always know the type. It's unambiguous here because Long.parseLong with a single parameter is not overloaded, so we can argue that the compiler could infer the intermediate .compose()'s <V> as <String>. But that should not be understood to mean that the compiler should be able to perform such inference in all situations. The function passed to .compose() could be one taking any other parameter type. The end to the discussion for now is that the compiler does not support this kind of inference.

  • Related