The Singleton "kind" is not an upper bound
See original GitHub issueA few of us discussed this, but it seems we lack an issue for it. Quoting from #4942:
If A <: Singleton and B <: Singleton, it follows that A | B <: Singleton, but for instance 1 | 2 is not actually a singleton type.
In fact, already in Scala 2 we can exploit this and write the questionable
def f[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): Bound = if (cond) a else b
def g(cond: Boolean): Singleton = f[Singleton, 1, 2](cond, 1, 2)
In Dotty we also get the clearly broken:
def f[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): (A | B) & Bound = if (cond) a else b
def g(cond: Boolean): Singleton = f[Singleton, 1, 2](cond, 1, 2)
// def h(cond: Boolean): (1|2) & Singleton = f[Singleton, 1, 2](cond, 1, 2) // doesn't compile
type || [A, B] = A | B
type Boo = 0 || 1
// def h1(cond: Boolean): (1||2) & Singleton = f[Singleton, 1, 2](cond, 1, 2) // still fails because 1 | 2 is collapsed eagerly
def f2[Bound, A <: Bound, B <: Bound](cond: Boolean, a: A, b: B): (A || B) & Bound = if (cond) a else b
def h2(cond: Boolean): (1||2) & Singleton = f2[Singleton, 1, 2](cond, 1, 2)
def h2(cond: Boolean): Int(1) Any Int(2) & Singleton
Only h
fails, and just because we forbid 1|2
syntactically for now (#1551), so h2
works.
Since Singleton
is special in core typing rules, @odersky suggested this endangers even soundness. Ad-hoc restrictions (say, forbidding Singleton
as a type argument) seems a losing whack-a-mole game.
Discussing this with @odersky today, we figured that Singleton
behaves like a kind that can’t be expressed via upper bounds, so we should consider something like an annotation or a modifier (tho modifiers on type variables don’t exist yet).
Migration isn’t easy, but one can easily support simple use cases of form [X <: Singleton]
or even [X <: Foo with Singleton]
. According to @nicolasstucki, even type SingAlias = Singleton; def foo[X <: Foo with SingAlias]
doesn’t currently work.
Paging @milessabin and @soronpo, as this affects SIP-23.
Issue Analytics
- State:
- Created 5 years ago
- Comments:23 (18 by maintainers)
Top GitHub Comments
Here is how a singleton type bound could work:
The
Singleton
type is parameterized and contravariant:Users write bounds as
T <: Singleton[T]
, which could also be syntax-sugared with an annotation:The type of every value
v
extendsSingleton[v.type]
. Non-singleton types naturally could extendSingleton[Nothing]
, which would be like a top type, but it’s not really useful as we already haveAny
. In any case we do havev.type | w.type <: Singleton[v.type] | Singleton[w.type] =:= Singleton[v.type & w.type] <: Singleton[Nothing]
.In the example above, you can’t instantiate
C
with a non-singleton type, because that would mean having to pass ana
argument typedNothing
or some other impossible stuff (likev.type & w.type
).The compiler could then provide useful reasoning on such types, such as
List[Int] & Singleton[x.type] <: x.type
. This would actually be useful to reason modularly about subtyping knowledge involving singleton types, which is currently very hard – sometimes we want to refine a singleton type with a type that is more precise than its widened form.Why can’t it be done with a special typeclass?