6 ms·
> 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
by mrkeen 6y ago
> 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.
- riffraff 6y agoI don't understand what you're saying, what is the problem with L<Integer> ?
- mrkeen 6y agoThere 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>)
- p2t2p 6y agoWorks perfectly: public <T extends Collection<Integer>> T process(T collectionOfInteger) { return collectionOfInteger; }
- petersellers 6y agoWhat if your "T" isn't a collection though?
- Joker_vD 6y agoThen what can you really do with it except of returning it back as-is?
- throw149102 6y agoNothing - 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 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.
- still_grokking 6y agoIndeed, 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).
- p2t2p 6y agoYeah, 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.
- willtim 6y agoThis 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.
- Taig 6y agoThe 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.
- riffraff 6y agooh, I see thanks for the explanation.
- deleted 6y ago[deleted]
- josefx 6y agoWhat 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?
- dboreham 6y agoIt'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.
- thinkharderdev 6y agoGenerally 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...
- Joker_vD 6y agoTypeclass 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).
- p2t2p 6y agoNo 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; }
- p2t2p 6y agoI can turn any collection to list like that: public <Z, T extends Collection<Z>> List<Z> process(T collectionOfInteger) { return collectionOfInteger.stream() .collect(toList()); }
- deleted 6y ago[deleted]
- mrkeen 6y agoGreat! 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?
- p2t2p 6y agoI'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
- fooker 6y agoCan do with C++ though. https://en.cppreference.com/w/cpp/language/template_parameters#Template_template_parameter https://en.cppreference.com/w/cpp/language/template_paramete...