I think @jducoeur is right. I'll elaborate on why below—but the TL;DR. is "use spire and follow https://github.com/non/spire/blob/master/GUIDE.md". Below I answer this thread and explain why that seems the correct answer—ahem, modulo the fact that some requirements seem overly restrictive.
@eaplatanios clearly wants no wrapping, so
Either is not OK. I'm not sure you can satisfy all the given requirements. But what he actually wants is
Float | Double, so there might be specialized solutions for that. In particular, you want to use Spire and specialization as described on the Spire guide.
You asked about having
If you don't want to box
value, your generated bytecode will need to use two separate methods (overloaded or not):
def cast(value: Float): T
def cast(value: Double): T
You can also try to generate that via specialization:
def cast[T @specialized(Float, Double)](value: V): T
The advantage of specialization is that it produces the two overloads automatically, and then you can write callers without duplicating them. That is, you can just write
def castUser[T @specialized(Float, Double)](value: V) = ... cast(value) ...
instead of having two copies, one for each overload of
def castUser(value: Float) = ... cast(value) ...
def castUser(value: Double) = ... cast(value) ...
Beware: I'm no expert on specialization, but it can have some issues (I'm no expert on those). However, the Spire guide recommends specialization and you only care about two types, Float and Double, so you'll have fewer problems.
On the JVM, the only alternative (in principle) would be to only support a single type (say
Double), and widen
Double if needed. That avoids boxing. Something like this was the basis of http://scala-miniboxing.org/—though I suspect that avoided floating-point extension by using
Float.floatToRawIntBits and friends. However, I understand that miniboxing is not stable enough for production use.
Using type parameters and specialization avoids union types, but let me answer anyway...
That's confusing to me—especially, the name "exclusive or" is. That thing is an exclusive-or, a value can't be both
Double. And if a value is an
Int | Double, it is a special case of
Int | Float | Double. A caller that handles the latter can also handle the former—its
Float branch will not be triggered by such a value, but it can still be triggered by users producing
Float. If you know you have no such users, you shouldn't need to write
Int | Float | Double anywhere (I know I'm oversimplifying, but not really). The pattern matching will still be exhaustive—at worst, it has more cases than strictly needed.