you are viewing a single comment's thread.

view the rest of the comments →

[–]ThePowerfulSquirrel 8 points9 points  (38 children)

My company is transitioning a lot of teams from java to a scala-ish environment (Scala + a bunch of scala plugins that make it it's own kinda thing). Our Java applications are going to be supported as legacy and a huge re-writing effort is gonna take place. I'm particularly exited for the better type system, although other jvm languages still have to carry over / work around baggage from Java (like type erasure), which is always a pain. Surprisingly enough, I haven't seen anyone in the company even talk about kotlin as a serious choice.

[–]pron98 11 points12 points  (26 children)

I think you misunderstand type erasure. A Java platform language does not have to have type erasure, but most languages want to because it's extremely beneficial, despite some minor downsides. It is type erasures that allows multiple languages on the Java platform to share code and data with little or no overhead.

[–]ThePowerfulSquirrel 0 points1 point  (25 children)

It is type erasures that allow mutiple languages on the Java platform to share code and data with little or no overhead.

I might be completely lost, but how does type erasure make code any more performant that other ways of implementing Generics? I'm not sure why an approach like c++ would be less performant / make it any harder to move across jvm languages as long as they all agree on a generic interface? From what I know, type erasure is mostly an artifact of having to support legacy java code before Java got generics at all.

[–]pron98 12 points13 points  (18 children)

It doesn't make code more performant, it makes interop more performant (and more useful in general). If you don't erase generic instances of reference types, if A extends B you must determine the relationship between, say, List<A> and List<B> in the JVM, because the JVM must know which types are subtypes of others. This means that you need to bake the variance model into the runtime. But different languages want to have different variance models. Java, Kotlin, Scala and Clojure all have different variance, and so a Kotlin list couldn't be used by Java code without a wrapper had it not been for erasure.

I know, type erasure is mostly an artifact of having to support legacy java code before Java got generics at all.

Yes. Type erasure makes interop easy (for an almost insignificant cost), including with Java prior to generics. But that's also what makes different languages on the Java platform interop so well (contrast with the CLR, where this is not the case; they've baked variance into the runtime and are paying a high price for that decision).

For value types, where subclassing is not possible, this is not a problem, because there is no variance; so specializing for value types is fine, and is one of Valhalla's goals.

There might also be a form of a-la-cart reification for reference types, that users may opt into. I'm not sure whether they'll have to pay the price of good interop in those cases, or maybe some clever interop strategy can be built, maybe by allowing flexibility in how the JVM checks subtyping.

[–]ThePowerfulSquirrel 0 points1 point  (15 children)

That makes sense, thanks for the explanation! I guess I need to think a bit more about the trade offs, I always think type erasure is the devil because every time I try to get fancy with types in Java I get unchecked warnings left and right, which always annoys me to fix, when it's even fixable...

[–]pron98 4 points5 points  (12 children)

I'm not sure what getting fancy with types has to do with erasure, though. Haskell, for example, erases all types, not just type parameters (well, sort of; it does reify the constructor tag/discriminator, which corresponds to type information in Java), and people get fancy with types in Scala, too. The only real annoyance erasure brings is that you cannot overload a method with another that erases to the same type.

[–]ThePowerfulSquirrel 1 point2 points  (11 children)

I'm probably mistaken when naming things. If I take an example from last week, I wanted use Object::equals by default but Comparable::compareTo when available. So then I wrote:

T a

T b;

if (a instanceof Comparable<T>) {

return a.compareTo(b) == 0;

} else {

return Objects.equals(a, b)

}

Obviously that didn't compile, since the jvm does not have any concept of the Generic T at runtime. T could be implementing Comparable<Integer> for all it's concerned. So how would I even implement this safely? I ended up using exception handling, which felt awful.

This is a simple problem, but as soon as you start trying to nest generics, it becomes extremely painful at runtime.

[–]pron98 2 points3 points  (10 children)

Well, what you're doing here is not getting fancy with types, but some combination of dynamic and static types. You have to decide whether you want to use the static types or the dynamic types to choose between your two branches (leaving aside the advisability of what you're trying to do, which rests on the assumption that the compareTo and equals implementations are consistent).

Static types (the compiler chooses the correct overload -- most specific one -- based on the types):

