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.