Type errors with recursive type parameter: Record[T <: Record[T]]

Showing 1-7 of 7 messages
Type errors with recursive type parameter: Record[T <: Record[T]] Adam Warski 3/22/12 4:49 PM
Hello,

I'm working with Lift's mongo-record library, which as a base uses Lift record. The basic trait there is: Record[T <: Record[T]] { self: T => ... }.
Now I have some code which creates a list of arbitrary records, plus some code which consumes such a list, or one given record.
I think a code example will be best here:


The method which creates a list of any Record implementations has type List[Record[_]]. The method which consumes such a list takes a List[Record[_]] as a parameter. Now, code from external libraries which has methods that operate on a single given record has type signature like:

def consumeRecord[T <: Record[T]](record: Record[T]) 

which when used with a Record[_], produces this compile error:

error: inferred type arguments [_$2] do not conform to method consumeRecord's type parameter bounds [T <: Example.Record[T]]
records.foreach(consumeRecord(_))

The _ in Record must be in the bounds, as otherwise you wouldn't be able to define the class.

Is it possible to make my code compile without casts?

Thanks,
Adam
Re: [scala-user] Type errors with recursive type parameter: Record[T <: Record[T]] Aleksey Nikiforov 3/22/12 8:39 PM
The best way to fix this is to change the type signature to:

def consumeRecord[T <: Record[T]](record: T)

However if it is in an external library, then it's out of your hands. In this case you can create a wildcard type that will bypass the type system (read: it's a hack)...

package object foo {
  val Recursor: {  type Recursive <: Record[Recursive] } = null
  type WildcardRecord = Recursor.type#Recursive
}

Of course you will still have to cast into WildcardRecord at some point, but in fewer places.
Re: [scala-user] Type errors with recursive type parameter: Record[T <: Record[T]] Adam Warski 3/22/12 9:42 PM

> def consumeRecord[T <: Record[T]](record: T)

I tried that on my example code, doesn't compile as well (same msg).
Why would this solve the problem?

> However if it is in an external library, then it's out of your hands. In this case you can create a wildcard type that will bypass the type system (read: it's a hack)...
>
> package object foo {
>   val Recursor: {  type Recursive <: Record[Recursive] } = null
>   type WildcardRecord = Recursor.type#Recursive
> }
>
> Of course you will still have to cast into WildcardRecord at some point, but in fewer places.

Ah, so it's basically a type of a non-existing implementation. And this would work because no actual casting would take place, because of erasure.

Thanks,
Adam

--
Adam Warski

http://twitter.com/#!/adamwarski
http://www.softwaremill.com
http://www.warski.org


Re: Type errors with recursive type parameter: Record[T <: Record[T]] Adriaan Moors 3/23/12 1:43 AM
hi,

the problem is we do not infer the bounds for the existentials -- you'd have to write types like T forSome {type T <: Record[T]}, but I recommend avoiding existentials whenever possible

you can avoid existentials by either 1) using covariance (so Record[Any] is like Record[_])  2) type members

to illustrate 2), this is what I'd write:

object Example {
  trait Record {
    type Self // okay, you lose the self type annotation (which wasn't used anyway),
    // but otherwise your life will be much nicer
    def me: Self // access this record under the assumed Self type
    def x: String
  }

  class Impl1 extends Record { type Self = Impl1 ; def me = this; val x = "1"}
  class Impl2 extends Record { type Self = Impl2 ; def me = this; val x = "2" }

  def createRecords(): List[Record] =
    List(new Impl1, new Impl2)

  def consumeRecords(records: List[Record]) =
    records foreach consumeRecord

  def consumeRecord(record: Record) {
    println(record.x)
  }

  // to illustrate how to refer to the Self type member
  def mapRecord[T](record: Record)(f: record.Self => T): T =
    f(record.me)

  consumeRecords(createRecords())
}
Re: Type errors with recursive type parameter: Record[T <: Record[T]] Adam Warski 3/23/12 2:19 PM
I tried with existentials but couldn't make it work either. Where would you need to put them? And I'd very much like to understand *why* it doesn't work :)

As I wrote the definition of the Record trait is fixed by the framework.

Adam
Re: Type errors with recursive type parameter: Record[T <: Record[T]] nuttycom 3/23/12 9:34 PM
Ugh... That's a lousy way to try to get the thistype. I see this again and and again and it always depresses me, having been down that road.

trait A[T <: A[T]] doesn't even achieve the desired effect of constraining the type parameter, which you discover when the compiler *tells* you when it forces you to try to use an existential. Here's an example:

trait A[T <: A[T]] { self: T =>
 //... try to do say something about T
}
trait B extends A[B]
trait C extends A[B] //totally valid according to the type signature, but not what was hoped for.

The fact of the mater is that Scala doesn't have a way to refer to the general type of the this reference - i.e. the type that is not the singleton type. And really, it's with good reason - because inheritance is not a reasonable way to model these kinds of problems. Typeclasses, however, are. Too bad that Record attempts the broken inheritance way.

Kris
Re: Type errors with recursive type parameter: Record[T <: Record[T]] Adam Warski 3/24/12 8:44 AM
Hmm but is it all all possible to have this work in a type-safe way, given the way Record is defined, and possibly using some existentials?

Also, how would a solution which uses typeclasses look like?

Adam