Overriding implicit contexts?

Hi all,

I’m wondering if there’s a sneaky trick I can pull to get past the implicit scoping rules so I can indicate one implicit is preferred over another.

My usecase: I have an API where I want to build up a context and pass it through the system.

package derp

class Context(val state: MyState) {

  def child[R](childState: MyState)(f: Context => R): R =
    f(new Context(childState))

  override def toString: String =
    s"Context(state = ${state})"
}

case class MyState(name: String)

class MyClass {

  val myState: MyState = MyState("myClass state")

  def doStuff()(implicit ctx: Context): Unit = {
    println(s"do stuff with ctx = ${ctx}")
    val myClass = new MyClass
    ctx.child(myState) { implicit f: Context =>
      myClass.doSomeOtherStuff
    }
  }

  def doSomeOtherStuff(implicit ctx: Context): Unit =
    println(s"do some other stuff with ctx = ${ctx}")
}

object Main {
  def main(args: Array[String]): Unit = {
    implicit val rootContext: Context = new Context(MyState("root"))

    val myClass = new MyClass()
    myClass.doStuff()
  }
}

The problem I have is that I can’t figure out a way to say “I want the child context to be the implicit here”:

Error:(22, 15) ambiguous implicit values:
 both value ctx of type derp.Context
 and value f of type derp.Context
 match expected type derp.Context
      myClass.doSomeOtherStuff

Is there a way to trick the compiler into treating childContext as higher priority or a more relevant type somehow?

Looks like I can trick it by specifying a more specific type:

package example

class Context[S](val state: S) {

  private def child[L, R](childState: L)(f: Context[L] => R): R =
    f(new Context[L](childState))

  override def toString: String =
    s"Context(state = $state)"
}

object Context {
  type C[_] = Context[_]

  def child[L, R](childState: L)(f: Context[L] => R)(implicit ctx: Context[_]): R =
    ctx.child(childState)(f)
}

case class MyState(name: String)

class MyClass {
  import Context._

  val myState: MyState = MyState("myClass state")

  def doStuff[_: C](): Unit = {
    val ctx = implicitly[C[_]]
    println(s"do stuff with ctx = $ctx")
    Context.child(myState) { implicit f =>
      doSomeOtherStuff()
    }
  }

  def doSomeOtherStuff[_: C](): Unit = {
    val ctx = implicitly[C[_]]
    println(s"do some other stuff with ctx = $ctx")
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    implicit val rootContext = new Context(MyState("root"))

    val myClass = new MyClass()
    myClass.doStuff()
  }
}
1 Like

I think that currently the most straightforward way is always using the same name for your context. Then the child ctx will shadow the outer ctx. In Scala 3 the inner most implicit should be picked automatically, regardless of its name. Or that’s how it was last time I checked.