Scala:全面理解中的可覆盖隐式

时间:2018-08-20 11:54:36

标签: scala override future for-comprehension implicits

我试图通过API定义隐式,并希望允许客户端覆盖它们。这里是一个讨论:[How to override an implicit value, that is imported?我已经用最简单的解决方案进行了尝试。它按预期工作。现在,我想以相同的方式定义基于未来的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
  }

}

我对理解力的每一行都犯了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))

我该如何解决?它基于“隐式”关键字尝试了不同的语法,但仍然没有成功。

更新1

  

这并不是要给它论据,而是要强迫   查找给定类型的隐式。

一旦我摆脱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])

摆脱在executor中显式传递prepareCappuccinoAsynchroniously也无济于事。 @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
  }

}

3 个答案:

答案 0 :(得分:2)

您是否考虑过不导入使它隐式的全局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

答案 1 :(得分:0)

您无需在executor中的任何地方显式传递prepareCappuccinoAsynchroniously,因为prepareCappuccinoAsynchroniously范围内的隐式参数将比global导入具有更高的优先级。

implicitly方法实际上不是关键字,而是在scala.Predef中定义的实际方法。它是这样实现的:

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

这并不是要为其提供参数,而是要为给定类型强制隐式查找。也就是说,如果您需要T隐式可用,则可以使用val t = implicitly[T]强制使用它。

在这种情况下,您根本不需要使用implicitly,因为您声明了implicit参数,因此已经有了它的名称。此implicitly方法通常与上下文绑定一起使用,上下文绑定是一个密切相关的概念,但概念更高级。您可以根据自己的兴趣查找它,但这对您的问题并不重要。

尝试通过删除所有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 { ??? }

编辑:我看错了您的帖子,您需要使用import scala.concurrent.ExecutionContext.global而不是import scala.concurrent.ExecutionContext.Implicits.global,因为隐式导入global会在此处引起歧义。

答案 2 :(得分:0)

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

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