Scala:理解中的可重写隐式
Scala: overridable implicits in for-comprehension
我正在尝试通过 API 定义隐式并希望允许客户端覆盖它们。这是一个讨论:[ 我已经尝试过最简单的解决方案。它按预期工作。现在我想以相同的方式定义基于未来的 API ,并将 ExecutionContext 定义为具有默认值的隐式。
/**
* 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
}
}
对于 for-comprehension 中的每一行,我都收到 5 个(相同的)错误:
[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))
我该如何解决?它基于 "implicitely" 关键字尝试了不同的语法,但仍然没有成功。
更新 1:
It is not meant to be given its argument, but rather to force the
lookup of an implicit for a given type.
一旦我摆脱了implicitly(executor)
:
def grind(beans: CoffeeBeans)
(implicit executor:ExecutionContext = global )
: Future[GroundCoffee] = Future {
...
}(implicitly[ExecutionContext])
我遇到同样的错误:
[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])
摆脱在 prepareCappuccinoAsynchroniously
中传递 executor
明确地也没有帮助。
@francoisr,能否请您举一个可行的例子,因为您的建议要么行不通,要么我没有得到正确的建议。
更新#2。这是一个工作版本,基于@Levi Ramsey 和@Łukasz 的提议。
/**
* 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
}
}
您不需要在 prepareCappuccinoAsynchroniously
中的任何地方显式传递 executor
,因为 prepareCappuccinoAsynchroniously
范围内的隐式参数将优先于 global
导入。
implicitly
方法其实不是关键字,而是scala.Predef
中定义的真正方法。它是这样实现的:
def implicitly[T](implicit t: T): T = t
它不是要给出它的参数,而是要强制查找给定类型的隐式。也就是说,如果您需要 T
隐式可用,您可以使用 val t = implicitly[T]
.
强制它
在你的情况下你根本不需要使用 implicitly
,因为你声明了一个 implicit
参数所以你已经有了它的名字。这个implicitly
方法一般和context bound一起使用,context bound是一个密切相关但更高级一点的概念。您可以根据自己的兴趣查找它,但这对您的问题并不重要。
尝试通过删除任何 implicitly
让隐式完成工作。这是一个简短的例子:
def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }(executor)
实际上,您甚至可以删除 executor
部分,只写
def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }
编辑:我误读了您的 post,您需要使用 import scala.concurrent.ExecutionContext.global
而不是 import scala.concurrent.ExecutionContext.Implicits.global
,因为隐式导入 global
会在此处引起歧义。
您是否考虑过不导入全局 ExecutionContext 这使其隐式而只是将其绑定到一个值?
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
}
然后您可以使用全局上下文,同时明确指出它的隐含位置。我还会删除 implicitly
s
下面演示了如何使用或提供一个隐式默认上下文。
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)
}
演示。
scalac EcTest.scala
scala EcTest
// output
Using executor: scala-execution-context-global-10
Using executor: pool-1-thread-1
我正在尝试通过 API 定义隐式并希望允许客户端覆盖它们。这是一个讨论:[
/**
* 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
}
}
对于 for-comprehension 中的每一行,我都收到 5 个(相同的)错误:
[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))
我该如何解决?它基于 "implicitely" 关键字尝试了不同的语法,但仍然没有成功。
更新 1:
It is not meant to be given its argument, but rather to force the lookup of an implicit for a given type.
一旦我摆脱了implicitly(executor)
:
def grind(beans: CoffeeBeans)
(implicit executor:ExecutionContext = global )
: Future[GroundCoffee] = Future {
...
}(implicitly[ExecutionContext])
我遇到同样的错误:
[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])
摆脱在 prepareCappuccinoAsynchroniously
中传递 executor
明确地也没有帮助。
@francoisr,能否请您举一个可行的例子,因为您的建议要么行不通,要么我没有得到正确的建议。
更新#2。这是一个工作版本,基于@Levi Ramsey 和@Łukasz 的提议。
/**
* 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
}
}
您不需要在 prepareCappuccinoAsynchroniously
中的任何地方显式传递 executor
,因为 prepareCappuccinoAsynchroniously
范围内的隐式参数将优先于 global
导入。
implicitly
方法其实不是关键字,而是scala.Predef
中定义的真正方法。它是这样实现的:
def implicitly[T](implicit t: T): T = t
它不是要给出它的参数,而是要强制查找给定类型的隐式。也就是说,如果您需要 T
隐式可用,您可以使用 val t = implicitly[T]
.
在你的情况下你根本不需要使用 implicitly
,因为你声明了一个 implicit
参数所以你已经有了它的名字。这个implicitly
方法一般和context bound一起使用,context bound是一个密切相关但更高级一点的概念。您可以根据自己的兴趣查找它,但这对您的问题并不重要。
尝试通过删除任何 implicitly
让隐式完成工作。这是一个简短的例子:
def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }(executor)
实际上,您甚至可以删除 executor
部分,只写
def grind(beans: CoffeeBeans)(implicit executor:ExecutionContext = global): Future[GroundCoffee] = Future { ??? }
编辑:我误读了您的 post,您需要使用 import scala.concurrent.ExecutionContext.global
而不是 import scala.concurrent.ExecutionContext.Implicits.global
,因为隐式导入 global
会在此处引起歧义。
您是否考虑过不导入全局 ExecutionContext 这使其隐式而只是将其绑定到一个值?
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
}
然后您可以使用全局上下文,同时明确指出它的隐含位置。我还会删除 implicitly
s
下面演示了如何使用或提供一个隐式默认上下文。
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)
}
演示。
scalac EcTest.scala
scala EcTest
// output
Using executor: scala-execution-context-global-10
Using executor: pool-1-thread-1