Closures and path dependent types on parameter

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} = ???
}
1 Like

For what it’s worth, this works perfectly well in Scala 3: Scastie - An interactive playground for Scala.

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.

Or upgrade to Scala 3 if possible!

1 Like

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:

 found   : Demo.Res{type VirtType = argarg.VirtType}
 required: Demo.Res{type VirtType = arg.VirtType}

which does seem to point to some issue with connecting the argument in the override with the one in the trait.

1 Like

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})

1 Like

@Erik
(T => R) forSome { type T } is just Nothing => R because Function1[-A, +B] is contravariant w.r.t. A

1 Like

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)
          }

but Scala 2 irrespective of -Xsource:3-cross says

 found   : Demo.Res{type VirtType = arg.VirtType}
 required: Demo.Res{type VirtType = arg.VirtType} forSome { val arg: Demo.Arg }
      override def apply(arg: Arg) = foo(arg)
1 Like

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..

It works for both anonymous forms.

So that example is SAM-specific.

Going back to the original, this failed for the first two forms: Scastie - An interactive playground for Scala.

It isn’t SAM-specific.

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…

2 Likes