Reputation: 67300
I am trying to build a small DSL that allows some if-then-else branches with two types of composition, one generic (If
) and one with added capabilities (IfGE
). I was under the impression that with mixing in a low priority implicit trait I can make Scala select the more precise return type for the Else
operation, but it fails. Here is the construction:
Edit: Here is a minimal case. Further below is a more thorough walk-through of the context. For answering the question, probably focusing on this minimal case is sufficient, while to understand what I'm doing the longer example might be better.
trait GE
trait If[A] {
def Else[B >: A, Out](cond: => B)(implicit result: Result[B, Out]): Out
}
trait IfGE extends If[GE] with GE
case class SinOsc() extends GE
case class WhiteNoise() extends GE
trait LowPri {
implicit def AnyRes[A]: Result[A, If[A]] = ??? // !
}
object Result extends LowPri {
implicit def GERes: Result[GE, IfGE] = ???
}
sealed trait Result[A, Out]
def IfExample: If[SinOsc]
val res1: GE = IfExample.Else(WhiteNoise())
val res2: GE = IfExample.Else[GE, IfGE](WhiteNoise())
Here, type inference and implicit resolution fails for res1
, while explicitly putting the types in res2
makes it work. I need to get res1
working, i.e. without specifying the type parameters.
Longer example:
(1) A graph element with some implicits to use numbers as graph elements and apply binary operators:
object GE {
implicit def intIsGE (x: Int ): GE = ???
implicit def doubleIsGE(x: Double): GE = ???
implicit class GEOps(private val ge: GE) extends AnyVal {
def <= (that: GE): GE = ???
def > (that: GE): GE = ???
def & (that: GE): GE = ???
def poll(): Unit = ???
}
}
trait GE
(2) A branching structure:
object If {
def apply(cond: => GE): IfBuilder = ???
}
trait IfBuilder {
def Then [A](branch: => A): If[A]
}
trait If[A] {
def Else: ElseBuilder[A]
def ElseIf(cond: => GE): ElseIfBuilder[A]
}
trait IfGE extends If[GE] with GE
object ElseBuilder {
trait LowPri {
implicit def AnyRes[A]: Result[A, If[A]] = ???
}
object Result extends LowPri {
implicit def GERes: Result[GE, IfGE] = ???
}
sealed trait Result[A, Out]
}
trait ElseBuilder[A] {
def apply[B >: A, Out](b: => B)(implicit res: ElseBuilder.Result[B, Out]): Out
}
trait ElseIfBuilder[A] {
def Then [B >: A](branch: => B): If[B]
}
(3) Some example graph elements:
case class Out(signal: GE)
case class SinOsc(freq: GE) extends GE
case class Dust(freq: GE) extends GE
case class WhiteNoise() extends GE
(4) A test suite:
trait Tests {
def freq: GE
// ---- Unit/Any result ----
val res0 = If (freq > 600) Then {
Out(SinOsc(freq))
}
val res1 = If (freq > 400 & freq <= 600) Then {
Out(SinOsc(freq))
} Else {
freq.poll()
}
// ---- GE result ----
val res2: GE = If (freq > 100) Then {
SinOsc(freq)
} Else {
WhiteNoise()
}
val res3: GE = If (freq > 1000) Then {
SinOsc(freq)
} ElseIf (freq > 100) Then {
Dust(freq)
} Else {
WhiteNoise()
}
Out(res3)
}
But the last two tests (res2
and res3
) do not work. The return type is not IfGE
apparently but only If[GE]
. How can I fix this, so that the last two examples find GERes
instead of AnyRes
?
Upvotes: 4
Views: 191
Reputation: 14224
Concerning your shorter example:
Definition implicit def GERes: Result[GE, IfGE]
corresponds to type GE
exactly, but WhiteNoise()
has type WhiteNoise <: GE
, and that doesn't fit the implicit.
You can either change the definition of the implicit to work for child types of GE
:
object Result extends LowPri {
implicit def GERes[T <: GE]: Result[T, IfGE] = ???
}
Or define Result
to be contravariant in its first type parameter. That will make the implicits work for subtypes as well:
sealed trait Result[-A, Out]
object Result extends LowPri {
implicit def GERes: Result[GE, IfGE] = ???
}
Upvotes: 3