Pre-SIP: Unboxed wrapper types

Extension methods strike me as completely distinct from value classes / opaque types. The point of extension methods is they are available on every instance of the original class. While value class / opaque class methods are only available on the specified instances and the underlying class’ methods are only available indirectly. I don’t quite follow why people would be using value classes for extension methods.

Until now, all discussion about inline classes suggests they’re just a different syntax for opaque types.

Not sure what you answer to — but people are using value classes + implicit conversions for extension methods available on all instances of the wrapped type, just to avoid instance creation.

The other use case is when people define an inline class/opaque type for type safety, and then want to add extension methods only on the wrapped instances (see the Logarithm example in SIP-35) — comparisons of different syntaxes proposals should also consider this use case.

It’s a tricky question: opaque types or inline classes?

Let’s try to argue from first principles

We are after an abstraction A which is different from its underlying type U, but which guarantees to never box, and instead always is represented as U at runtime.

Current value classes cannot fit the bill - they box. We could tweak value classes so that they don’t box in the single parameter case (that’s what I called inline classes). But that begs the question what to do once we can support multiple parameters. To support multiple parameter value classes, we really only need a runtime that supports returning multi-word results from a function - the rest can be done by the compiler. So let’s assume we have

inline class Complex(re: Double, im: Double)

A reasonable strategy to represent Complex variables is by two fields for their real and imaginary parts. So

val x: Complex

would translate to

val x_re: Double; val y_im: Double

But every first-order type in Scala is a subtype of Any, so there must be a way to convert such Complex aggregates to Any. In other words, Complex must box in some situations, just like today’s value classes do (we should be a able to do a better job with avoiding boxing for array elements, though, but that is a separate story).

In summary, there are really two reasons why a type must be boxed: to maintain class info and the vtable (that’s why single-parameter value classes box) and to obtain a uniform representation for Any and unspecialized generics (that’s why Int and Complex box).

If multi-parameter inline classes box it would be a hack to make a special exception for the single-parameter case, with special restrictions (i.e. no override, no pattern matching) in order to guarantee no boxing.

So that means: In the balance, value classes or inline classes are not a suitable abstraction for what we are after. This leaves just opaque types and SIP 35.

What about value classes then? SIP 35 + extensions (Dotty PR #4114) cover most of the use cases for single-parameter value classes. On the other hand, it would be awkward to deprecate / remove value classes now, only to re-introduce them in new form once multiple parameter value classes are supported.

So, I would propose to accept SIP 35 and keep value classes for the time being. Once multiple parameter value classes are supported we can switch to the more convenient inline class syntax. That would also give us room for some tweaks if the (as yet unknown) multi-parameter value class model requires them.

There is overlap between value classes and opaque types, but I don’t see a good way to avoid it.

8 Likes

Thanks for the summary. That seems very reasonable, and that solution lets room to accomodate to what really lands in Valhalla.

Will multiple parameter value classes be used to support struct?

1 Like

Will multiple parameter value classes be used to support struct ?

I don’t think there’s a way to know this yet. But it would be good to work out the connections.