0__ - 2 months ago 5x
Scala Question

# If-Then-Else DSL - define implicits to distinguish two return types

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`
`AnyRes`
?

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] = ???
}
``````