Hi –
I’ve run into a case where I get this somewhat unhelpful type error message when using closure syntax for implementing a SAM which uses path-dependent types:
error: type mismatch;
found : Demo.Res{type VirtType = arg.VirtType}
required: Demo.Res{type VirtType = arg.VirtType}
(arg: Arg) => foo(arg)
^
This is with Scala version 2.13.6.
There’s no problem when I subclass the SAM explicitly, as demonstrated by the full example below.
Is there any particular reason why I shouldn’t expect it to work, and/or are there workaround or tweaks which might help me?
object Demo {
// Defining some types
trait Arg {type VirtType}
trait Res {type VirtType}
trait Fun {
def apply(arg: Arg): Res {type VirtType = arg.VirtType}
}
// Using a closure - this fails to typecheck:
def blah1(): Fun = {
(arg: Arg) => foo(arg)
}
// Making an explicit subclass – this works:
def blah2(): Fun = {
new Fun {
override def apply(arg: Arg): Res {type VirtType = arg.VirtType} = foo(arg)
}
}
def foo(arg: Arg): Res {type VirtType = arg.VirtType} = ???
}
Back to Scala 2, I tried removing the return type annotation in the longhand anonymous class form, and got the same error as for the shorthand SAM form.
So I would surmise this is not a SAM versus anonymous class issue, rather the need for an explicit type redefinition on the override in Scala 2. Perhaps the compiler thinks that the arg in the abstract trait declaration is different from the arg in the concrete override, and thus concludes that the path dependent types aren’t the same?
Personally I wouldn’t bother with virtual types in this particular situation, and just work with a generic type parameter for your VirtType.
Good to know that Scala 3 is sharper with this, thanks.
That sounds like a testable hypothesis – and indeed, if I change the closure to (argarg: Arg) => foo(argarg)
then the error message sound less nonsensical:
Now, I only made Fun a trait because I had trouble defining it as a type alias.
I may have found out how to do that though:
object Demo {
// Defining some types
trait Arg {type VirtType}
trait Res {type VirtType}
type Fun = (T => Res {type VirtType = T#VirtType}) forSome {type T <: Arg}
// Using a closure - and this works:
def blah(): Fun = {
(argarg: Arg) => foo(argarg)
}
def foo(arg: Arg): Res {type VirtType = arg.VirtType} = ???
}
I think this gives me the same type guarantees as with the path-dependent type.
(In the actual application of this, VirtType is a phantom type used to track resources – “these offsets point into that byte array”, like.)
UPDATE: Yeah no, this is useless because the quantification is completely wrong. “You have a function which has some argument type which you don’t know but are ensured exists” is just not helpful at all… (T => R forSome {type T})
Scala 2 ticket with “conveniently pastable” example:
Scala 3 says
20 | val probe: Nothing = (arg: Arg) => foo(arg)
| ^^^^^^^^^^^^^^^^^^^^^^
| Found: (arg: Demo.Arg) => Demo.Res{type VirtType = arg.VirtType²}
| Required: Nothing
|
| where: VirtType is a type in trait Res
| VirtType² is a type in trait Arg
which is what is intended, but Scala 2
found : Demo.Arg => Demo.Res{type VirtType = arg.VirtType} forSome { val arg: Demo.Arg }
which is not intended.
For the anonymous subclass with inferred result type, Scala 3 infers correctly
final class $anon() extends Object(), Demo.Fun {
override def apply(arg: Demo.Arg):
Demo.Res{type VirtType = arg.VirtType} = Demo.foo(arg)
}
To muddy the waters somewhat, I tried the example quoted by @som-snytt using an anonymous class in Scala 2.13, using SAM, anonymous with inferred result type and anonymous with explicit type: Scastie - An interactive playground for Scala..
Perhaps there are two related bugs here? Or am I just mistaken?
EDIT:given the age of the bug report in question and the fact that it is fixed in Scala 3, this is probably just of academic interest now. Carry on coding…