0

I would like to have a trait with two methods as follows:

trait Foo[A] {
  def bar1(): A
  def bar2(a: A): Unit
}

now the problem is, I don't want the A to propagate. Say:

val foos: List[Foo[???]] = ...
foos.foreach({ foo =>
  val x = foo.bar1()
  // stuff
  foo.bar2(x)
})

the type A doesn't really matter in this code, but I can't make compiler get along.

The type of x is dynamic, which is something I need to eliminate probably?

Is there a pattern to rewrite such a code?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
P H P
  • 5
  • 2

2 Answers2

3

Let

val foos: List[Foo[_]] = ???

This is the same as

val foos: List[Foo[T] forSome { type T }] = ???

This makes foos a list of pairs. Each pair consists of a type T and a value of type Foo[T]. In the foreach, destructure the pair to get access to that type and name it, and also get the value:

foos.foreach { case foo: Foo[t] =>
  val x = foo.bar1() // inferred type t
  foo.bar2(x) // foo.bar2 wants a t, x is a t, this is well-formed
}

Scastie

Note: the "pairs" vanish after type erasure. In the erased code, the List just contains Foo objects. It is necessary to explicitly destructure the pair with a case. If you do not, then foo will refer to the entire pair, and at each use the compiler will implicitly destructure it. When this happens, the two appearances of the contained type are not considered equal.

foos.foreach { foo =>
   val x = foo.bar1()
   foo.bar2(x)
}

is about the same as

foos.foreach { foo =>
  val x = foo match { case foo0: Foo[t] => foo0.bar1() }
  foo match { case foo0: Foo[u] => foo0.bar2(x) }
}

The compiler doesn't see that the t and the u are actually the same. This is because t is not in scope when x is defined, so you end up with x: Any, and thus you've lost type information.

Despite the existential type, this will work in future Scala (with the _ syntax).

HTNW
  • 27,182
  • 1
  • 32
  • 60
2

What do you mean it doesn't matter? It matters, and a lot!

Look at this:

val x = foo.bar1()
// stuff
foo.bar2(x)

Here you needed to make sure the type of x is the same type foo.bar2 needs.

You probably can do something like:

def process[A](data: List[Foo[A]]): Unit = 
  list.foreach { foo =>
    val x = foo.bar1()
    // stuff
    foo.bar2(x)
}

But maybe, what you wanted to say with "dynamic" is that you want to have a list of different types of Foo. So, it doesn't matter which one, because you know each foo will produce a value that it can consume, if so, you can use type members instead of type parameters:

trait Foo {
  type A
  def bar1(): A
  def bar2(a: A): Unit
}

def process(data: List[Foo]): Unit = 
  list.foreach { foo =>
    val x = foo.bar1()
    // stuff
    foo.bar2(x)
}