1

Because traits with representation types are self-referential, declaring that a variable holds an instance of that trait is a little difficult. In this example I simply declare that a variable holds an instance of the trait, declare that a function takes and returns and instance of that trait, and call that function with the variable:

trait Foo[+A <: Foo[A]]
case class Bar() extends Foo[Bar]
case class Grill() extends Foo[Grill]

// Store a generic instance of Foo
val b: Foo[_] = if(true) {
  Bar()
} else {
  Grill()
}

// Declare a function that take any Foo and returns a Foo of the same type
// that "in" has in the calling context
def echoFoo[A <: Foo[A]](in: A): A = in

// Call said function
val echo = echoFoo(b)

It fails with the error:

inferred type arguments [this.Foo[_$1]] do not conform to method 
echoFoo's type parameter bounds [A <: this.Foo[A]]
val echo = echoFoo(b)
           ^

Now, this makes sense because [_] is like Any (in ways I don't fully understand). What it probably wants is something like Foo[Foo[_]], so that the type parameter conforms to the bounds of A <: Foo[A]. But now there's an inner Foo that has a non-conforming type parameter, suggesting that the solution is something like Foo[Foo[Foo[Foo[..., which is clearly not correct.

So my question can probably be distilled down to: What is the Scala syntax for "This variable holds any legal Foo"?

drhagen
  • 8,331
  • 8
  • 53
  • 82
  • As I said in my answer, these problems come due to poor design. If you explain us more what you would like to do, we can discuss the best design for that – Edmondo Jul 24 '12 at 07:01
  • @Edmondo1984 You are not the first person to suggest that my fundamental design might be wrong. But of course, you can't suggest a fix without knowing what I am trying to accomplish with Scala. I am going to post a follow-up question that will be closer to my actual problem. – drhagen Jul 24 '12 at 12:52
  • post a ref here so we can try to help – Edmondo Jul 24 '12 at 13:08
  • I have posted an [extended follow-up question](http://stackoverflow.com/questions/11635981/) framing my problem in terms closer to my actual problem. – drhagen Jul 24 '12 at 17:19

2 Answers2

2

Self-referential type parameters like this are a bit problematic, because they're not sound. For example, it's possible to define a type like the following:

case class BeerGarden extends Foo[Grill]

As you can see, the A <: Foo[A] bound isn't sufficiently tight. What I prefer in situations like this is to use the cake pattern, and abstract type members:

trait FooModule {
  type Foo <: FooLike

  def apply(): Foo

  trait FooLike {
    def echo: Foo
  }
}

Now you can use the Foo type recursively and safely:

object Foos {
  def echo(foo: FooModule#Foo) = foo.echo
}

Obviously, this isn't an ideal solution to all the problems you might want to solve with such types, but the important observation is that FooLike is an extensible trait, so you can always continue to refine FooLike to add the members that you need, without violating the bound that the type member is intended to enforce. I've found that in every real-world case where the set of types I want to represent is not closed, this is about the best that one can do. The important thing to see is that FooModule abstracts over both the type and the instance constructor, while enforcing the "self-type." You can't abstract over one without abstracting over the other.

Some additional information on this sort of thing (and a bit of a record of my own early struggles with recursive types) is available here:

https://issues.scala-lang.org/browse/SI-2385

Kris Nuttycombe
  • 4,560
  • 1
  • 26
  • 29
  • What is the return type of `Foos.echo`? – drhagen Jul 23 '12 at 20:47
  • If `Foos.echo` is called on a concrete instance of `FooModule#Foo`, say `BarModule#Foo`, will it return a concrete instance, or will it be downcast to `FooModule#Foo`? – drhagen Jul 23 '12 at 21:02
0

While I agree the problem of propagating generics exists, when you hit this problem you should see a big WARNING on your screen because its typically a sign of a bad design. These are general suggestions on the topic.

  • If you use generics, the type parameter is there for a reason. It lets you interact with a Foo[A] in a type-safe manner by passing in or receiving parameters of type A and allows to put you constraint on A. If you lose the type information, you lose the type-safety and in that case so there is no point of writing a generic class if you do not need the generic anymore: you can change all your signatures to Any and do pattern matching.

  • In most of the cases, recursive types can be avoided by implementing something like the CanBuildFrom approach for collections, using a "typeclass"

  • Finally,type-projection (FooModule#Foo) has little application and you might want to look to path-dependent types. However, these have little application as well.

Edmondo
  • 19,559
  • 13
  • 62
  • 115