Scala 不成形的';惰性参数和默认参数会导致隐式解析失败

Scala 不成形的';惰性参数和默认参数会导致隐式解析失败,scala,shapeless,Scala,Shapeless,我的一个项目混合使用了一些scala功能,这些功能似乎不能很好地结合在一起: 类型类和无形状的自动类型类实例派生 隐式转换(向具有类型类实例的类型添加有用的语法) 默认参数,因为尽管它们通常是一件坏事,但它们在这里太方便了 我遇到的问题是类型类实例派生失败,如果: 默认参数没有明确指定 无形状派生使用Lazy 以下是我为重现问题所能编写的尽可能少的代码量: 斯卡拉 编译失败,错误消息如下: Run.scala:10: could not find implicit value for p

我的一个项目混合使用了一些scala功能,这些功能似乎不能很好地结合在一起:

  • 类型类和无形状的自动类型类实例派生
  • 隐式转换(向具有类型类实例的类型添加有用的语法)
  • 默认参数,因为尽管它们通常是一件坏事,但它们在这里太方便了
我遇到的问题是类型类实例派生失败,如果:

  • 默认参数没有明确指定
  • 无形状派生使用
    Lazy
以下是我为重现问题所能编写的尽可能少的代码量:

斯卡拉 编译失败,错误消息如下:

Run.scala:10: could not find implicit value for parameter sa: Show[Run.Foo]
[error]   println(Foo(12).show())
编译错误通过以下任一方法修复:

  • 标题
    参数显式传递给
    Run.scala中的
    show
  • Show.scala

我必须承认我在这里完全不知所措。我很想了解会发生什么,如果有解决办法,我也很想知道。

简短回答:

如果将上下文绑定到隐式类,它也可以正常工作。要做到这一点,您必须牺牲value类,但我认为事先告诉编译器只有具有
Show
A
s才能通过它得到丰富也是更为简洁的:

implicit class Show2Ops[A : Show](a: A) {
  def show2(header: String = "> ") = header + implicitly[Show[A]].show(a)
}

println(Foo(12).show2())
长理论:

Lazy
做了一些有趣的把戏,很难遵循。你没有特别问过Lazy在做什么,但我很好奇,因为我一直在使用它,但不知道它是如何工作的。所以我看了一下。据我所知,事情是这样的

您有一个带有递归字段的case类:

case class A(first: Int, next: Option[A])
假设您在
Show
选项的同伴中有另一个案例:

implicit def opt[A](implicit showA: Show[A]): Show[Option[A]] = Show.from {
  case Some(a) => s"Some(${showA.show(a)})"
  case None => "None"
}
而不是
singletonShow
你有一个真实的
HNil
案例和一个归纳案例,这是典型的:

implicit val hnil: Show[HNil] = Show.from(_ => "")
implicit def hcons[H, T <: HList](implicit
  showH: Show[H],
  showT: Show[T]
): Show[H :: T] = Show.from {
  case h :: t => showH(h) + ", " + showT(t) // for example
}
implicit val-hnil:Show[hnil]=Show.from(=>“”)
隐式def-hcons[H,T-showH(H)+,“+showT(T)//例如
}
让我们将
singletonCaseClassShow
重命名为
genericShow
,因为它不再只适用于单身人士

现在让我们假设您在
genericShow
中没有
Lazy
。当您尝试调用
Show[a]
时,编译器会转到:

  • genericShow[A]
    打开隐式搜索
    Show[A]
  • hcons[Int::Option[A]::HNil]
    打开隐式搜索
    Show[A]
    Show[Int::Option[A]::HNil
  • intShow
    打开隐式搜索
    Show[A]
    Show[Int]
    Show[Option[A]::HNil]
  • hcons[Option[A]::HNil]
    打开隐式搜索
    Show[A]
    Show[Option[A]::HNil]
  • opt[A]
    打开隐式搜索
    Show[A]
    Show[Option[A]]
    Show[Option[A]::HNil]
  • genericShow[A]
    打开隐式搜索
    Show[A]
    Show[Option[A]
    Show[Option[A]::HNil]
  • 现在很明显,这是一个问题,因为它将回到第二阶段,然后再次发生,从未取得任何进展

    Lazy
    克服这一问题的方法是在编译器试图具体化它的隐式实例时进入宏。因此,当您在
    hcons
    中使用
    implicit Show:Lazy[Show[H]]
    而不仅仅是
    Show[H]
    时,编译器会转到该宏来查找
    Lazy[Show[H]]
    而不是停留在隐式的
    展示
    案例中

    宏检查打开的隐式(宏有权访问该隐式),并进入其自己的隐式解析算法,该算法在继续查找
    T
    (对于
    Lazy[T]
    )的隐式实例之前始终完全解析打开的隐式。如果要解析一个已经打开的隐式,它将替换一个虚拟树(本质上是告诉编译器“我得到了这个,不要担心它”),该树跟踪打结的依赖项,以便解析的其余部分可以完成。最后,它将清理虚拟树(我不太明白它是如何工作的;那里的代码数量惊人,而且相当复杂!)

    那么为什么
    Lazy
    似乎会把默认的参数设置搞砸呢?我认为这是几个因素的综合(只是一个假设):

  • 使用原始的
    ShowOps
    ,对值调用
    .show
    会导致将其隐式包装在
    ShowOps[a]中
    。A将是什么?它将是
    Foo
    AnyRef
    Any
    ?它将是一个唯一的单一类型吗?它并不完全清楚,因为那时对
    A
    没有约束,Scala也不知道对
    的调用实际上会约束它(由于上下文的限制)
  • 如果没有
    Lazy
    ,这就行了,因为如果Scala选择了错误的
    A
    ,并且
    .show
    没有进行打字检查,它将意识到自己的错误并退出它选择的
    A
  • 由于
    Lazy
    ,还有一系列其他的逻辑在进行,这有点欺骗Scala,使其认为无论它选择了什么
    a
    ,都是好的。但是,当到了结束循环的时候,它就不起作用了,到那时,退出已经太晚了
  • 不知何故,未指定的默认参数会影响Scala在
    ShowOps[A]
    中选择
    A
    的初始选择

  • 我知道我不应该只是评论说
    implicit def opt[A](implicit showA: Show[A]): Show[Option[A]] = Show.from {
      case Some(a) => s"Some(${showA.show(a)})"
      case None => "None"
    }
    
    implicit val hnil: Show[HNil] = Show.from(_ => "")
    implicit def hcons[H, T <: HList](implicit
      showH: Show[H],
      showT: Show[T]
    ): Show[H :: T] = Show.from {
      case h :: t => showH(h) + ", " + showT(t) // for example
    }