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