【问题标题】:What Scala implicit conversion on a Seq is happening here?Seq 上的 Scala 隐式转换在这里发生了什么?
【发布时间】:2016-11-02 19:09:03
【问题描述】:

我正在阅读 Debasish Ghosh 的新书《函数式和反应式域建模》,我真的很喜欢它。

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

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

可以获取 Seq[Account] 并将其转换为 Seq[Show]。我知道隐式在起作用,但是编译器采取了哪些步骤来允许它编译?这只是更一般的隐含规则的一个特定实例吗? 似乎编译器正在将 Show trait 混合到 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 _)
  }
}

【问题讨论】:

    标签: scala implicit-conversion


    【解决方案1】:

    这是 typeclass 模式的一种非常直接的用法。所有的“魔法”都发生在report 函数中。

    首先注意类型参数:

    def report[T: Show]
    

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

    现在在report 的正文中,我们看到了implicitly[Show[T]] 的使用。这只是返回对需要在范围内的 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 _)
    

    【讨论】:

      【解决方案2】:

      语法糖

      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。这就是这种转换是可能的。

      【讨论】:

      • 感谢您的编辑。他们真的帮助我得到了正在进行的句法糖化,你对证据所做的笔记对我来说是全新的,所以感谢“啊哈!”时刻!
      猜你喜欢
      • 2015-06-30
      • 1970-01-01
      • 2013-09-28
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 2013-11-03
      • 1970-01-01
      相关资源
      最近更新 更多