编写隐式推导 "Laws"
Composing Implicit Derivation "Laws"
我正在尝试设计一个玩具 CoversionRate
实现以了解如何通过链式隐式定义对 "laws" 进行编码。以下代码编译:
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
}
调用站点推导确实不工作
val cr = implicitly[ConversionRate[EUR.type, GBP.type]]
diverging implicit expansion for type ConversionRate[EUR.type,GBP.type]
starting with method transitivity in object ConversionRate
我应该如何以可用于推导的方式编写定律?
隐式无法派生出您可以从规则中组合的任何可能类型,因为它很容易在这样的情况下结束,那么您有无限多的可能性:
id[X]: X => X
x: A => B
y: B => C
可以组成
z = x andThen y
但也许可以用
生成
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] //
...
你知道这是怎么回事吗?
同时推导应该是明确的,当您从 "primitives" 隐式开始并尝试将它们组合到您的 "target" 隐式中时,应该只有一种可能的方法。当 Scala 发现有歧义时,它就停止推导。
在您的示例中,您将 ConversionRate[X, USD]
与 ConversionRate[Y, USD]
组合成 ConversionRate[X, Y]
。但它不会阻止你做类似的事情:
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with...
实现此目的的一种方法是以这样一种方式定义您的转换,即应该只有一种方法,您不能轻易破坏,例如:
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))
冲突),等等,与 traverse
和 unit
等相同。你必须保证扩展不能发散,任何会导致循环的事情,或者任何其他两种不同的方式来获得相同的类型都是被禁止的。
你最大的问题是,使用这样的 2 个参数组合:A -> B
,你总是可以尝试通过其他方式来实现它 A -> C -> B
、A -> D -> B
,所以最好的方法是消除任何通过一些额外步骤的可能性。我的提议是,您仅从 USD -> X 转化率得出 A -> B 转化率,并防止每次转化与另一次转化发生冲突,完全不允许循环。
仔细研究之后,我想我找到了一个更简单的解决方案,即使用优先级较低的隐式而不是无形运算符来打破隐式循环。
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
}
以下所有问题现在都很好地解决了
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]]
我正在尝试设计一个玩具 CoversionRate
实现以了解如何通过链式隐式定义对 "laws" 进行编码。以下代码编译:
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
}
调用站点推导确实不工作
val cr = implicitly[ConversionRate[EUR.type, GBP.type]]
diverging implicit expansion for type ConversionRate[EUR.type,GBP.type]
starting with method transitivity in object ConversionRate
我应该如何以可用于推导的方式编写定律?
隐式无法派生出您可以从规则中组合的任何可能类型,因为它很容易在这样的情况下结束,那么您有无限多的可能性:
id[X]: X => X
x: A => B
y: B => C
可以组成
z = x andThen y
但也许可以用
生成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] //
...
你知道这是怎么回事吗?
同时推导应该是明确的,当您从 "primitives" 隐式开始并尝试将它们组合到您的 "target" 隐式中时,应该只有一种可能的方法。当 Scala 发现有歧义时,它就停止推导。
在您的示例中,您将 ConversionRate[X, USD]
与 ConversionRate[Y, USD]
组合成 ConversionRate[X, Y]
。但它不会阻止你做类似的事情:
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with
ConversionRate[USD, USD] combine with...
实现此目的的一种方法是以这样一种方式定义您的转换,即应该只有一种方法,您不能轻易破坏,例如:
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))
冲突),等等,与 traverse
和 unit
等相同。你必须保证扩展不能发散,任何会导致循环的事情,或者任何其他两种不同的方式来获得相同的类型都是被禁止的。
你最大的问题是,使用这样的 2 个参数组合:A -> B
,你总是可以尝试通过其他方式来实现它 A -> C -> B
、A -> D -> B
,所以最好的方法是消除任何通过一些额外步骤的可能性。我的提议是,您仅从 USD -> X 转化率得出 A -> B 转化率,并防止每次转化与另一次转化发生冲突,完全不允许循环。
仔细研究之后,我想我找到了一个更简单的解决方案,即使用优先级较低的隐式而不是无形运算符来打破隐式循环。
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
}
以下所有问题现在都很好地解决了
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]]