It will allow (almost) direct control over data layout. At least make it so that it is cache/prefetch/SIMD friendly.
Here is the JEP: https://openjdk.java.net/jeps/401
I have a List<Integer>. Maybe I want to replace List with something else, call it L.
Can I write L<Integer> in Java? No. Rust? No. C#? No Kotlin? No.
Does Go have generics yet? Once it does, I'm sure it'll be a No as well.
Haskell and Scala got it right.
<L> void process(final L<Integer> input) {
}
------------------------------------------
<L> void process(final L<Integer> input) {
^
required: class
found: type parameter L
where L is a type-variable:
L extends Object declared in method <L>process(<any>)public <T extends Collection<Integer>> T process(T collectionOfInteger) { return collectionOfInteger; }
S<U> map(S<B> b, (B -> U) func) { //stuff}
Which allows you to do things like: Optional<Integer> i = 4;
(Integer -> Float) func = (i \* 2.5);
Optional<Float> f = map(i, func);
This ability becomes much more powerful when you're parsing some file. Imagine you're parsing some JSON of people, and you're using https://schema.org/Person as the type. So the JSON contains things like additionalName, affiliation, birthPlace, email, gender, image, etc. and each of those are typed respectively - a Name class, Affiliation Class, Location class, Email class, Gender class, Image class, etc. Now imagine that the service you're calling may fail to return certain values.In java, it's idiomatic for libraries like Gson to return null on the fields that aren't returned - so if you try to get a person who doesn't have an additionalName, the additionalName field in the Person object will be null. It would be nice if it could be an Optional<AdditionalName> instead of just an AdditionalName to force the null check, but then the library becomes extremely painful to use. You would end up having to call additionalName.get() whenever you just want the name. If you have HKTs, then you can operate on the fields and ignore the fact that they're technically Optionals.
It's also generally recommended in Java to not use Optionals in fields in classes, or to express the optionality of a value in a constructor. If you do, you have to wrap values in Optionals to make the object, which harms readability.
HKTs can become even more powerful with types other than Optional/Maybe. Either, Monad, Future, Comonad, IO, Functor, Applicative, Task, etc. You would be surprised how many design patterns in Java end up being just a simple combination of these types that are much more ergonomic with HKTs.
Something like the following Scala 3 code is closer to the actual intend, I think:
@main def entryPoint =
trait ConsoleLogger[W[_]]:
extension[E] (w: W[E]) def log(): Unit
given ConsoleLogger[List] with
extension[A] (list: List[A]) def log() = println(list)
given ConsoleLogger[Some] with
extension[A] (some: Some[A]) def log() = println(some)
def process[E, L[_] : ConsoleLogger](input: L[E]): Unit =
input.log()
process(List(23))
process(Some("'process' is generic!"))
//process(None) // Does not compile — even Some and None are interchangeable under subtype polymorphism
Here process(input) can work with any wrapped input as long as a ConsoleLogger instance is given for a specific wrapper type. The point is: There doesn't need to be any relation between the wrapper types (like in the example with List and Option.Some), only the "shape" needs to match W[_] (which could be actually also adapted with type lambdas in case it doesn't match, but not going into this here).I dream about "implicit" interfaces in Java like in Go. So everything that has "flatMap" would implicitly implement "FlatMappable" or something like that.
def process[L[_]](input: L[Int]): Unit = ???
Where `L[_]` could be any type that has a type parameter, e.g. `List[_]`, `Option[_]`, `Future[_]` or `Either[String, *]`. This opens the door to a new level of abstraction where other languages must resort to code generation or similar ad hoc-like solutions.For example, there is a typeclass Foldable[F[_]] which provides the ability to "fold" (i.e. recurse over) some type F[_] which takes a type parameter. Then you have typeclass definitions for Foldable[Set], Foldable[List], Foldable[Tree], Foldable[Option], etc.
Then if you have an operation and you want it to work on any collection that can be folded, then you can just define it in terms of a generic type that has a Foldable typeclass. For instance to generically sum a collection of ints, you might do
def sum[F[_] : Foldable](ints: F[Int]): Int = ...
Now you can call sum(List(1,2,3)), sum(Set(1,2,3)), sum(Option(1)), etc...
You want to fold over Options? Make a FoldableOption class implementing Foldable<Option> and pass "new FoldableOption(myOptionValue)" to whatever places you need. Haskell does pretty much the same (unless they moved away from dictionary passing).
public <T extends Collection<Integer>> T process(T collectionOfInteger) { return collectionOfInteger; }
And basically your T will be as wide as the interface/class it extends in the generic parameter.
I guess the most Java way to do that would be using Iterable:
public <Z, T extends Iterable<Z>> T process(T iterable) { return iterable; }
public <Z, T extends Collection<Z>> List<Z> process(T collectionOfInteger) { return collectionOfInteger.stream() .collect(toList()); }
These are the three implementations I want to hide behind a single abstraction:
// java.util.Optional
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
// java.util.concurrent.CompletionStage:
public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
// java.util.stream.Stream:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Any suggestions? public <T, Z, F extends Function<? super T, Z>> Z apply(Function<F, Z> target, F fn) {
return target.apply(fn);
}
@Test
public void test() throws Exception {
Stream<String> stream = Stream.of("foo", "bar", "baz");
stream = apply(stream::flatMap, s -> Stream.of(s + "_a", s + "_b"));
System.out.println(stream.collect(toList()));
Optional<String> bean = Optional.of("bean");
bean = apply(bean::flatMap, b -> Optional.of("soy"));
System.out.println(bean);
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> future2 = apply(future1::thenCompose, string -> CompletableFuture.supplyAsync(() -> string + " world"));
System.out.println(future2.get());
}
Output:[foo_a, foo_b, bar_a, bar_b, baz_a, baz_b]
Optional[soy]
hello world
I'm trying to figure out how to refer to flatMap directly from the abstract function, e.g.
flatMapTwice(x, fun) {
return x.flatMap(fun).flatMap(fun);
}
or chain(x, fun1, fun2) {
return x.flatMap(fun1).flatMap(fun2);
}
It should also preserve the type information (just like you did above). I don't want the caller to need to downcast from FlatMappable.It doesn't need to be x.flatMap(f) - I'd be interested seeing a flatMap(x, f) version.
public interface FlatMappable<T> {
<U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper);
}
public class FlatMappableOption<T> implements FlatMappable<T> {
private final Option<T> value;
public FlatMappableOption<T>(Option<T> value) {
this.value = value;
}
<U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
return value.flatMap(mapper);
}
}
// similar wrappers for CompletionStage and Stream
Now whenever you want to pass a Stream/CompletionStage/Option to something that accepts FlatMappable, you either a) already has it wrapped in FlatMappable, so pass that wrapper in; or b) has a naked value of known type, so wrap it in the propper wrapper class and pass the resulting wrapper in.Of course, it would be nice to have this wrapping done automatically instead of manually, but that's pretty much how it's actually implemented behind the scenes (dictionary passing, or look at Go's way to wrap things inside interface{}).
public <Z, T extends Iterable<Z>, R> T<R> process(T, Function<Z,R>)
change the type parameter of T<Z> to T<R> and return an instance of T<R>, but the best I can do is return an instance of Iterable<R> because I cannot define T to have a type parameter.
I'm curious what your were working on where you ran into this?
* At the same job, a coworker expressed a desire to rewrite it with Observables.
* At a different job, a different coworker rewrote a chunk of code from Observables into futures.
* At my current job, we use micronaut, which has announced it's switching from rxjava2 to project reactor.
In the above cases, having interfaces would really help in changing the code piece-by-piece. And the rewrites wouldn't be necessary if they were behind interfaces.
In the general case I just want to program to the interface, and not pin my business logic to any particular implementation.
public <A, B, T extends Iterable<A>, R extends Iterable<B>> R process(T input, Function<? super A, ? extends B> transformer,
Function<Iterable<B>, R> helper) {
return helper.apply(StreamSupport.stream(input.spliterator(), false)
.map(transformer)
.collect(toList()));
}
@Test
public void testProcess() {
Iterable<Integer> ints = process(Arrays.asList("1", "2", "3"), Integer::parseInt, identity());
System.out.println(ints);
}
Outputs nicely integers nicely converted from strings:
[1, 2, 3]:-P
`identity()` is a static import of `Function.identity()`
baz(bar(foo(t, f)));
T<A> should go into foo(..) and come out (e.g. as T<B>) such that I can then call bar(..) on it.But in this case it leaves foo(..) as R extends Fooable, which is not something I can pass into bar(..).
class SpecialIterator<R> extends Iterator<R>{}
SpecialIterator<String> strings =...;
SpecialIterator<Integer> ints = process(string, magicFunc);
The Iterator example starts to break down, so here's a more concrete example:
I have 3 classes: AbstractTester, StringTester, and ListTester
class AbstractTester<R, T extends AbstractTester<R, ? super T>>
class StringTester<R> extends AbstractTester<R, StringTester<R>>
class ListTester<R, T extends AbstractTester<R, ? super T>>
I want to make a method in AbstractListTest like this: public <Z> T<Z> convert(Z z)
I have supporting code in my classes to create an return an instance of type T<Z>, but I cannot express the type T<Z> in the type system. The type parameter T can extend something with a type parameter, and be passed a type with a type parameter, but it cannot be defined to have a type parameter itself.
This is for a fluent api framework, where I need the compiler to know the type T<Z> without casting
I can do something like this: public <Z, Y extends AbstractTester<Z, ? super T>> Y convert(Z z);
but that will return an AbstractTester<Z> which can be cast to a StringTester<Z>, but the compiler does not know it is a StringTester<Z>
A subclass of AbstractTester is not required to have any type parameters itself. Converting a T to a T<Z> would not be legal for a T without a type parameter. To do what I want, Java would need to add a new type bounds operator to restrict a type parameter to subclasses with a particular type parameter.
Java would need to allow me to define ListTester like this to restrict T to only subclasses with a type parameter:
class ListTester<R, T<_> extends AbstractTester<_, ? super T<_>>
I'd love to be wrong, and would be very grateful to find a way to do this.
foo.doSomething() // returns Iterable.
.doSomethingElse(); // Iterable doesn't implement doSomethingElse();[1] https://cr.openjdk.java.net/~jrose/values/parametric-vm.pdf
And the VM can specialize on any values, not just types.