2

I have tried solutions, described in How to override an implicit value?, but it does not help. Here is a code example.

A definition of TestImplicit abstraction with 2 different implementations (analogue of ExecutionContextExecutor):

trait TestImplicit {
  def f(s:String):Unit
}
object TestImplicitImpl1 extends TestImplicit {
  override def f(s: String): Unit = println(s"1: $s")
}
object TestImplicitImpl2 extends TestImplicit {
  override def f(s: String): Unit = println(s"2: $s")
}

And in the ImplDefinition object a q variable is defined to be used implicitly via import (analogue of ExecutionContext.Implicits.global):

object ImplDefinition {
  implicit val q:TestImplicit = TestImplicitImpl1
}

Client that defines a method, accepting TestImplicit implicitly (analogue of scala.concurrent.Future):

trait TestImplicitClient {
  def fu(implicit ti:TestImplicit):Unit
}
object TestImplicitClient extends TestImplicitClient {

  override def fu(implicit ti: TestImplicit): Unit = {
    println("client")
    ti.f("param")
  }
}

The next step, a client of client, that chooses which implementation of TestImplicit should be used, the decision is done via import (analogue of API that uses Future):

object ClientOfClient {

  import somepackage.ImplDefinition.q

  def t():Unit =
    TestImplicitClient.fu
}

Now in test, I want to use this ClientOfClient.t(), but I need to override implicit, and use TestImplicitImpl2 instead. The main idea behind - implicits should be defined/overridable by the client of API, but not by API itself:

import somepackage.{ClientOfClient, TestImplicit, TestImplicitImpl2}
import org.junit.Test

class ImplTest {
  // trying to hide it via import, does not help
  import somepackage.ImplDefinition.{q => _,_}

  @Test def test(): Unit ={
    //trying to hide it via downgrading to non-implicit, does not work either
    val q = somepackage.ImplDefinition.q
    implicit val ti = TestImplicitImpl2
    ClientOfClient.t()
  }
}

Each time I run test, I get in the output:

client
1: param

But not expected:

client
2: param

How can I fix it? I need a way to allow clients to override implicits and stay with as simple API as possible. Which means, I do not want to add additional implicit parameter into ClientOfClient.t() method.

Alexandr
  • 9,213
  • 12
  • 62
  • 102

1 Answers1

1

As soon as you have a singleton object ClientOfClient with a hard-coded constant TestImplicitImpl1 everywhere, there is essentially nothing you can do. But there are several work-arounds.


1. Use default implicit parameters

object ClientOfClient {
  def t()(implicit ti: TestImplicit = ImplDefinition.q): Unit =
    TestImplicitClient.fu
}

object ImplTest {
  def test(): Unit = {
    implicit val ti2 = TestImplicitImpl2
    ClientOfClient.t()
  }
}

ImplTest.test() // 2: param

2. Supply the implicit through a separate method that can be overridden

If you want to make the implicit overridable, then make ClientOfClient extendable, and create a method (here "cocti") that returns the implicit, instead of importing the implicit directly. You can then override the method (whereas you cannot override an import).

This here produces 2: param in the end of the day:

trait TestImplicit {
  def f(s: String): Unit
}
object TestImplicitImpl1 extends TestImplicit {
  override def f(s: String): Unit = println(s"1: $s")
}
object TestImplicitImpl2 extends TestImplicit {
  override def f(s: String): Unit = println(s"2: $s")
}

object ImplDefinition {
  implicit val q: TestImplicit = TestImplicitImpl1
}

trait TestImplicitClient {
  def fu(implicit ti: TestImplicit): Unit
}

object TestImplicitClient extends TestImplicitClient {
  override def fu(implicit ti: TestImplicit): Unit = {
    println("client")
    ti.f("param")
  }
}

class ClientOfClient {

  implicit def cocti: TestImplicit = {
    ImplDefinition.q
  }

  def t():Unit =
    TestImplicitClient.fu
}

object ImplTest {
  def test(): Unit = {
    implicit val ti2 = TestImplicitImpl2
    new ClientOfClient {
      override def cocti = ti2
    }.t()
  }
}

ImplTest.test() // 2: param
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • it works, thank you very much. But it makes API for a client much more complicated. Is there a way, for a client to override only implicit, like: implicit val ti2 = TestImplicitImpl2 without adding some additional steps? And at the same time to allow client to use a default implicit, defined directly in ClientOfClient? – Alexandr Jul 30 '18 at 13:21
  • @Alexandr You could use an implicit that is supplied from the outside, but fall back to a default if none is provided. For example `def t()(implicit ti: TestImplicit = TestImplicitImpl1) = ...`, and then in `test` just use `ClientOfClient.t()`. – Andrey Tyukin Jul 30 '18 at 13:24
  • this is ideal solution for a client:) I've forgotten about default values at all. Unfortunately we still have to adopt our client API, but nevertheless, it seems it is the best solution. Спасибо:) – Alexandr Jul 30 '18 at 13:28