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. <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; }
I dream about "implicit" interfaces in Java like in Go. So everything that has "flatMap" would implicitly implement "FlatMappable" or something like that.
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).