1
votes

I created an group of case classes which I want to implement different behaviors using Scala Typeclass. The code sample below works as expected.

case class QueryBuilder(s: String)

abstract class A()
case class B() extends A
case class C() extends A

abstract class S()
case class X() extends S
case class Y() extends S

trait BuildableQuery[T] {
  def toQuery(p: T): QueryBuilder
}
implicit object BQueryBuilder extends BuildableQuery[B] {
  def toQuery(p: B): QueryBuilder = { QueryBuilder("Query of B") }
}
implicit object CQueryBuilder extends BuildableQuery[C] {
  def toQuery(p: C): QueryBuilder = { QueryBuilder("Query of C") }
}
implicit object XQueryBuilder extends BuildableQuery[X] {
  def toQuery(p: X): QueryBuilder = { QueryBuilder("Query of X") }
}
implicit object YQueryBuilder extends BuildableQuery[Y] {
  def toQuery(p: Y): QueryBuilder = { QueryBuilder("Query of Y") }
}

def toQuery[A: BuildableQuery](value: A): QueryBuilder =
  (implicitly[BuildableQuery[A]]).toQuery(value)

println(toQuery(B()).s) // Query of B
println(toQuery(C()).s) // Query of C
println(toQuery(X()).s) // Query of X
println(toQuery(Y()).s) // Query of Y

So far so good. I can use the method toQuery with any of the defined types.

My problem: I want to create a function that returns a BuildableQuery by combining abstract classes A and S.

I tried creating a new implicit object which defines a behavior for a new case class consisting of a combination of A and S. When using it in a function, it does not compile.

case class AS[I <: A, P <: S](a: I, s: P)
implicit object ASQueryBuilder extends BuildableQuery[AS[B, X]] {
  def toQuery(p: AS[B, X]): QueryBuilder = { QueryBuilder("Query of BX") }
}

// Does not compile...
// Error: could not find implicit value for evidence parameter of type BuildableQuery[AS[A,S]]
def func(a: A, s: S): QueryBuilder = toQuery(AS(a,s))

// Does not compile...
// Error: could not find implicit value for evidence parameter of type BuildableQuery[AS[I,P]]
def func2[I <: A: BuildableQuery, P <: S: BuildableQuery](a: I, s: P): QueryBuilder = 
  toQuery(AS(a,s))

Does a solution exist for this problem?

1

1 Answers

1
votes

Update

If you only want to define AS[I, P] instances for particular values of I and P, that's also possible—you can define ASQueryBuilder as in your question, and then require an instance in your methods that use the type class:

def func(a: A, s: S)(implicit bq: BuildableQuery[AS[A, S]]): QueryBuilder = toQuery(AS(a,s))

def func2[I <: A: BuildableQuery, P <: S: BuildableQuery](a: I, s: P)(implicit
  bq: BuildableQuery[AS[I, P]]
): QueryBuilder = toQuery(AS(a, s))

Now these will only compile if the appropriate instances are available.


Original answer

It sounds like you want to create instances generically, not just for AS[B, X]. You can do this with a generic method:

implicit def ASQueryBuilder[I <: A, P <: S]: BuildableQuery[AS[I, P]] =
  new BuildableQuery[AS[I, P]] { 
    def toQuery(p: AS[I, P]): QueryBuilder = { QueryBuilder("Query of AS") }
}

Now your examples will compile.

As a side note, using the same name (toQuery) as both a type class method and type class syntax method can make things a little difficult. You may want to rename one so that e.g. the syntax method is available in the definition of a type class instance's toQuery.