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
}
}
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 implicitly
s