有没有一种方法可以从Scala Shapeless派生HList的每个成员的隐式?

时间:2020-04-04 22:43:48

标签: scala implicit shapeless

我尝试了以下操作:

type Params = String :: Int :: HNil

implicit val params: Params = "hello" :: 5 :: HNil

// Supposed to create an implicit for string and int if needed
implicit def meberImplicit[A](
  implicit 
  params: Params,
  selector: Selector[Params, A]
): A = params.select[A]

// Summoning a string
implicitly[String]    // compile-time error

但是,我得到了一个发散的隐式错误:

diverging implicit expansion for type String

我在这里想念什么吗?也许已经有一种内置的或更好的方法可以实现这一目标?

1 个答案:

答案 0 :(得分:1)

问题在于您太普通了

implicit def memberImplicit[A](
  implicit // what you put here is irrelevant
): A = ...

基本上,您为任何值提供了隐式。这与您定义的任何其他隐式参数以及需要获取的任何隐式参数都存在冲突。

但是让我们问一个问题,为什么编译器不能证明您无法提供在坏情况下传递给memberImplicit的隐式函数,因此它不会被认为是可行的替代方法,所以它可以证明应当减少这一派生分支(在您不打算使用的地方),解决歧义,然后结块。

结果是,您要返回的类型为A。这意味着即使您在其中添加了一些约束,例如A =:!= Params-在正常情况下会起作用...您只提供了所有这些隐式内容,因此类型约束停止起作用,并突然衍生出诸如Selector[Params, String]具有多种实例化方式。在这种情况下,您尝试尝试的任何实现-只要它返回A-都会失败。

为了使事情正常进行,您必须将输出约束为不能匹配所有内容的东西-事实上,匹配越少越好。例如,创建一个单独的类型类以从HList中提取值:

trait Extractable[A] { def extract(): A }
object Extractable {
  implicit def extractHList[H <: HList, A](
    implicit
    h: H,
    selector: Selector[H, A]
  ): Extractable[A] = () => selector(h)
}

def extract[A](implicit extractable: Extractable[A]): A = extractable.extract()

然后

extract[String] // "hello"