这里发生了什么 Scala 对 Seq 的隐式转换?

What Scala implicit conversion on a Seq is happening here?

我正在读 Debasish Ghosh 的新书,"Functional and Reactive Domain Modeling",我真的很喜欢。

第 5 章中让我感到困惑的一件事是下面的行:

Reporting.report(accts).foreach(println _)

可以使用 Seq[Account] 并将其转换为 Seq[Show]。我知道隐含函数在起作用,但编译器采取了哪些步骤来允许它编译?这只是更一般的隐含规则的一个具体实例吗? 似乎 编译器正在将 Show 特征混合到 Account 对象中。谢谢!

改编自第 164 页:

import scala.util.Try

trait Show[T] {
  def shows(t: T): Try[String]
}

trait ShowProtocol {
  implicit val showAccount: Show[Account]
  implicit val showCustomer: Show[Customer]
}

trait DomainShowProtocol extends ShowProtocol {
  override implicit val showAccount: Show[Account] = (t: Account) => Try("Account")
  override implicit val showCustomer: Show[Customer] = (t: Customer) => Try("Customer")
}

case class Account()
case class Customer()

object Reporting {
  def report[T: Show](as: Seq[T]): Seq[Try[String]] = as.map(implicitly[Show[T]].shows _)
}

object DomainShowProtocol extends DomainShowProtocol

object Main {
  def main(args: Array[String]): Unit = {
    import DomainShowProtocol._

    val accts: Seq[Account] = Seq(
      Account(),
      Account(),
      Account()
    )

    Reporting.report(accts).foreach(println _)
  }
}

语法糖

def report[T: Show](seq: Seq[T]) 

的语法糖
def report(seq: Seq[T])(implicit evidence: Show[T])

你可以大致假设

[T: Show]

的工作
(implicit evidence: Show[T])

implicitly[Show[T]]

不过是隐式 Show[T]

的引用

特质DomainShowProtocol有隐含证据Show[Account]

object DomainShowProtocol extends DomainShowProtocol

现在使用对象 DomainShowProtocol 隐式导入范围。

report 方法能够将 Seq[Account] 转换为 Seq[Try[String]],因为来自对象 DomainShowProtocolimplicit 证据又来自特征 DomainShowProtocol

def report[T: Show](as: Seq[T]): Seq[Try[String]] = as.map(implicitly[Show[T]].shows _)

以上函数是

的语法糖
def report(as: Seq[T])(implicit evidence: Show[T]): Seq[Try[String]] = as.map(evidence.shows _)

此处 TAccount,隐含证据 Show[Account] 来自对象 DomainShowProtocol。这就是这种转换的可能方式。

这是对类型类模式的一种非常直接的使用。所有 "magic" 都发生在 report 函数中。

首先注意类型参数:

def report[T: Show]

这意味着无论 T 是什么类型,在调用站点的范围内都必须有一个隐式的 Show[T]。在 Main 中,函数被调用,其中 TAccount,因此需要隐式 Show[Account] 在该行的范围内。由于 Main 混合在 DomainShowProtocol 中,隐式 val showAccount 在范围内,因此满足该要求。

现在在 report 的正文中,我们看到了 implicitly[Show[T]] 的用法。这只是 returns 对需要在范围内的 Show[T] 的引用,因此在这种情况下它等于 showAccount.

最后,对隐式返回值调用 show 方法,将 Seq 的当前元素作为参数传入。这会将每个 Account 转换为 Try[String],从而将 Seq 作为一个整体。

如果我们删除所有隐含的魔法,方法及其调用如下所示:

//in Reporting
def report[T](as: Seq[T])(show: Show[T]): Seq[Try[String]] = {
  as.map{t => show.shows(t)}
}

//in Main
Reporting.report(accts)(accountsShow).foreach(println _)