简体   繁体   中英

scala: override implicit parameter around a call-by-name code block

Is there a way to override an implicit parameter used by functions invoked inside a control structure block? I have some code that looks like this:

def g()(implicit y: Int) {
  // do stuff with y
}

class A {
  implicit val x: Int = 3

  def f() {
    overrideImplicit(...) { // <-- overrideImplicit() is permitted to do anything it wants make it so that g() sees a different implicit val, as long as we do not explicitly declare "implicit" here (though it could happen within the method/function)
      g() // somehow sees the new implicit as opposed to x
    }
  }
}

My understanding is that even if overrideImplicit() sets the implicit inside itself, g() is still going to see the one that was in scope at the time, which is the one declared in A. I realize that one way to get the desired behavior is to explicitly state "implicit val x2: Int = 4" inside f(), but I want to avoid that and hide the fact that implicits are used. Is there any way to do this? Thanks.

This is currently being done in STMs like this:

implicit object globalCtx extends Ctx
val r = ref(0)

atomic { implicit txn =>
  r := 5 // resolves `txn` as the implicit parameter instead of globalCtx
}

so to my knowledge, there is no better way to do it. At least not yet - see this discussion on SAM (Single Abstract Method) types and possibly adding them to Scala. It's suggested at one point SAM closures could solve this problem if they were implemented so that implicits inside the SAM closure are resolved once again in the context of the target type.

I'd really question the reasoning behind why you'd want to do such a thing, as I sure it could lead to some unexpected behaviour down the line.

If you can live with x being a var though (and accessible from the overrideImplicit definition), this could get you close

def overrideImplicit(i:Int)(block: =>Unit) = {
  val old = x
  x = i
  block
  x = old
}

I'm not sure of a way to do this, but I also don't think it's such a good idea, given that you can't use two different variables with the same type, and given that even if you got it to work it wouldn't be obvious to many people what the expected behavior was. Have you considered using a mutable stack instead? Something like

object Example {
  private[this] val stack = collection.mutable.ArrayStack[Int](0)
  def g() { println(stack.top) }
  def using(i: Int)(f: => Unit) { stack push i; f; stack pop }
  def f() {
    using(1) { g }
  }
}

This is how I do it:

trait A {
  implicit val unwantedImplicit: X = ...
}

object/class B extends A {

  def blockWhereMeWannaOverrideImplicit() = ({
    implicit unwantedImplicit: X => // here I have new X instead of one from trait A
      ...
  })(new X)

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM