Составление неявного деривации "L aws" - PullRequest
3 голосов
/ 18 марта 2020

Я пытаюсь разработать игрушечную реализацию CoversionRate, чтобы понять, как я могу кодировать «l aws» с помощью неявных определений. Компилируется следующий код:

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

Как мне go написать написание aws таким образом, что они могут использовать для деривации?

Ответы [ 2 ]

3 голосов
/ 18 марта 2020

Неявный не может получить любой возможный тип, который вы могли бы составить из ваших правил, потому что он легко может оказаться в ситуации, тогда у вас будет бесконечное количество возможностей:

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] //
...

Вы видите, к чему это идет?

Между тем, вывод должен быть однозначным, когда вы начинаете со своими примитивами и вы пытаетесь объединить их в свою «целевую» неявную, должен быть только один возможный путь. В тот момент, когда 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...

Одним из способов сделать это будет определение ваших конверсий таким образом, что должен быть только один способ сделать это, что вы не можете легко break, например:

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))), et c, то же самое с traverse и unit, et c. Вы должны гарантировать, что расширение не может расходиться, и что-либо, что может привести к циклу или любым другим двум различным способам получения одного и того же типа, запрещено.

Ваша самая большая проблема заключается в том, что с составом из двух параметров, подобным этому: A -> B, вы всегда можете попытаться сделать это через что-то еще A -> C -> B, A -> D -> B, поэтому лучший способ - это исключить любую возможность выполнения некоторых дополнительных шагов. Мое предложение заключается в том, что вы выводите коэффициент конвертации A -> B только из курсов разговоров в долларах США -> X и предотвращаете каждое преобразование в cla sh с другим преобразованием, полностью запрещая циклы.

0 голосов
/ 19 марта 2020

Посмотрев немного подробнее, я думаю, что нашел еще более простое решение, использующее имплициты с более низким приоритетом вместо бесформенных операторов для разрыва неявных циклов.

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
}

Все следующее теперь разрешается красиво 1004 *

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]]
...