简体   繁体   中英

Understanding the interaction in Scala between self-types and type bounds

I previously tried breaking this problem down into smaller, simpler problems here and here , but I realized that answers to those, while technically correct, were not helping me understand this particular case.

I'm using a library, Circumflex ORM , that lets you define schemas as follows:

class User extends Record[Int, User] {
  val name = "name".TEXT
  val age = "age".INTEGER
  def relation = User
}
object User extends User with Table[Int, User]

This works because of an implicit view that's in-scope within Record:

abstract class Record[PK, R <: Record[PK, R]] extends Equals { this: R =>
  implicit def view(x: String) = new DefinitionHelper(x, this)
  ...
}

class DefinitionHelper[R <: Record[_, R]](name: String, record: R) {
  def TEXT = ...
  def INTEGER = ...
  ...
}

I'm trying to introduce a new extension method alongside TEXT, etc. called BYTEA. So I know I need my own implicit helper class:

class DefinitionHelper[R <: Record[_, R]](name: String, record: R) {
 def BYTEA = new BytesField[R](name, record)
}

Now I need an implicit in scope whenever I'm defining new records, but I don't want to write an import statement every time:

class User extends Record[Int, User] {
 import Implicits._
 ...
}

And I don't want to introduce this implicit to any other scopes besides the record definitions.

import Implicits._
class User extends Record[Int, User] { ... }

So one idea is to subclass Record (or introduce a mixin), then define my schema records by extending MyRecord instead of Record (or always mixing in MyMixin).

class User extends MyRecord[Int, User] { ... }

I first tried:

abstract class MyRecord[PK, R <: MyRecord[PK, R]]
extends Record[PK, R] {
  implicit def str2ddlHelper2(str: String) =
    new DefinitionHelper(str, this)
}

This produces:

illegal inheritance;  self-type MyRecord[PK,R] does not conform to ru.circumflex.orm.Record[PK,R]'s selftype R

So instead I tried:

abstract class MyRecord[PK, R <: MyRecord[PK, R]]
extends Record[PK, MyRecord[PK, R]] {
 implicit def str2ddlHelper2(str: String) =
   new DefinitionHelper(str, this)
}

But then I get these two issues when defining a record:

class User extends MyRecord[Int, User] {
 val id = "id".INTEGER
 val value = "value".BYTEA // works
 val value2 = new DefinitionHelper("", this) // does not work?!
 ...
 def relation = User // another error here
}
object User extends User with Table[Int, User]

The errors are:

inferred type arguments [User] do not
conform to class DefinitionHelper's type parameter bounds [R <:
ru.circumflex.orm.Record[_, R]]

type mismatch;  found   : User.type (with
underlying type object User)  required:
ru.circumflex.orm.Relation[Int,MyRecord[Int,User]]
Note: User <:
MyRecord[Int,User] (and
User.type <:
ru.circumflex.orm.Table[Int,User]), but
trait Relation is invariant in type R. You may wish to define R as +R
instead. (SLS 4.5)

After more fiddling, I surprised myself by finding something that worked:

abstract class MyRecord[PK, R <: MyRecord[PK, R]]
extends Record[PK, R] { this: R =>
  implicit def str2ddlHelper2(str: String) =
    new DefinitionHelper(str, this)
}

I'm curious to understand what just happened here, as well as perhaps some more examples to help me wrap my head around things better so that I don't feel like I'm always "fiddling-until-it-works."

Apologies for the question title - not sure it makes any sense.

Your first error is the simpler one, and easily solved by your final solution. The self type R=> in the declaration

Record[PK, R <: Record[PK, R]] extends Equals { this: R =>

forces every descendant of Record to ensure that it will be an R too (it does not make it an R, the descendant will have to do something to be an R). In practice, that means that in class X extends Record[PK, R] , R must be an ancestor of X (and as there is also R <: Record[PK, R] , it should be X most of the time, but as we will see at the end, this may not be the case).

This constraint disappears in MyRecord, hence your first error. Your final solution states the constraint again and that's the end of it.


Your second version is more complicated. I'll start with the second error.

first, some elements from the Circumflex API not stated above.

  • Record has an abstract def relation: Relation[PK, R]
  • Table[K,R] extends Relation[K,R]

You define relation in the class User as the object User , which is a Table[Int, User] , hence a Relation[Int, User] .

However, your class User is a MyRecord[Int, User] , but that means it is a Record[Int, MyRecord[Int, User]] , not a Record[Int, User] . The R of Record (the one that matters here) is MyRecord[Int, User] , not User . relation must thus be Relation[Int, MyRecord[Int, User]] .

A Relation[Int, User] is not a Relation[Int, MyRecord[Int, User]] , even when User is a MyRecord[Int, User] . In general, if B is an A , C[B] is not a C[A] , except if class C states so by being declared C[+X] rather than C[X] . (hence the message about Relation being invariant (no +), and suggesting a +R so that it would be covariant in R ).

I'm really less sure about the error in DefinitionHelper. It seems related to the R being MyRecord[Int, User] again. If you explicitly state that as the generic parameter R , doing

new DefinitionHelper[MyRecord[Int, User]]("", this) 

it should work (I did it on an example quite close to your code, but not actually using circumflex). Why the compiler does not infer that, I do not know. Anyway, the fact that your User is not a Record[Int, User] but Record[Int, MyRecord[Int, User]] is bound to cause problems. The actual solution is much simpler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM