使用基于类型类的多态性时避免样板
Avoiding boilerplate when using typeclass-based polymorphism
我发现我的代码经常看起来有点像这样:
trait Example {
def getThing1[A, O <: HList](a: A)(implicit g1: GetThing1[A] { type Out = O }): O = g1(a)
def getThing2[A, O <: HList](a: A)(implicit g2: GetThing2[A] { type Out = O }): O = g2(a)
def combineThings[T1 <: HList, T2 <: HList, O <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2] {type Out = O},
): O = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(combineThings(getThing1(a), getThing2(a)))
}
这实际上比仅使用隐式且不调用 getThing1
、getThing2
或 combineThings
方法的独立 getCombinedReversed
方法更复杂:
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(c(g1(a), g2(a)))
我对此没有特别的问题,但它确实使我的代码膨胀了一点,所以我想我会检查一下是否没有明显的解决方案。显然调用 getThing
和 combineThings
方法而不断言正确的隐含在范围内是不可能的。
感谢您的帮助。
在方法的隐式参数中,您可以更喜欢 Aux
类型而不是类型细化(您可以 automize 使用 AUXify 的宏注释生成 Aux
类型)。同样在 return 类型的方法中,您可以更喜欢依赖于路径的类型而不是附加类型参数(待推断)。
def getThing1[A](a: A)(implicit g1: GetThing1[A]): g1.Out = g1(a)
def getThing2[A](a: A)(implicit g2: GetThing2[A]): g2.Out = g2(a)
def combineThings[T1 <: HList, T2 <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2]
): c.Out = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(combineThings(getThing1(a), getThing2(a)))
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(c(g1(a), g2(a)))
此外,关于重复隐式参数的必要性,请阅读
一般来说,按照您描述的方式编写代码似乎很传统。隐式参数有助于理解方法的逻辑(这当然需要一些技巧)。如果您开始隐藏隐式参数,那么您的代码可能会开始看起来不那么传统:) 如果您多次重复同一组隐式参数,这是引入新类型的信号 class.
import com.github.dmytromitin.auxify.macros.{aux, instance}
import shapeless.DepFn1
@aux @instance
trait GetCombinedReversed[A] extends DepFn1[A] {
type Out
def apply(a: A): Out
}
object GetCombinedReversed {
implicit def mkGetCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): Aux[A, r.Out] = instance(a => r(c(g1(a), g2(a))))
}
def foo1[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
f(..., gcr(a), ...)
def foo2[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
g(..., gcr(a), ...)
在 Scala 3 中你可以这样写
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(using
g1: GetThing1[A],
g2: GetThing2[A],
c: CombineThings[g1.Out, g2.Out],
r: Reverse[c.Out]
): r.Out = ???
因此类型改进或 Aux
-类型变得越来越必要,尽管有时它们仍然是必要的。我将从 :
复制我的评论
def foo(using tc1: TC1[tc2.Out], tc2: TC2[tc1.Out]) = ???
doesn't compile while
def bar[A, B](using tc1: TC1.Aux[A, B], tc2: TC2.Aux[B, A]) = ???
and
def baz[A](using tc1: TC1[A], tc2: TC2.Aux[tc1.Out, A]) = ???
do.
我发现我的代码经常看起来有点像这样:
trait Example {
def getThing1[A, O <: HList](a: A)(implicit g1: GetThing1[A] { type Out = O }): O = g1(a)
def getThing2[A, O <: HList](a: A)(implicit g2: GetThing2[A] { type Out = O }): O = g2(a)
def combineThings[T1 <: HList, T2 <: HList, O <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2] {type Out = O},
): O = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(combineThings(getThing1(a), getThing2(a)))
}
这实际上比仅使用隐式且不调用 getThing1
、getThing2
或 combineThings
方法的独立 getCombinedReversed
方法更复杂:
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(c(g1(a), g2(a)))
我对此没有特别的问题,但它确实使我的代码膨胀了一点,所以我想我会检查一下是否没有明显的解决方案。显然调用 getThing
和 combineThings
方法而不断言正确的隐含在范围内是不可能的。
感谢您的帮助。
在方法的隐式参数中,您可以更喜欢 Aux
类型而不是类型细化(您可以 automize 使用 AUXify 的宏注释生成 Aux
类型)。同样在 return 类型的方法中,您可以更喜欢依赖于路径的类型而不是附加类型参数(待推断)。
def getThing1[A](a: A)(implicit g1: GetThing1[A]): g1.Out = g1(a)
def getThing2[A](a: A)(implicit g2: GetThing2[A]): g2.Out = g2(a)
def combineThings[T1 <: HList, T2 <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2]
): c.Out = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(combineThings(getThing1(a), getThing2(a)))
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(c(g1(a), g2(a)))
此外,关于重复隐式参数的必要性,请阅读
一般来说,按照您描述的方式编写代码似乎很传统。隐式参数有助于理解方法的逻辑(这当然需要一些技巧)。如果您开始隐藏隐式参数,那么您的代码可能会开始看起来不那么传统:) 如果您多次重复同一组隐式参数,这是引入新类型的信号 class.
import com.github.dmytromitin.auxify.macros.{aux, instance}
import shapeless.DepFn1
@aux @instance
trait GetCombinedReversed[A] extends DepFn1[A] {
type Out
def apply(a: A): Out
}
object GetCombinedReversed {
implicit def mkGetCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): Aux[A, r.Out] = instance(a => r(c(g1(a), g2(a))))
}
def foo1[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
f(..., gcr(a), ...)
def foo2[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
g(..., gcr(a), ...)
在 Scala 3 中你可以这样写
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(using
g1: GetThing1[A],
g2: GetThing2[A],
c: CombineThings[g1.Out, g2.Out],
r: Reverse[c.Out]
): r.Out = ???
因此类型改进或 Aux
-类型变得越来越必要,尽管有时它们仍然是必要的。我将从
def foo(using tc1: TC1[tc2.Out], tc2: TC2[tc1.Out]) = ???
doesn't compile while
def bar[A, B](using tc1: TC1.Aux[A, B], tc2: TC2.Aux[B, A]) = ???
and
def baz[A](using tc1: TC1[A], tc2: TC2.Aux[tc1.Out, A]) = ???
do.