首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >构成隐式推导的“定律”

构成隐式推导的“定律”
EN

Stack Overflow用户
提问于 2020-03-18 15:45:05
回答 2查看 58关注 0票数 3

我正在设计一个玩具CoversionRate实现,以了解如何通过链式隐式定义编码“规则”。以下代码编译:

代码语言:javascript
复制
case class ConversionRate[X <: Currency, Y <: Currency](rate: Double) extends AnyVal
object ConversionRate {
  implicit val EUR2USD: ConversionRate[EUR.type, USD.type] = ConversionRate(1.1)
  implicit val GBP2USD: ConversionRate[GBP.type, USD.type] = ConversionRate(1.21)

  // "Laws"
  implicit def inverse[X <: Currency, Y <: Currency](implicit cr: ConversionRate[X, Y]): ConversionRate[Y, X] =
    ConversionRate[Y, X](1 / cr.rate)

  implicit def transitivity[X <: Currency, Z <: Currency](implicit cr: ConversionRate[X, USD.type], cr2: ConversionRate[Z, USD.type]): ConversionRate[X, Z] =
    ConversionRate(cr.rate * cr2.rate)

  private val unit = ConversionRate(1)
  implicit def self[X <: Currency]: ConversionRate[X,X] = unit.asInstanceOf
}

然而,调用站点派生不起作用。

代码语言:javascript
复制
val cr = implicitly[ConversionRate[EUR.type, GBP.type]]
代码语言:javascript
复制
diverging implicit expansion for type ConversionRate[EUR.type,GBP.type]
starting with method transitivity in object ConversionRate

我应该如何以一种可以用来推导的方式来写法律呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-03-19 06:21:53

在深入研究它之后,我想我找到了一个更简单的解决方案,使用更低优先级的内嵌而不是无形状的操作符来打破隐式循环。

代码语言:javascript
复制
case class ConversionRate[X <: Currency, Y <: Currency](rate: Double) extends AnyVal
trait ConversionRateLowPriorityImplicits {
  implicit def transitivity[X <: Currency, Z <: Currency](implicit cr: ConversionRate[X, USD.type], cr2: ConversionRate[Z, USD.type]): ConversionRate[X, Z] =
    ConversionRate(cr.rate * cr2.rate)
}
object ConversionRate extends ConversionRateLowPriorityImplicits {
  implicit val EUR2USD: ConversionRate[EUR.type, USD.type] = new ConversionRate(1.1)
  implicit val GBP2USD: ConversionRate[GBP.type, USD.type] = new ConversionRate(1.21)

  // "Laws"
  implicit def inverse[X <: Currency, Y <: Currency](implicit cr: ConversionRate[X, Y]): ConversionRate[Y, X] =
    new ConversionRate[Y, X](1 / cr.rate)

  private val unit = new ConversionRate(1)
  implicit def self[X <: Currency]: ConversionRate[X,X] = unit.asInstanceOf
}

下面的所有内容现在都很好地解决了

代码语言:javascript
复制
implicitly[ConversionRate[EUR.type, EUR.type]]
implicitly[ConversionRate[EUR.type, USD.type]]
implicitly[ConversionRate[EUR.type, GBP.type]]
implicitly[ConversionRate[USD.type, USD.type]]
implicitly[ConversionRate[USD.type, EUR.type]]
implicitly[ConversionRate[USD.type, GBP.type]]
implicitly[ConversionRate[GBP.type, GBP.type]]
implicitly[ConversionRate[GBP.type, EUR.type]]
implicitly[ConversionRate[GBP.type, USD.type]]
票数 0
EN

Stack Overflow用户

发布于 2020-03-18 16:33:53

隐式不能从规则中派生出任何可能的类型,因为它很容易在情况下结束,因此您有无限多的可能性:

代码语言:javascript
复制
id[X]: X => X
x: A => B
y: B => C

可以组成

代码语言:javascript
复制
z = x andThen y

但也许可以用

代码语言:javascript
复制
z = x andThen id[B] andThen y // or
z = id[A] andThen x andThen y // or
z = x andThen y andThen id[C] // or
z = id[A] andThen x andThen y andThen id[C] //
...

你看这是怎么回事?

同时,派生应该是明确的,当您从您的“原语”开始,并试图将它们组合到您的“目标”隐式中时,应该只有一种可能的方法。一旦Scala发现有一些歧义,它就停止派生。

在您的示例中,您正在将ConversionRate[X, USD]ConversionRate[Y, USD]组合成ConversionRate[X, Y]。但这并不能阻止你做这样的事情:

代码语言:javascript
复制
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with...

这样做的一种方法是以这样一种方式定义您的转换,即应该只有一种方法,即您不能轻易地破坏,例如:

代码语言:javascript
复制
case class USDConversionRate[A <: Currency](rate: Double)
// implicit conversion rates

case class ConversionRate[X <: Currency, Y <: Currency](rate: Double) extends AnyVal
object ConversionRate {
  implicit def combine[X <: Currency, Y <: Currency](
    implicit
    ev1: X =:!= USD.type, // available in shapeless
    ev2: Y =:!= USD.type, // proves type inequality
    ev3: X =:!= Y,        // which we use to force only one way to derive each pair
    xFromUSD: USDConversionRate[X],
    yFromUSD: USDConversionRate[Y],
  ): ConversionRate[X, Y] = ...

  implicit def toUSD[X <: Currency](
    implicit X =:!= USD.type,
    xFromUSD: USDConversionRate[X]
  ): ConversionRate[X, USD.type] = ...

  implicit def fromUSD[X <: Currency](
    implicit X =:!= USD.type,
    xFromUSD: USDConversionRate[X]
  ): ConversionRate[USD.type, X] = ...

  implicit def unit: ConversionRate[X, X] = ...
}

在这里,您将使用类型界来剪切所有循环。在原始代码中,如果使用inverse (因为任何派生出X -> Y,与inverse(inverse(X -> Y))冲突的东西)等,以及traverseunit等,它们就会出现。您必须保证扩展不会发散,任何可能导致循环的东西,或者任何其他两种不同的方法都是禁止的。

最大的问题是,对于像这样的两个参数组合:A -> B,您总是可以尝试通过其他A -> C -> BA -> D -> B来完成它,所以最好的方法是消除任何通过一些额外步骤的可能性。我的建议是,您得到的A -> B转换率仅从美元-> X转换利率,并防止每次转换与另一个转换冲突,完全不允许循环。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60742961

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档