Consider this example match type from Jamie Thompson:
type Last[T] = T match
case x *: EmptyTuple.type => x case _ *: xs => Last[xs]
case _ *: xs => Last[xs]
We expect
summon[Boolean =:= Last[Int *: String *: Boolean *: EmptyTuple.type]]
to return a witness since Boolean is the last type in the tuple type, and it does:
scala> summon[Boolean =:= Last[Int *: String *: Boolean *: EmptyTuple.type]]
val res32: Boolean =:= Boolean = generalized constraint
I would similarly expect
summon[Nothing =:= Last[Nothing *: EmptyTuple.type]]
to return a witness. There are several reasons to expect this besides the code of the match type. It could be my expectations are off, though. If there is something about the type system preventing this equality from being proved, we’d expect the message to be Cannot prove that Nothing =:= Last[Nothing *: EmptyTuple.type]. (Edit: I misspoke in my original post and wasn’t able to edit it till a moderator posted it, so I am going to edit a few spots to fix errors and clarify).
We do get the “cannot prove”, which I find puzzling. Additionally, the reason we are given has to do with Nothing being uninhabited preventing a reduction:
scala> summon[Nothing =:= Last[Nothing *: EmptyTuple.type]]
-- [E172] Type Error: ----------------------------------------------------------
1 |summon[Nothing =:= Last[Nothing *: EmptyTuple.type]]
| ^
| Cannot prove that Nothing =:= Last[Nothing *: EmptyTuple.type].
|
| Note: a match type could not be fully reduced:
|
| trying to reduce Last[Nothing *: EmptyTuple.type]
| failed since selector Nothing *: EmptyTuple.type
| is uninhabited (there are no values of that type).
1 error found
You also see unexpected behavior this way:
type N = Last[Nothing *: EmptyTuple.type]
summon[Nothing =:= N]
which results in
scala> summon[N =:= Nothing]
-- [E172] Type Error: ----------------------------------------------------------
1 |summon[Nothing =:= N]
| ^
| Cannot prove that Nothing =:= N.
1 error found
Referential transparency leads me to think that, at the very least, the behavior should be the same in both cases regardless of what that behavior turns out to be.
Lest one believe the complexity of the example is to blame, the following code results in the same error:
type NameOf[X] = X match
case Int => "Int"
case String => "String"
case Nothing => "Nothing"
case _ => "unknown"
summon[NameOf[Nothing] =:= "Nothing"]
while the corresponding summons for Int and String work as expected.
I don’t know enough about the scala internals to guess at the source of this error message, but taken at face value the message suggests match types only work as one might naively expect when the types involved are inhabited. I find this counterintuitive and difficult to explain. Is there a rationale for this behavior that I’m missing? Thanks!
Incidentally I ran the above with
#> scala-cli repl
Welcome to Scala 3.8.3 (17, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.