A few days ago I started to re-design Vavr 0.9 towards 1.0. We collected ideas for months, now is the time to put them into practice.
The more I think about 1.0, the more I realize that I want to simplify Vavr. I already mentioned earlier that we will reduce Vavr over time as the 'slang' is adapted by the 'host language'. Our main design principle is to align to Scala but we also see that several things do not work well in Java, mostly because of the type system.
I'm aware of the fact that the users of Vavr rely on continuity. Vavr 1 will break backward compatibilty as we already stated in previous posts. I want to take the opportunity to re-design Vavr's type hierarchy from the scratch.
Dismantling the Core API
Core features like tuples and n-ary functions aren't properly integrated into the Java language. It turned out that n-ary types do not scale very well. It is a smell that we need code generators in order to tackle the maintenance complexity.
Our maximum arity of eight isn't sufficient in several cases. For example our validation type can only handle up to eight values. In practice more fields are validated. We searched for a strategy to combine several validations. In other words: we searched for a workaround in order to fix design flaws. This is where complexity starts.
My answer is simple, I want a clear cut by completely getting rid of the code generator and searching for other design solutions. What options do we have? Currently I see the following:
- The curried form of a function is an alternate notion for an n-ary function.
Function3<T1, T2, T3, R> f3 = (t1, t2, t3) -> r; Function<T1, Function<T2, Function<T3, R>>> f = t1 -> t2 -> t3 -> r;
Local variable type inference of Java 10 won't be of much help here to reduce the ceremony,
var does not handle lambdas.
- We are able to express heterogeneous generic types using recursive type definitions.
HCons<String, HCons<Integer, HCons<Boolean, HNil>>> x = HList.of("Vavr", HList.of(1, HList.of(true, HList.empty()));
One major flaw of Java in general is that it overdid types, for example function types. Libraries followed this orgy, e.g. when supporting checked functions.
Some of these signatures could be useful in practice but Vavr will not support them. We simply focus on those signatures that can't be expressed in other ways. It boils down to the following functions.
- CheckedFunction<T, R>
- CheckedRunnable ≈ CheckedFunction<void, void>
- CheckedSupplier<R> ≈ CheckedFunction<void, R>
- CheckedConsumer<T> ≈ CheckedFunction<T, void>
Additionally we will support
- CheckedPredicate<T> ≈ CheckedFunction<T, boolean>
In Vavr 2.0 (not planned yet), these also will get obsolete (see JEP 218). This confirms my strategy of dismantling Vavr.
The Essence of Vavr One
Simplicity is the most important design principle. Here is my take on Vavr 1.
I removed useless interfaces such as Lambda and Tuple. As explained above I removed Function0..8 and also Tuple0..8. In fact Tuple0 and Tuple1 where academical. In most cases we used Tuple2 and rarely Tuple3. I think Tuple4+ are not needed at all.
This will have implications on Vavr's pattern matching API and also on for comprehensions. Native pattern matching will come to Java. For comprehension are syntactic sugar of map and flatMap with the overhead of creating unnecessary additional instances. I will re-think that.
Additional collections will be packaged in a separate module.
Java 8 and Java 9 work well together. However, native Java 9 modularization is awkward to achieve when we still strive for supporting Java 8.
I'm aware of the fact that currently no one is adapting Java 9 for several reasons. It implies big changes, e.g. reflection may not work anymore. I'm still thinking about supporting Java 9+ only. Maybe it is the easiest solition to take the decision from the users. The majority is using Java 8, they should use Vavr 0.9.x. Vavr 1.0 is for those who are able to use the new features of Java and Vavr.
Vavr 1.0 is still conceptually in the make. I will blog on a regular basis now about my progress...