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

}

然后您可以使用全局上下文,同时明确指出它的隐含位置。我还会删除 implicitlys

下面演示了如何使用或提供一个隐式默认上下文。

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