Hacker News new | past | comments | ask | show | jobs | submit login

One problem with Java is, for the time being, the lack of proper value types.

Had a language like Eiffel, Oberon dialects or Modula-3 taken its role in the industry, I bet we wouldn't be having this constant discussions of GC vs manual, in terms of performance.

Another issue, is that many developers even in languages with GC and value types, tend to be "new() happy" sometimes leading to designs that are very hard to refactor when the the need arises, given the differences in the type system between reference and value types.

Eiffel is probably the only one I can remember, where the difference is a simple attribute.




Does Haskell also qualify?

https://downloads.haskell.org/~ghc/latest/docs/html/users_gu...

Haskell doesn't quite have a distinction between reference and value types, but there are boxed and unboxed values, which I think fits the bill for the purpose of this discussion.


> Haskell doesn't quite have a distinction between reference and value types, [...]

Perhaps difference between normal values, and values wrapped in eg IORef counts?


To a Haskeller, you could explain Java-style reference types by saying “it's an artificial limitation whereby every value is behind an IORef, and the value itself isn't a first-class entity”. But Haskell itself doesn't have this artificial limitation, and every type is a value type.


In Haskell, since it is a pure language, every type is a value type in the sense that you can pass around values without worrying that callees might overwrite your data.

However, these types (excepting unboxed types) can also be considered reference types since values are technically passed by reference. Boxed values are stored in thunks, and only accessed through these (at least conceptually). Thunks in turn have GC overhead.

Boxed values have even associated internal state, but there is not much control over it: the state of their evaluation.

So another viewpoint is that boxed Haskell types are just like Java reference types, but from the outside they seem immutable.


> However, these types (excepting unboxed types) can also be considered reference types since that is how values are passed.

This is an implementation detail, and abstractions shall never be conflated with their possible implementations. What matters to the user of the abstraction is that you're passing around (a computation[0] that evaluates to) the value itself, not some mutable entity whose current state is the value.

> So another viewpoint is that boxed Haskell types are just like Java reference types, but they are immutable.

This is wrong. In Java, all objects have a distinct identity, so, for instance, `new Complex(2,3) == new Complex(2,3)` evaluates to `false`, even if both objects represent the complex number `2+3i`.

[0] Since Haskell is non-strict.


This is an implementation detail, and abstractions shall never be conflated with their possible implementations

We're talking about performance here, right? So unfortunately this statement is totally untrue in that context.

Java world wants value types largely to avoid pointer chasing that hurts cpu cache effectiveness. Haskell suffers the same issue, then. You can make what are effectively value types in Java as long as you don't use == to compare but rather .equals, make all the fields final, override toString/equals/hashcode etc, and more sensible JVM targeting languages of course convert == into calls to .equals by default.


> We're talking about performance here, right? So unfortunately this statement is totally untrue in that context.

You don't need implementation details to discuss performance. Abstractions can come with a cost model. (C++'s standard library concepts are a great example of this.) And Haskell's cost model says that values can be passed around in O(1) time.

> Java world wants value types largely to avoid pointer chasing that hurts cpu cache effectiveness. Haskell suffers the same issue, then.

That's not the whole story. When the physical identity of an object no longer matters (note that immutability alone isn't enough), the language implementation can, without programmer intervention:

(0) Merge several small objects into a single large object. (Laziness kind of gets in the way, though. ML fares better in this regard.)

(1) Use hash consing or the flyweight pattern more aggressively.

> You can make what are effectively value types in Java as long as you don't use == to compare but rather .equals, make all the fields final, override toString/equals/hashcode etc, and more sensible JVM targeting languages of course convert == into calls to .equals by default.

But the JVM itself is completely unaware that you intend to use a class as a value type, and thus can't automatically apply optimizations that are no-brainers in runtime systems for languages where all types are value types.

Also, `.equals()`'s type is broken: it allows you to compare any pair of `Object`s, but it should only allow you to compare objects of the same class.


I know what optimisations identity-less aggregates allow. Objects can be passed around in O(1) time in any language that uses references. It's not the issue. Indeed passing value types is O(n) in the size of the value type, so that's a loss. The goal is to avoid data dependent loads (i.e. pointer dereferences) by flattening/inlining data structures.

As you point out, Haskell could in theory merge things together and lay out memory more effectively, but in theory so could Java with e.g. interprocedural escape analysis. But in practice there's a limit to what automatic compiler optimisations can do - learning this the hard way is a big part of the history of functional languages (like, how many functional language runtimes automatically parallelise apps with real speedups?). Java sometimes does convert object allocations into value types via the scalarisation optimisations, but not always, hence the desire to add it to the language.


