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 declarationforces every descendant of
Recordto 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 inclass X extends Record[PK, R],Rmust be an ancestor ofX(and as there is alsoR <: Record[PK, R], it should beXmost 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.
Recordhas an abstractdef relation: Relation[PK, R]Table[K,R]extendsRelation[K,R]You define relation in the class
Useras the objectUser, which is aTable[Int, User], hence aRelation[Int, User].However, your class
Useris aMyRecord[Int, User], but that means it is aRecord[Int, MyRecord[Int, User]], not aRecord[Int, User]. TheRofRecord(the one that matters here) isMyRecord[Int, User], notUser. relation must thus beRelation[Int, MyRecord[Int, User]].A
Relation[Int, User]is not aRelation[Int, MyRecord[Int, User]], even whenUseris aMyRecord[Int, User]. In general, ifBis anA,C[B]is not aC[A], except if classCstates so by being declaredC[+X]rather thanC[X]. (hence the message aboutRelationbeing invariant (no +), and suggesting a+Rso that it would be covariant inR).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 parameterR, doingit 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
Useris not aRecord[Int, User]butRecord[Int, MyRecord[Int, User]]is bound to cause problems. The actual solution is much simpler.