【问题标题】:Scala: overridable implicits in for-comprehensionScala:理解中的可覆盖隐式
【发布时间】:2019-01-26 13:15:04
【问题描述】:

我正在尝试通过 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
  }

}

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))

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

更新 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])

明确地摆脱在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
  }

}

【问题讨论】:

  • 为什么不能使用 scala.concurrent.ExecutionContext.Implicit.global ?我的意思是为什么你需要创建自己的 ExecutionContext?据我所知,您可以简单地导入执行上下文,这是最有效的。实际上问题是你在同一个对象中使用了多个相同类型的隐式变量。
  • @RamanMishra,我可以。背后的主要动机 - 允许客户端选择使用哪个执行上下文。要么是默认的,由 API 创建者使用,要么覆盖它。这就是我努力实现的目标。
  • 然后尝试在不同的不同范围内清除上下文,我认为应该可以。现在存在隐式集合,因为您不止一次使用相同类型的隐式。
  • @RamanMishra 使用自定义ExecutionContext 有充分的理由。 global 由 fork-join 线程池支持,这对许多应用程序都有好处,但对于密集型 IO 则相当不利。
  • scala.concurrent.ExecutionContext.global(没有Implicits)替换你导入的全局ec。导入和隐式参数总是会冲突...

标签: scala overriding future for-comprehension implicits


【解决方案1】:

您不需要在prepareCappuccinoAsynchroniously 的任何地方显式传递executor,因为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 会在此处造成歧义。

【讨论】:

  • 在定义中没有执行者,例如研磨,同样的错误。当我将它显式传递为:defgrin(..) = Future {..} (executor) 时,它可以工作,但它也适用于:(implicitly(executor)) 和 (implicitly[ExecutionContext])。但我仍然无法让 prepareCappuccino 异步编译。同样的错误
  • 您是否将import scala.concurrent.ExecutionContext.Implicits.global 替换为import scala.concurrent.ExecutionContext.global。您真的不希望您的默认 ExecutionContext 也被隐式导入。
【解决方案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

}

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

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-23
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多