Search code examples
scalatypechecking

Scala type check


How to make this typecheck?

trait Test[A] {
  type Output = A
  def get: Iterable[A]
  def check(a: A): Boolean
}

object A {
  def fs: List[Test[_]] = ???
  def test = for{f <- fs
    a <- f.get
    if f.check(a)} println(a)
}

It complains with

<console>:12: error: type mismatch;
 found   : a.type (with underlying type _$1)
 required: _$1
           if f.check(a)} println(a)

Attempt #1 (failed)

object A {
  def fs: List[Test[_]] = ???
  def test = for{f <- fs
    a <- f.get
    if f.check(a.asInstanceOf[f.Output])} println(a)
}

but then I have the same problem:

<console>:12: error: type mismatch;
 found   : f.Output
    (which expands to)  _$1
 required: _$1
               if f.check(a.asInstanceOf[f.Output])} println(a)

Attempt #2 (failed)

Having learned of some existential types, I am trying to replace the underscore -- at Test level, because not all A may be identical

object A {
  def fs: List[Test[A] forSome { type A }] = ???
  def test = for{f <- fs
    a <- f.get
    if f.check(a)} println(a)
}

It complains with:

<console>:12: error: type mismatch;
 found   : a.type (with underlying type A)
 required: A
           if f.check(a)} println(a)

How to get out of this type nightmare?


Solution

  • The basic issue is that the Scala compiler doesn't notice that the types hidden by the existential must be the same.

    There are two solutions:

    1. Make a single method calling both get and check:

      object A {
        private def getChecked[A](f: Test[A]) = f.get.withFilter(f.check)
      
        def fs: List[Test[_]] = ???
        def test = for { f <- fs; a <- getChecked(f) } println(a)
      
    2. Give name to the existential using a type variable pattern. Unfortunately, this doesn't seem to work inside a for, so you'll need to desugar it first:

      // equivalent to your initial code
      def test = fs.flatMap { f => f.get.withFilter { a => f.check(a) } }.
                    foreach { a => println(a) }
      
      // with a type variable
      def test = fs.flatMap { case f: Test[x] => f.get.withFilter { a => f.check(a) } }.
                    foreach { a => println(a) }