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

159 views
Skip to first unread message

Adam Warski

unread,
Mar 22, 2012, 7:49:41 PM3/22/12
to scala...@googlegroups.com
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

Aleksey Nikiforov

unread,
Mar 22, 2012, 11:39:24 PM3/22/12
to Adam Warski, scala...@googlegroups.com
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.

Adam Warski

unread,
Mar 23, 2012, 12:42:56 AM3/23/12
to Aleksey Nikiforov, scala...@googlegroups.com

> 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


Adriaan Moors

unread,
Mar 23, 2012, 4:43:55 AM3/23/12
to scala...@googlegroups.com
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())
}

Adam Warski

unread,
Mar 23, 2012, 5:19:49 PM3/23/12
to scala...@googlegroups.com
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

nuttycom

unread,
Mar 24, 2012, 12:34:25 AM3/24/12
to scala...@googlegroups.com
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

Adam Warski

unread,
Mar 24, 2012, 11:44:20 AM3/24/12
to scala...@googlegroups.com
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
Reply all
Reply to author
Forward
0 new messages