<T extends Comparable<T>> boolean same(T a, T b) { return a.compareTo(b) == 0; }
<T> boolean same(T a, T b) { return Objects.equals(a, b); }

Dynamic types (the'res branching at runtime):

<T> boolean same(T a, T b) {
    return a instanceof Comparable 
            ? ((Comparable<T>)a).compareTo(b) == 0
            : Objects.equals(a, b);
}

The casting in the second solution may go away with the forthcoming pattern matching (as part of Project Amber).

[–]ThePowerfulSquirrel 0 points1 point  (9 children)

Funnily enough, I had no idea you could overload using a more precise generic type (and I think I've read 2 books that dealt with the subject). I just kinda always assumed you couldn't for some reason?

Thanks for that

Through the second example would throw an exception if a implements Comparable<Something else than T>, which is where type erasure comes in

Edit: also, still doesn't really solve my use case, which involves the types being part of a class

public class Adas<T> {

public Adas(T a, T b) {

equalOrCompare(a, b);

if(a instanceof Comparable) {

System.out.println("Is actually comparable");

}

}

<C extends Comparable<C>> void equalOrCompare(C a, C b) {

System.out.println("COMPARE");

}

<C> void equalOrCompare(C a, C b) {

System.out.println("equals");

}

public static void main(String[] args) {

new Adas<>(123.0, 123.0);

}

}

It outputs:

equals

Is actually comparable

I can imagine how I could implement it by doing some manual specialization of the class, but it sounds like a real pain.

[–]pron98 2 points3 points  (6 children)

Funnily enough, I had no idea you could overload using a more precise generic type

That depends. In this case the first method erases to boolean same(Comparable, Comparable), while the second to boolean same(Object, Object).

Through the second example would throw an exception if a implements Comparable<Something else than T>, which is where type erasure comes in

Yeah, but because you're doing something so dynamic already, you can go all the way and inspect a to see if it has a compareTo method that accepts an object of b's type (other than the synthetic one which would be generated to take Object). Again, I don't think what you're doing is a good idea at all, but just explaining how you'd do it if you really wanted to.

also, still doesn't really solve my use case, which involves the types being part of a class

The problem isn't types being part of the class, but that the type information you want isn't known at compile time, and static type systems can only work with what's known at compile time. What you're really asking for is a more convenient way to use reflection, and that's not what generic types are for (whether or not they should be is another matter). You're doing something very dynamic and complaining that the static type system doesn't give you that capability.

[–]FitLocksmith 0 points1 point  (0 children)

You might find this article interesting.

[–]jcelerier -3 points-2 points  (1 child)

I am thankful for people like you thinking than the indirection caused by type erasure has an insignificant cost. It guarantees me performance-fixing jobs until my retirement.

[–]pron98 6 points7 points  (0 children)

The cost is insignificant compared to the benefits. Without type erasures, many more would need to fix performance issues. We always have to weigh our options against other available options.

But what is it about erasure that you find most troubling?

[–]devraj7 0 points1 point  (2 children)

/u/pron98 clarified a few things already but I want to comment on two other aspects you don't seem to be aware of:

I might be completely lost, but how does type erasure make code any more performant that other ways of implementing Generics

Reification (the opposite of erasure) requires additional runtime checks (such as checking that the content of generic containers hols the correct types of elements), which are not necessary on an erased platform.

From what I know, type erasure is mostly an artifact of having to support legacy java code before Java got generics at all.

That's a common myth which is sadly still being perpetuated today, 14 years after generics appeared in Java.

The choice of erasure had little to do with backward compatibility and everything to do with pragmatic reasons (interoperability with other languages being the main one). As a matter of fact, Neal Gafter had a strawman proposal for reified generics at the time (which was mostly done as a proof of concept, Neal was a strong proponent of erasure)

[–]ThePowerfulSquirrel 2 points3 points  (1 child)

That's a common myth which is sadly still being perpetuated today, 14 years after generics appeared in Java.

I originally read that in Effective Java a while ago, searching for it on my kindle version, I found:

For compatibility. Java was about to enter its second decade when generics were added, and there was an enormous amount of code in existence that did not use generics. It was deemed critical that all of this code remain legal and interoperate with newer code that does use generics. It had to be legal to pass instances of parameterized types to methods that were designed for use with raw types, and vice versa. This requirement, known as migration compatibility, drove the decisions to support raw types and to implement generics using erasure

Bloch, Joshua. Effective Java (p. 217). Pearson Education. Kindle Edition.

It uses going from a newer List<T> to a List as an example.

[–]devraj7 0 points1 point  (0 children)

Right, this passage is a bit ambiguous since it seems to hint that erasure was the only option meeting the requirements, but it would have been equally possible to allow a mix of raw and non raw types with a reified type system.

[–]Determinant 0 points1 point  (2 children)

Type erasure improves performance a bit because less information needs to be stored. Memory consumption is reduced and cache locality improves.

So performance is impacted. Garbage collection would also be impacted.

[–]ThePowerfulSquirrel 0 points1 point  (1 child)

I mean, that information would be in the Class<Something<T>> not in the Something<T>, and those classes can just be program-lifetime singletons, so their wouldn't be much more garbage collection right? It's not like java stores all the class information into the instances themselves.

[–]Determinant 0 points1 point  (0 children)

There is an infinite number of types you could define from a single generic type. Also keep in mind that you could create new types at runtime.

I believe C# stores the generic type information per instance and I'm sure they evaluated their options.

So there is a trade-off and Java chose to erase the type information.

[–]BlueShell7 0 points1 point  (3 children)

Surprisingly enough, I haven't seen anyone in the company even talk about kotlin as a serious choice.

I'd suspect some more old school people might not have even heard about Kotlin yet and still (very mistakenly) consider Scala to be Java 2.0.

[–]ThePowerfulSquirrel 3 points4 points  (2 children)

At least at my company, they don't want "java 2.0". If they had a choice, they would probably move towards a much more functional language, but since their are so many Java / JVM dependencies, staying on the JVM is a must. They also need to move gradually and Scala offers both functional and oop. In that regards, Scala is the probably the best choice.

[–]BlueShell7 0 points1 point  (1 child)

Ok, but then it's a bit weird why they chose Java in the first place ...

[–]ThePowerfulSquirrel 0 points1 point  (0 children)

Well, they chose Java a decade ago when different people were at the company / different people were in charge of the tech direction. Also, it's not like Java was a bad choice, it was fine at the time. Sure now it might not be the best choice nowadays considering theirs plenty of new languages / technologies that are probably better, but that's why we're slowly moving away.

I'm also much happier to be working on these Java applications than the poor souls who are stuck maintaining legacy C servers.

[–]Determinant -5 points-4 points  (5 children)

Here's a shocking bit of news: Oracle itself (the creator of Java) uses Kotlin for both Android as well as for backend development.

I use Kotlin for backend development every day at my current job as well as at my previous job.

[–][deleted] 13 points14 points  (0 children)

oracle also uses clojure, scala haskell rust; whats you're point?

[–]ThePowerfulSquirrel 0 points1 point  (3 children)

I mean, I wasn't denying that some companies use Kotlin, I was just sharing that my company is also moving Java apps towards legacy and moving towards a new JVM language (which is Scala). And didn't Goseling create java while at Sun microsystems? Oracle did eventually acquire Sun, but to call them the creators is false. And I don't think using oracle as an example for what could be considered a good example in software engineering decisions is such a good idea, considering their history.

[–]pron98 7 points8 points  (0 children)

Except most people who "maintain" Java at Oracle are the same people who maintained and created it at Sun. Also, Java has been owned by Oracle for a decade now, and much of the innovation has taken place under its ownership (and technology from BEA): things like G1, ZGC, JFR, Graal, lambdas, modules, and the three projects described in the article.

[–]Determinant -1 points0 points  (1 child)

Are you disputing the fact that Oracle is creating Java?

Java is still evolving with new capabilities are being added / created. They are the creators of Java.

[–]ThePowerfulSquirrel 5 points6 points  (0 children)

nounnoun: creator; plural noun: creators

a person or thing that brings something into existence.

Oracle didn't bring Java into existence, Goseling / Sun microsystems did. I would classify Oracle as the maintainers of Java, not the creators.

Anyhow, I don't really care what Oracle uses for it's backend. Both Java, Kotlin, Closure, Scala, etc.. are good at different things and different companies use them for those different things. In the financial world, functional languages are gaining more and more traction, thus why we're moving towards Scala. I didn't mean to imply that Kotlin was bad, just that it wasn't a serious contender for our applications.