You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 27, 2025. It is now read-only.
But this offers no type safety because the aliased type is transparent
typeMiles=DoubleclassBooster():defadvanceRocket(rocket: Rocket, distanceToAdvance: Miles):Rocket= {
// Kilometres and Miles are transparent. They are both Double so this bug is allowed
rocket.advance(distanceToAdvance)
}
case class wrappers
Simplest approach for type-safety would be to create distinct new types
So now the compiler helps us to prevent the bug...
classBooster():defadvanceRocket(rocket: Rocket, distanceToAdvance: Miles):Rocket= {
// Kilometres and Miles are different types. So compiler prevents this bug
rocket.advance(distanceToAdvance)
|^^^^^^^^^^^^^^^^^|Found: (distanceToAdvance : Miles)
|Required:Kilometres
}
...but at the cost of runtime overhead (allocating wrapper Kilometres and Miles objects)
Value-class wrappers
Extending the wrappers with 'AnyVal' promises to eliminate overhead
caseclassMiles(value: Double) extendsAnyValclassBooster():defadvanceRocket(rocket: Rocket, distanceToAdvance: Miles):Rocket= {
// Kilometres and Miles are different types. So compiler prevents this bug
rocket.advance(distanceToAdvance)
|^^^^^^^^^^^^^^^^^|Found: (distanceToAdvance : Miles)
|Required:Kilometres
}
...and in theory we eliminate the runtime overhead of allocating wrapper objects
Value-class wrappers limitations - I
BUT! Allocations happen in many cases (e.g. parametric polymorphism)
Limitation especially significant for numeric computing
"...There has been concern for numerical computing. We think future SIP(s), using work from SIP-15, can provide more benefit to numerical computing users..."
Must be members of classes, traits, or objects, or defined at the top-level. They cannot be defined in local blocks.
But this by itself is not useful
Outside of the scope of object Scala3OpaqueTypeAliasesDefinitions we only know the type names but we cannot do anything
At a minimum we need to provide a way to 'introduce' values of our opaque type and a public API for accepting and handling values of opaque type
So in practice opaque type aliases would have a companion object
Opaque Type Aliases - II
So we have opaque types and extension methods that define public API
objectScala3OpaqueTypeAliasesDefinitions:opaquetypeKilometres=DoubleobjectKilometres:defapply(d: Double):Kilometres= d
opaquetypeMiles=DoubleobjectMiles:defapply(d: Double):Miles= d
extension (a: Kilometres)
@scala.annotation.targetName("plusKm")
def+ (b: Kilometres):Kilometres= a + b
deftoMiles:Miles= a /1.6extension (a: Miles)
@scala.annotation.targetName("plusMiles")
def+ (b: Miles):Miles= a + b
deftoKm:Kilometres= a *1.6
Opaque Type Aliases - III
Outside of the scope where the opaque type alias is defined the knowledge of underlying representation is hidden
So revisiting our Rocket and Booster example, we get type-safety...
importScala3OpaqueTypeAliasesDefinitions._classRocket(distanceTravelled: Kilometres):defadvance(distanceToAdvance: Kilometres):Rocket=newRocket(
distanceTravelled + distanceToAdvance
)
classBooster():defadvanceRocket(rocket: Rocket, distanceToAdvance: Miles):Rocket= {
// Kilometres and Miles are different types. So compiler prevents this bug
rocket.advance(distanceToAdvance)
-- [E007] TypeMismatchError:-------------------------------------------------11| rocket.advance(distanceToAdvance)
|^^^^^^^^^^^^^^^^^|Found: (distanceToAdvance : Scala3OpaqueTypeAliasesDefinitions.Miles)
|Required:Scala3OpaqueTypeAliasesDefinitions.Kilometres|| longer explanation available when compiling with`-explain`
}
Opaque Type Aliases - IV
...but without allocation cost, even in context of parametric polymorphism
importScala3OpaqueTypeAliasesDefinitions.*classRocket(distanceTravelled: Kilometres):defadvance(distanceToAdvance: Kilometres):Rocket=newRocket(
distanceTravelled + distanceToAdvance
)
typeConversion[A] =A=>KilometresclassBooster():defadvanceRocket[A:Conversion](rocket: Rocket, distanceToAdvance: A):Rocket= {
valdistanceInKm= summon[Conversion[A]](distanceToAdvance)
rocket.advance(distanceInKm)
}
valrocket1=newRocket(Kilometres(0))
valrocket2=newRocket(Kilometres(0))
valbooster=newBooster()
givenConversion[Kilometres] = identity
givenConversion[Miles] = _.toKm
booster.advanceRocket(rocket1, Kilometres(100)) // No allocation of Kilometres object
booster.advanceRocket(rocket2, Miles(200)) // No allocation of Miles object
Opaque Type Aliases - V
...and no allocation costs when assigning to arrays
importScala3OpaqueTypeAliasesDefinitions.*valdistances:Array[Kilometres] =Array(Kilometres(10)) // No allocation of Kilometres object
The wrapper type only exists at compile-time
At runtime the opaque type is erased to its runtime representation
NOTE: This means type tests (e.g. when type casing in a pattern match -- case myType: MyType) are done on the underlying representation, not the opaque type alias
So beware opaque type alias pattern matching!!
Opaque Type Aliases - VI
Buggy version with pattern matching though...
importScala3OpaqueTypeAliasesDefinitions.*classRocket(distanceTravelled: Kilometres):defadvance(distanceToAdvance: Kilometres):Rocket=newRocket(
distanceTravelled + distanceToAdvance
)
typeDistance=Kilometres|MilesclassBooster():// THIS GIVES A WARNING. THE 'Kilometres' CASE IS UNREACHABLE due to erasure.// SO WE HAVE A BUG. Any 'Kilometres' passed to this method will be multiplied by 1.6defadvanceRocket(rocket: Rocket, distanceToAdvance: Distance):Rocket=valdistanceInKm= distanceToAdvance match {
casemiles: Miles=> miles.toKm
casekm: Kilometres=> km
[warn] -- [E030] MatchcaseUnreachableWarning: dottyslidescodesnippets/src/main/scala/org/lunatech/dotty/opaquetypes/Units.scala:16:13
[warn] 16|casekm: Kilometres=> km
[warn] |^^^^^^^^^^^^^^
[warn] |Unreachablecase
[warn] one warning found
}
rocket.advance(distanceInKm)
valrocket1=newRocket(Kilometres(0))
valrocket2=newRocket(Kilometres(0))
valbooster=newBooster()
booster.advanceRocket(rocket1, Kilometres(100)) // BUG! Will actually advance by 160km
booster.advanceRocket(rocket2, Miles(200))
EXERCISE
Using Opaque Type Aliases
In this exercise, we will take a deeper look at Opaque type aliases
Make sure you're positioned at exercise "exploring opaque type aliases"
Follow the exercise instructions provided in the README.md file in the code folder
EXERCISE
OPTIONAL EXERCISE
Using Opaque Type Aliases
In this exercise, we will explore the mechanism for creating Opaque Type aliases
Make sure you're positioned at exercise "optional opaque type aliases"
Follow the exercise instructions provided in the README.md file in the code folder