根据抽象类型成员搜索类型

Search for types based on their abstract type members

我有以下类型定义:

trait Content
trait Wrapper {
  type ContentType 
}

final case class Foo(param: String) extends Content
final case class Bar(param: String) extends Content

final case class FooWrapper(foo: Foo) extends Wrapper { type ContentType = Foo }
final case class BarWrapper(bar: Bar) extends Wrapper { type ContentType = Bar }

在运行时给定一个内容值,我想 return 将其包装在相应的包装器类型中。我使用 Shapeless 尝试了以下操作:

def fetchWrapper[N, G <: Wrapper](
    implicit
    gen: Generic.Aux[G, N :: HNil],

    // this also compiles, as an alternative to Generics.Aux
    // =:= :G#ValueType =:= N
) = ...

它有效,但前提是我明确提供类型参数:fetchWrapper[Foo, FooWrapper]。我如何利用隐式解析来概括事物,以便我可以为给定内容派生出正确的包装器?

我正在考虑在 shapeless book 的 random number generator 部分使用相同的推导技术生成包装器的实例(即如果我有一个隐式 Bar :: HNil 在范围内),但我一开始甚至找不到正确的包装器类型。

Generic 可以轻松帮助将 Wrapper 子类型转换为 Content 子类型,但反之亦然。

试试类型class

trait ContentToWrapper[C <: Content] {
  type Out <: Wrapper { type ContentType = C }
}
object ContentToWrapper {
  implicit val foo: ContentToWrapper[Foo] { type Out = FooWrapper } = null
  implicit val bar: ContentToWrapper[Bar] { type Out = BarWrapper } = null
}

def fetchWrapper[C <: Content](implicit ctw: ContentToWrapper[C]): ctw.Out = ???

如果你使 Wrapper 密封你可以导出类型 class

import shapeless.{Coproduct, Generic, HList, Poly1, poly}
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.CollectFirst

object ContentToWrapper {
  implicit def mkContentToWrapper[C <: Content, WC <: Coproduct, WL <: HList](implicit
    generic: Generic.Aux[Wrapper, WC],
    toHList: ToHList.Aux[WC, WL], // there is CollectFirst for HList but not for Coproduct
    collect: CollectFirst[WL, WrapperSubtypePoly[C]]
  ): ContentToWrapper[C] { type Out = collect.Out } = null

  trait WrapperSubtypePoly[C] extends Poly1
  object WrapperSubtypePoly {
    implicit def cse[C, A <: Wrapper { type ContentType = C }]: 
      poly.Case1.Aux[WrapperSubtypePoly[C], A, A] = poly.Case1(identity)
  }
}

测试:

val w1 = fetchWrapper[Foo]
w1: FooWrapper
val w2 = fetchWrapper[Bar]
w2: BarWrapper