简体   繁体   中英

What Scala implicit conversion on a Seq is happening here?

I'm reading Debasish Ghosh's new book, "Functional and Reactive Domain Modeling" and I'm really enjoying it.

One thing in Chapter 5 that has me puzzled is how the line below:

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

can take a Seq[Account] and convert it to a Seq[Show]. I know implicits are at play but what steps are the compiler taking to allow this to compile? Is this just a specific instance of a more general implicit rule? It seems like the compiler is mixing the Show trait into the Account objects. Thanks!

Adapted from page 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 _)
  }
}

This is a pretty straight-forward use of the typeclass pattern. All of the "magic" happens in the report function.

First notice the type parameter:

def report[T: Show]

This means that whatever type T is, there must be an implicit Show[T] in scope at the call-site. In Main , the function is called where T is Account , and thus requires an implicit Show[Account] to be in scope at that line. Since Main mixes in DomainShowProtocol , the implicit val showAccount is in scope, so that requirement is satisfied.

Now in the body of report , we see the use of implicitly[Show[T]] . This simply returns a reference to that very Show[T] that was required to be in scope, so in this case it is equal to showAccount .

Lastly, the show method is called on the implicitly returned value, passing in the current element of the Seq as a parameter. This converts each Account into a Try[String] and thus the Seq as a whole.

If we remove all the implicit magic, the method and its invocation look like :

//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 _)

Syntactic sugar

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

is syntactic sugar for

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

roughly you can assume

[T: Show]

does the job of

(implicit evidence: Show[T])

implicitly[Show[T]]

is nothing but the reference of the implicit Show[T]

trait DomainShowProtocol has a implicit evidence Show[Account]

object DomainShowProtocol extends DomainShowProtocol

Now using the object DomainShowProtocol implicit is imported into scope.

report method is able to convert Seq[Account] to Seq[Try[String]] because of the implicit evidence from object DomainShowProtocol which in turn is coming from trait DomainShowProtocol

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

above function is the syntactic sugar for

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

Here T is Account and implicit evidence Show[Account] is comming from the object DomainShowProtocol . Thats how this conversion is possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM