News for hackers. Stuff that matters.
Re:Background: how we got the generics we have (2020) by gkorland on Thursday April 01, 2021 @05:53AM (#26658458)
I think the way Rust took with Generics should be the next step for Java, it for sure can boost the Java apps performance. In Rust you have both types erasure and C++ style templates which don't erasure the types.
Re:Background: how we got the generics we have (2020) by kaba0 on Thursday April 01, 2021 @07:50AM (#26659259)
I don’t think templates are wanted, but with the coming of unification of primitive and reference types, Java could get a huge speedup in performance.
Re:Background: how we got the generics we have (2020) by henryjcee on Thursday April 01, 2021 @08:50AM (#26659789)
This has passed me by, what's happening?
Re:Background: how we got the generics we have (2020) by BenoitP on Thursday April 01, 2021 @09:11AM (#26660038)
Not so much a unification, as the new constructs sort of enhance and retrofit current primitives types. These new primitives are "class instances that lack object identity and can be stored and passed directly, without object headers or indirections".

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

Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @07:57AM (#26659328)
> the way Rust took with Generics should be the next step for Java

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.

Re:Background: how we got the generics we have (2020) by fooker on Thursday April 01, 2021 @01:44PM (#26663751)
Re:Background: how we got the generics we have (2020) by riffraff on Thursday April 01, 2021 @08:01AM (#26659377)
I don't understand what you're saying, what is the problem with L<Integer> ?
Re:Background: how we got the generics we have (2020) by Taig on Thursday April 01, 2021 @08:42AM (#26659739)
The feature that mrkeen is describing is called higher kinded types (HKT). I.e. in Scala (and similarly in Haskell), you could write something like:

    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.
Re:Background: how we got the generics we have (2020) by riffraff on Friday April 02, 2021 @12:34PM (#26672995)
oh, I see thanks for the explanation.
Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @08:27AM (#26659566)
There shouldn't be a problem with it!

    <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>)
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @09:52AM (#26660513)
Works perfectly:

public <T extends Collection<Integer>> T process(T collectionOfInteger) { return collectionOfInteger; }

Re:Background: how we got the generics we have (2020) by willtim on Thursday April 01, 2021 @12:54PM (#26663050)
This uses subtyping polymorphism not higher-kinded parametric polymorphism. If subtyping worked perfectly in all important cases, "generics" (parametric polymorphism) would not have been retrofitted to Java.
Re:Background: how we got the generics we have (2020) by petersellers on Thursday April 01, 2021 @12:53PM (#26663025)
What if your "T" isn't a collection though?
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @09:02PM (#26668149)
Yeah, as Joker_vD said you need _some_ common ground as to where you get your methods from.

I dream about "implicit" interfaces in Java like in Go. So everything that has "flatMap" would implicitly implement "FlatMappable" or something like that.

Re:Background: how we got the generics we have (2020) by Joker_vD on Thursday April 01, 2021 @01:10PM (#26663317)
Then what can you really do with it except of returning it back as-is?
Re:Background: how we got the generics we have (2020) by throw149102 on Saturday April 03, 2021 @01:11AM (#26678576)
Nothing - unless you also take a function that allows you to transform from one type to another. For example, you can write a function like:

  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.

Re:Background: how we got the generics we have (2020) by still_grokking on Saturday April 03, 2021 @01:42AM (#26678666)
Indeed, without a type-class constraint on the type constructor this isn't useful at all.

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).
Re:Background: how we got the generics we have (2020) by josefx on Thursday April 01, 2021 @09:02AM (#26659927)
What operations can you perform on an L<Integer>? Does it work based on duck typing instead of explicitly declaring what base interface (Collection<Integer>) it requires?
Re:Background: how we got the generics we have (2020) by thinkharderdev on Thursday April 01, 2021 @09:43AM (#26660425)
Generally you are using a typeclass to specify behavior (which I guess is a kind of duck-typing).

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...

Re:Background: how we got the generics we have (2020) by Joker_vD on Thursday April 01, 2021 @01:20PM (#26663462)
Typeclass instance = Adaptor/Decorator in OOP terms, automatically derived (and wrapped in) for you by the compiler.

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).

Re:Background: how we got the generics we have (2020) by dboreham on Thursday April 01, 2021 @09:20AM (#26660140)
It's a similar idea to inheriting from a base collection class (iterable, etc). Not quite the same though. It is a kind of duck typing I suppose. You get to specify the nature of a class of types. e.g. it can be ordered, behaves like a number, etc.
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @09:51AM (#26660510)
No worries in Java (and I bet in C#) in works no problemo:

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; }

Re:Background: how we got the generics we have (2020) by vimax on Thursday April 01, 2021 @01:39PM (#26663705)
I want to do something like this:

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.

Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @10:10PM (#26668451)
Sooooo.... A bit more involved than what you describe but I managed to do that:

    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()`

Re:Background: how we got the generics we have (2020) by vimax on Friday April 02, 2021 @05:13PM (#26676059)
That's close, but not quite what I want. The Iterator example starts to break down, but I want to pass in a subclass of Iteratable and get that subclass back.

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.

Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @10:12PM (#26668460)
The only caveat is that if you change `Iterable<Integer>` to say `Iterable<String>` the compilation error becomes a bit weird in that it says you provide `Iterable<Object>` while `Iterable<String>` is requested. But you do get compile time check after all.
Re:Background: how we got the generics we have (2020) by mrkeen on Friday April 02, 2021 @04:17PM (#26675490)
T is not R though. I want to preserve the static type information such that if I call:

    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(..).

Re:Background: how we got the generics we have (2020) by zeroimpl on Friday April 02, 2021 @05:25PM (#26676169)
I’d just solve this by passing in a Class<S extends Iterable<R>> arg and making the returned list be an instance of this class, instead of forcing the input collection type to be the same as the output collection type.
Re:Background: how we got the generics we have (2020) by mrkeen on Saturday April 03, 2021 @04:16PM (#26684359)
Then I can't chain method calls.

    foo.doSomething()      // returns Iterable.
       .doSomethingElse(); // Iterable doesn't implement doSomethingElse();
Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @05:59PM (#26666688)
This comment explains my problem much better than I did.
Re:Background: how we got the generics we have (2020) by vimax on Friday April 02, 2021 @05:22PM (#26676142)
See my other comment for a longer explanation.

I'm curious what your were working on where you ran into this?

Re:Background: how we got the generics we have (2020) by mrkeen on Saturday April 03, 2021 @04:31PM (#26684451)
* At a previous job, I needed to change a fairly large codebase from using Guava futures to Java 8 futures.

* 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.

Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @10:13PM (#26668467)
see my comment for the parent with _a_ solution =)
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @09:54AM (#26660536)
I can turn any collection to list like that:

public <Z, T extends Collection<Z>> List<Z> process(T collectionOfInteger) { return collectionOfInteger.stream() .collect(toList()); }

Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @10:14AM (#26660740)
Great!

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?
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @11:24AM (#26661621)
I'm not sure if I catch your drift, but if I do:

    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

Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @05:32PM (#26666360)
This is interesting stuff.

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.

Re:Background: how we got the generics we have (2020) by Joker_vD on Thursday April 01, 2021 @01:30PM (#26663607)
Sure:

    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{}).

Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @11:44AM (#26661956)
Also I don't understand why would you need HKT here. I don't see language limitation here, I see poorly designed inheritance in the standard library. They should've introduced "FlatMappable" type and make Option and Stream extend it. CompletionStage, I'm torn on it, on one hand yes, it is a flatMap essentially but semantics is a bit different...
Re:Background: how we got the generics we have (2020) by p2t2p on Thursday April 01, 2021 @12:00PM (#26662240)
Oh my god, there's more to that, Optional is a class, not an interface and it's final :facepalm:
Re:Background: how we got the generics we have (2020) by mrkeen on Thursday April 01, 2021 @05:39PM (#26666440)
I've made my peace with those aspects of the libraries. I'm happy to wrap the standard classes in MyOption, MyStream, etc. Then I can extend FlatMappable myself. I just don't know how to declare and implement FlatMappable in a sane way (preserving type information, not needing casts, etc.)
Re:Background: how we got the generics we have (2020) by kasperni on Thursday April 01, 2021 @12:33PM (#26662730)
A Parametric VM [1] is part of Java's project Valhalla. However, it is currently some releases away.

[1] https://cr.openjdk.java.net/~jrose/values/parametric-vm.pdf

Re:Background: how we got the generics we have (2020) by _old_dude_ on Thursday April 01, 2021 @12:55PM (#26663061)
Very cool, it seems to be the best of both worlds, the specialization of the class/fields is done the first time a specialized type is instantiated while the specialization of methods is done by the JIT.

And the VM can specialize on any values, not just types.

Re:Background: how we got the generics we have (2020) by dep_b on Thursday April 01, 2021 @07:45AM (#26659229)
There is no next step for Java, because of backwards compatibility this is as good as it gets.
Re:Background: how we got the generics we have (2020) by Felk on Thursday April 01, 2021 @08:29AM (#26659590)
While it does need to maintain backwards compatibility, Project Valhalla seems promising on improving on some of Java Generic's pain points. So it's not a dead end quite yet