Take a look at this:
/** takes a Spellbook and returns a Spellbook guaranteeing
* that all spells have been loaded from the database. */
def checkIfSpellsLoaded[S <: Spellbook](spellbook :S) :Option[S { type SpellsLoaded }] =
if (spellbook.spellsLoaded) Some(spellbook.asInstanceOf[S { type SpellsLoaded }])
else None
def checkIfOwnerLoaded[S <: Spellbook](spellbook :S) :Option[S { type OwnerLoaded }] =
if (spellbook.ownerLoaded) Some(spellbook.asInstanceOf[S { type OwnerLoaded }])
else None
What is that { type X } doing as part of a type parameter?? What is going on here?
In Scala class members can be def
, val
and (relevant for us) type
https://docs.scala-lang.org/tour/abstract-type-members.html
https://typelevel.org/blog/2015/07/13/type-members-parameters.html
Scala: Abstract types vs generics
How to work with abstract type members in Scala
Type members are used to create path-dependent types
What is meant by Scala's path-dependent types?
https://docs.scala-lang.org/scala3/book/types-dependent-function.html
If Spellbook
has type members SpellsLoaded
, OwnerLoaded
trait Spellbook {
type SpellsLoaded
type OwnerLoaded
def spellsLoaded: Boolean
def ownerLoaded: Boolean
}
then for S <: Spellbook
the types S
, S { type SpellsLoaded }
and S { type OwnerLoaded }
are the same
type S <: Spellbook
implicitly[(S { type SpellsLoaded }) =:= S] // compiles
implicitly[S =:= (S { type SpellsLoaded })] // compiles
implicitly[(S { type OwnerLoaded }) =:= S] // compiles
implicitly[S =:= (S { type OwnerLoaded })] // compiles
But if Spellbook
doesn't have type members SpellsLoaded
, OwnerLoaded
trait Spellbook {
// no SpellsLoaded, OwnerLoaded
def spellsLoaded: Boolean
def ownerLoaded: Boolean
}
then the refined types S { type SpellsLoaded }
and S { type OwnerLoaded }
are just subtypes of S
(having those type members)
implicitly[(S { type SpellsLoaded }) <:< S] // compiles
// implicitly[S <:< (S { type SpellsLoaded })] // doesn't compile
implicitly[(S { type OwnerLoaded }) <:< S] // compiles
// implicitly[S <:< (S { type OwnerLoaded })] // doesn't compile
and the refined types S { type SpellsLoaded = ... }
and S { type OwnerLoaded = ... }
in their turn are subtypes of the former refined types
implicitly[(S {type SpellsLoaded = String}) <:< (S {type SpellsLoaded})] // compiles
// implicitly[(S {type SpellsLoaded}) <:< (S {type SpellsLoaded = String})] // doesn't compile
implicitly[(S {type OwnerLoaded = Int}) <:< (S {type OwnerLoaded})] // compiles
// implicitly[(S {type OwnerLoaded}) <:< (S {type OwnerLoaded = Int})] // doesn't compile
S { type SpellsLoaded }
and S { type OwnerLoaded }
are shorthands for S { type SpellsLoaded >: Nothing <: Any }
and S { type OwnerLoaded >: Nothing <: Any }
while S { type SpellsLoaded = SL }
and S { type OwnerLoaded = OL }
are shorthands for S { type SpellsLoaded >: SL <: SL }
and S { type OwnerLoaded >: OL <: OL }
.
Casting .asInstanceOf[S { type SpellsLoaded }]
, .asInstanceOf[S { type OwnerLoaded }]
looks like SpellsLoaded
, OwnerLoaded
are used as phantom types
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:labelled-generic:type-tagging (5.2 Type tagging and phantom types)
So you seem to encode in types that the methods checkIfSpellsLoaded
, checkIfOwnerLoaded
were applied to S
.
See also
Confusion about type refinement syntax
What is a difference between refinement type and anonymous subclass in Scala 3?