0

I am trying to define implicits by API and want to allow client to override them. Here is a discussion: [How to override an implicit value, that is imported? I have tried it with simplest solution. It works as expected. Now I want to define future-based API in the same way, with ExecutionContext defined as implicit with default value.

/**
  * Client can reuse default implicit execution context or override it
  */
trait CappuccinoWithOverridableExecutionContext {
  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.concurrent.Future
  import scala.util.Random
  import com.savdev.fp.monad.composition.future.scala.Cappuccino._

  def grind(beans: CoffeeBeans)
           (implicit executor:ExecutionContext = global )
  : Future[GroundCoffee] = Future {
    println("01.Start start grinding..., " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    if (beans == "baked beans") throw GrindingException("are you joking?")
    println("01.End finished grinding...")
    s"ground coffee of $beans"
  }(implicitly(executor))

  def heatWater(water: Water)
               (implicit executor:ExecutionContext = global )
  : Future[Water] = Future {
    println("02.Start heating the water now, " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("02.End hot, it's hot!")
    water.copy(temperature = 85)
  }(implicitly(executor))

  def frothMilk(milk: Milk)
               (implicit executor:ExecutionContext = global )
  : Future[FrothedMilk] = Future {
    println("03.Start milk frothing system engaged!, " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("03.End shutting down milk frothing system")
    s"frothed $milk"
  }(implicitly(executor))

  def brew(coffee: GroundCoffee, heatedWater: Water)
          (implicit executor:ExecutionContext = global )
  : Future[Espresso] = Future {
    println("04.Start happy brewing :), " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("04.End it's brewed!")
    "espresso"
  }(implicitly(executor))

  def combine(espresso: Espresso, frothedMilk: FrothedMilk)
             (implicit executor:ExecutionContext = global )
  : Future[Cappuccino.Cappuccino] = Future {
    println("05.Start happy combining :), " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("05.End it's combined!")
    "cappuccino"
  } (implicitly(executor))

  // going through these steps asynchroniously:
  def prepareCappuccinoAsynchroniously(implicit executor:ExecutionContext = global )
  : Future[Cappuccino.Cappuccino] = {
    println("Preparing cappucchino with overridable execution context")
    val groundCoffee = grind("arabica beans")(implicitly(executor))
    val heatedWater = heatWater(Water(20))(implicitly(executor))
    val frothedMilk = frothMilk("milk")(implicitly(executor))
    for {
      ground <- groundCoffee
      water <- heatedWater
      foam <- frothedMilk
      espresso <- brew(ground, water)(implicitly(executor))
      cappuchino <- combine(espresso, foam)(implicitly(executor))
    } yield cappuchino
  }

}

I am getting 5 (same) errors for each line inside of the for-comprehension:

[ERROR] .../src/main/scala/com/savdev/fp/monad/composition/future/scala/CappuccinoWithOverridableExecutionContext.scala:91: error: ambiguous implicit values:
[ERROR]  both lazy value global in object Implicits of type => scala.concurrent.ExecutionContext
[ERROR]  and value executor of type scala.concurrent.ExecutionContext
[ERROR]  match expected type scala.concurrent.ExecutionContext
[ERROR]       cappuchino <- combine(espresso, foam)(implicitly(executor))

How can I solve it? It tried different syntaxes, based on the "implicitely" keyword, but still not success.

Update 1:

It is not meant to be given its argument, but rather to force the lookup of an implicit for a given type.

As soon as I get rid of implicitly(executor):

  def grind(beans: CoffeeBeans)
           (implicit executor:ExecutionContext = global )
  : Future[GroundCoffee] = Future {
    ...
  }(implicitly[ExecutionContext])

I am getting the same error:

[ERROR] .../src/main/scala/com/savdev/fp/monad/composition/future/scala/CappuccinoWithOverridableExecutionContext.scala:25: error: ambiguous implicit values:
[ERROR]  both lazy value global in object Implicits of type => scala.concurrent.ExecutionContext
[ERROR]  and value executor of type scala.concurrent.ExecutionContext
[ERROR]  match expected type scala.concurrent.ExecutionContext
[ERROR]   }(implicitly[ExecutionContext])

Getting rid of passing executor in prepareCappuccinoAsynchroniously explicitly did not help either. @francoisr, can you please give a working example, cause your proposal either does not work, or I did not get correctly.

Update #2. Here is a working version, based on @Levi Ramsey and @Łukasz proposals.

/**
  * Client can reuse default implicit execution context or override it
  */
trait CappuccinoWithOverridableExecutionContext {
  import scala.concurrent.Future
  import scala.util.Random
  import com.savdev.fp.monad.composition.future.scala.Cappuccino._

  //do not import it:
  //import scala.concurrent.ExecutionContext.Implicits.global
  val defaultEc = scala.concurrent.ExecutionContext.Implicits.global

  def grind(beans: CoffeeBeans)
           (implicit executor:ExecutionContext = defaultEc)
  : Future[GroundCoffee] = Future {
    println("01.Start start grinding..., " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    if (beans == "baked beans") throw GrindingException("are you joking?")
    println("01.End finished grinding...")
    s"ground coffee of $beans"
  }

  def heatWater(water: Water)
               (implicit executor:ExecutionContext = defaultEc)
  : Future[Water] = Future {
    println("02.Start heating the water now, " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("02.End hot, it's hot!")
    water.copy(temperature = 85)
  }

  def frothMilk(milk: Milk)
               (implicit executor:ExecutionContext = defaultEc )
  : Future[FrothedMilk] = Future {
    println("03.Start milk frothing system engaged!, " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("03.End shutting down milk frothing system")
    s"frothed $milk"
  }

  def brew(coffee: GroundCoffee, heatedWater: Water)
          (implicit executor:ExecutionContext = defaultEc )
  : Future[Espresso] = Future {
    println("04.Start happy brewing :), " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("04.End it's brewed!")
    "espresso"
  }

  def combine(espresso: Espresso, frothedMilk: FrothedMilk)
             (implicit executor:ExecutionContext = defaultEc )
  : Future[Cappuccino.Cappuccino] = Future {
    println("05.Start happy combining :), " +
      "thread: " + Thread.currentThread().getName)
    TimeUnit.SECONDS.sleep(Random.nextInt(3))
    println("05.End it's combined!")
    "cappuccino"
  }

  // going through these steps synchroniously, wrong way:
  def prepareCappuccinoSequentially(implicit executor:ExecutionContext = defaultEc )
  : Future[Cappuccino.Cappuccino] = {
    for {
      ground <- grind("arabica beans")
      water <- heatWater(Water(20))
      foam <- frothMilk("milk")
      espresso <- brew(ground, water)
      cappuchino <- combine(espresso, foam)
    } yield cappuchino
  }

  // going through these steps asynchroniously:
  def prepareCappuccinoAsynchroniously(implicit executor:ExecutionContext = defaultEc)
  : Future[Cappuccino.Cappuccino] = {
    println("Preparing cappucchino with overridable execution context")
    val groundCoffee = grind("arabica beans")
    val heatedWater = heatWater(Water(20))
    val frothedMilk = frothMilk("milk")
    for {
      ground <- groundCoffee
      water <- heatedWater
      foam <- frothedMilk
      espresso <- brew(ground, water)
      cappuchino <- combine(espresso, foam)
    } yield cappuchino
  }

}
Alexandr
  • 9,213
  • 12
  • 62
  • 102
  • why can't you use the scala.concurrent.ExecutionContext.Implicit.global ? I mean why do you need to create your own ExecutionContext? you can simply import Execution context as far as i know it's the most efficient one. Actually the problem is you are using more then one implicit variable of the same type in the same object. – Raman Mishra Aug 20 '18 at 12:53
  • @RamanMishra, I can. The main motivation behind - allow client to choose, which execution context to use. Either a default one, used by API creator, or to override it. That's what I try to achieve. – Alexandr Aug 20 '18 at 12:56
  • then try declearing the context in different different scope, that should work i think. Right now there is collesion of implicits because you are using the same type of implicits more than once. – Raman Mishra Aug 20 '18 at 12:57
  • @RamanMishra There are good reasons for using a custom `ExecutionContext`. The `global` one is backed by a fork-join thread pool, which is good for many application but is for example rather bad for intensive IO. – francoisr Aug 20 '18 at 13:00
  • @francoisr i am not saying there are not, i was just trying to understand why he is tring to use custom one. – Raman Mishra Aug 20 '18 at 13:02
  • 1
    Replace your import of global ec with `scala.concurrent.ExecutionContext.global` (no `Implicits`). Imports and implicit parameters will always conflict... – Łukasz Aug 20 '18 at 13:03
  • @Łukasz, it works!!! Ty! – Alexandr Aug 20 '18 at 13:14
  • @Łukasz, "Imports and implicit parameters", where can I find something to read about these conflicts? Maybe you can recommend something? – Alexandr Aug 20 '18 at 13:27
  • @Alexandr https://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits?rq=1 – Łukasz Aug 20 '18 at 13:50
  • Please avoid using default parameters for implicit parameters—it really easily creates code which is using the wrong implicit without the developer noticing. – Viktor Klang Aug 23 '18 at 16:21

3 Answers3

2

Have you considered not importing the global ExecutionContext which makes it implicit but just binding it to a value?

trait CappuccinoWithOverridableExecutionContext {
  import scala.concurrent.Future
  import scala.util.Random
  import com.savdev.fp.monad.composition.future.scala.Cappuccino._

  protected val global = scala.concurrent.ExecutionContext.Implicits.global // probably rename this to something like defaultGlobalExecutionContext

}

Then you can use the global context while being explicit about where it's implicit. I'd also remove the implicitlys

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
0

You should not need to explicitly pass executor anywhere in prepareCappuccinoAsynchroniously, because implicit parameters in the scope of prepareCappuccinoAsynchroniously will get priority over the global import.

The implicitly method is actually not a keyword, but a real method defined in scala.Predef. It's implemented like this:

def implicitly[T](implicit t: T): T = t

It is not meant to be given its argument, but rather to force the lookup of an implicit for a given type. That is, if you need a T to be available implicitly, you can force it using val t = implicitly[T].

You should not need to use implicitly at all in your case, because you declare an implicit parameter so you already have a name for it. This implicitly method is generally used with context bound, which is a closely related but a bit more advanced concept. You can look it up in you're interested but it doesn't really matter for your problem.

Try to let the implicits do the work by removing any implicitly. Here is a short example:

def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }(executor)

Actually, you should even be able to drop the executor part and only write

def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }

EDIT: I misread your post, you need to use import scala.concurrent.ExecutionContext.global instead of import scala.concurrent.ExecutionContext.Implicits.global because importing global implicitly will cause ambiguity here.

francoisr
  • 4,407
  • 1
  • 28
  • 48
  • without executor in definition of, for instance grind, the same error. When I pass it explicitly as: def grind(..) = Future {..} (executor) it works, but it worked also with: (implicitly(executor)) and (implicitly[ExecutionContext]). But I still cannot make prepareCappuccinoAsynchroniously compilable. The same error – Alexandr Aug 20 '18 at 13:10
  • Have you replaced `import scala.concurrent.ExecutionContext.Implicits.global` with `import scala.concurrent.ExecutionContext.global`. You really don't want your default `ExecutionContext` to also be implictly imported. – francoisr Aug 20 '18 at 14:34
0

The following demonstrates how you can use an implicit default context, or provide one.

object EcTest extends App {

  import scala.concurrent.Future
  import scala.concurrent.ExecutionContext
  import java.util.concurrent.Executors


  val default = scala.concurrent.ExecutionContext.Implicits.global

  def test(comp: String)(implicit exc: ExecutionContext = default): Future[String] = Future { 
    println("Using executor: " + Thread.currentThread().getName)
    comp 
  }

  test("Use default executor")

  val myExecutor = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor)
  test("Use custom executor")(myExecutor)

}

Demo.

scalac EcTest.scala
scala EcTest

// output
Using executor: scala-execution-context-global-10
Using executor: pool-1-thread-1
jacks
  • 4,614
  • 24
  • 34