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

问题描述

我有以下类型定义:

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 }

鉴于在运行时提供了一个内容值,我想返回以其相应包装类型包装的内容。我使用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]。如何利用隐式分辨率对事物进行概括,以便可以为给定的内容派生正确的包装器?

我当时想在无形书的random number generator部分中使用相同的派生技术生成包装器的实例(即,如果我有一个隐式{{1} }),但首先我什至找不到正确的包装器类型。

解决方法

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

尝试输入类型

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密封,则可以派生类型类

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