Passing around copies is O(n) in the size of the copy. This has nothing to do with call-by-value, and all with the retardedness of having to defensively clone objects to cope with arbitrary mutation.


> And Haskell's cost model says that values can be passed around in O(1) time.

I am not sure of that. The compiler is free to inline expressions, or otherwise evaluate them multiple times.


What you say is not incorrect. But you totally miss the context of the discussion.

And you switch sides just to disagree with your last quote.


Yeah as I said it's a viewpoint. The viewpoint I meant is the technical (and performance-centric) one, not the mathematical one.


> The viewpoint I meant is the technical

My objection was also a technical one: Haskell doesn't give you access to the physical identity of any runtime object that isn't a reference cell (`IORef`, `STRef`, `MVar`, `TVar`, etc.).

> (and performance-centric) one

The ability to pass around arbitrarily complicated values in O(1) time is an intrinsic part of Haskell's cost model. This is the most natural thing in the world, unless you've lived all your life subordinating values to object identities, in which case, yes, non-destructively transferring a value from one object identity to another (aka “deep cloning”) might be an arbitrarily expensive operation.


One thing I like about Go is its strong Oberon heritage, picking up where those languages left.


That is what attracted me initially to it, but then I got disappointed with the overall direction the language design was going.

I am more of a Swift/Rust guy than Go, in terms of features.

Even Oberon eventually evolved into Active Oberon and Component Pascal variants, both more feature rich than Go.

To be honest, Niklaus Wirth's latest design, Oberon-07 is even more minimalist than Oberon itself.

EDIT: Typo


>then I got disappointed with the overall direction the language design was going.

Can you elaborate? Thanks.


For me the fact that Go is a descent of Oberon-2 and Limbo is quite interesting, but there are several features that a modern language should have that aren't present in Go and never will be.

Hence why I rather see the appeal of Go as a way to attract developers that would otherwise use C, to make use of a more safer programming language.

As many turn to C, just because they don't know other AOT compiled languages well, not because they really need any C special feature.

Regardless of the discussion regarding if it is a systems programming language or not, I think it can be, given its lineage. It only needs someone to get the bootstraped version (1.6) write a bare metal runtime and then it would be proven. Maybe a nice idea for someone looking for a PhD thesis in OS area.

Me, I would rather make use of a .NET, JVM or ML influenced language as those have type systems more of my liking.


Considering that Wirth's latest Oberup update took features away, I'm not sure whether he and Pike et al. would really agree about that... But yeah, it's an interesting language in that family, although I'd still much rather have a modern Modula-3 system...


As I only have used Modula-2 and Go, what are the features of Modula-3 missing in Go?


Quite a few:

- Enumerated types

- Enumerations as array indexes

- Generics

- Classic OO with inheritance

- Untraced references in unsafe packages

- Unsafe packages for low level systems programming

- Exceptions

- Reference parameters

- Bit packing

- Since Modula-3 was a system programming language for SPIN OS, the runtime library was richer, including GUI components

There a few other features.


Man I loved the Eiffel book. When I think about the difference between invariants and pre/post-condition contracts, vs the "bean" anti-pattern that industry went with, I want to vomit.


Could you elaborate on what about those three languages? Are you talking about copying GCs vs mark and sweep? Thanks.


Java is one of the few languages that only has reference types.

All the languages that I referenced have value types in the same lineage as Algol derived languages.

This means that you can make use of the stack, global statics, structs of arrays, arrays of structs and so on.

The GC only comes into play when you make use of the heap, of course. Also the GC has a more rich API, given that besides Eiffel, the other languages are system programming languages. So you can let the GC know that certain areas aren't to be monitored or released right away (manual style).

So given that you have all the memory allocation techniques at your disposal the stress on the GC isn't as big as in Java's case.

But sadly none of those earned the hearts of the industry.

The closest you have to them are D (which deserves a better GC implementation) and the improvements coming to .NET Native via the Midori project.


But surely you could implement "global statics, structs of arrays, arrays of structs" in Java(classes with public members) as well even if it lacks value types(aside from primitives that is) no?


Yes, but you won't gain much given the prevalence of references.

You will need to decompose the classes across multiple arrays, thus leading to very hard to maintain code.


Thanks for the explanation. This makes me want to read up on Algol and Eiffel. Do you have any recommendations? It's funny I like Algol has finds it's way into a lot of discussions lately.


>Java is one of the few languages that only has reference types.

Uhm, aren't those "primitives"? Or did you mean user-definable value-types?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: