1
votes

Update: I've accepted an answer but I'm still very curious why what I tried doesn't work (to understand Scala implicit behaviors). Any other answers would be greatly appreciated.

(Hopefully this question is answerable without much knowledge of Circumflex, but just in case, here's the documented source code reference.)

I'm trying to add some convenience functions over the Circumflex ORM library, but I'm running into some barriers trying to use Scala implicit conversions. Below, why doesn't the implicit conversion trigger? I suspect there's some complex interaction with the subclassing and/or the recursive type parameters.

import ru.circumflex.orm._

// I subclass Record and Table to add my own convenience methods etc. (not pasted, irrelevant)
abstract class XRecord[PK, R <: XRecord[PK, R]] extends Record[PK, R] { this: R => }
trait XTable[PK, R <: XRecord[PK, R]] extends Table[PK, R] { this: R => }

// Example entity.
class Org extends XRecord[Long,Org] {
  val id = "id".BIGINT.NOT_NULL.AUTO_INCREMENT
  def PRIMARY_KEY = id
  def relation = Org
}
object Org extends Org with XTable[Long,Org]

object Test extends App {
  // I want this conversion to work for all Records, not just XRecords.
  // Need implicit f to be able to accept XRecord, a subclass of Record.
  implicit def toRichRelationNode[PK, R <: Record[PK,R], RR](xs: RR)(implicit f: RR => RelationNode[PK,R]) =
    new { def GET(f: RelationNode[PK,R] => Predicate) = 0 }

  // This works.
  toRichRelationNode(Org) GET (_.id EQ 1)

  // This doesn't:
  // "No implicit view available from Org.type => ru.circumflex.orm.RelationNode[PK,R]."
  Org GET (_.id EQ 1)
}
1
Not sure it will help in this case, but in general it is recommended to specify return type of the Implicit conversion. Also, answering the question that what is the type of new { def GET(f: RelationNode[PK,R] => Predicate) = 0 } may help you solve the problem. - Jamil
@Jamil Explicitly specifying the return type and hoisting the anonymous class into a named class isn't making any difference. - Yang
Is this really the minimum code needed to demonstrate the issue? - Seth Tisue
@SethTisue Well, understanding the exact interactions that are relevant is half of my confusion. If you have any suggestions then I'm happy to revise. - Yang

1 Answers

4
votes

Frankly, I've never used implicit parameters and I've never met a condition which forced me to use them (Circumflex itself is written without implicit parameters and goes pretty well as far as I am concerned).

Anyhow, I've been able to reproduce your scenario and make it work. It took 2 implicits, however, to make everything go fine. Here's the code:

// The helper which holds custom methods, it will accept any subclass of R
class NodeHelper[PK, R <: Record[PK, R]](val node: RelationNode[PK, R]) {
  def GET(f: R => Predicate): Option[R] = node.criteria.add(f(node)).unique()
} 

// Now the implicits and usage scenario

object Tester {

  implicit def nodeToHelper[PK, R <: Record[PK, R]](node: RelationNode[PK, R]): NodeHelper[PK, R] = new NodeHelper(node)

  implicit def tableToHelper[PK, R <: Record[PK, R]](table: Table[PK, R]): NodeHelper[PK, R] = new NodeHelper(table.AS("this"))

  // Testing with table
  println(User GET (_.cn EQ "patrick"))
  // Testing with node
  println(User.AS("u") GET (_.cn EQ "patrick"))
}

// And finally, the test model we've mentioned above

class User extends Record[Long, User] {
  def PRIMARY_KEY = id
  val id = "id".BIGINT.NOT_NULL.AUTO_INCREMENT
  val cn = "cn".TEXT.NOT_NULL
  def relation = User
}

object User extends User with Table[Long, User]

Hope it helps.