Syntax and meaning of a Scala/Play! code sample

2020-02-10 09:16发布

问题:

I have the following Scala/Play! code:

case class Tweet(from: String, text: String)

implicit val tweetReads = (
 (JsPath \ "from_user_name").read[String] ~
 (JsPath \ "text").read[String]) (Tweet.apply _)

I have several questions regarding the syntax and meaning of the above code:

  • On what class/object is the ~ method invoked on?
  • What is the class/type of the argument to Tweet.apply?

edit 1: full source code:

package models

import play.api.libs.json._
import play.api.libs.json.util._
import play.api.libs.json.Reads._
import play.api.libs.json.Writes._
import play.api.libs.functional.syntax._

case class Tweet(from: String, text: String)

object Tweet {

  implicit val tweetReads = (
      (JsPath \ "from_user_name").read[String] ~
      (JsPath \ "text").read[String])(Tweet.apply _)

  implicit val tweetWrites = (
    (JsPath \ "from").write[String] ~
    (JsPath \ "text").write[String])(unlift(Tweet.unapply))
}

回答1:

Step by step:

Method \ on object JsPath

val path1: JsPath = JsPath \ "from_user_name"
val path2: JsPath = JsPath \ "text"

Method read on object of type JsPath

val reads1: Reads[String] = path1.read[String]
val reads2: Reads[String] = path2.read[String]

There is no method ~ in Reads, but there is such method in FunctionalBuilderOps and there is an implicit conversion from M[T] to FunctionalBuilderOps[M[_], T] in play.api.libs.functional.syntax - toFunctionalBuilderOps.

val reads1FunctionalBuilderOps: FunctionalBuilderOps[Reads, String] =
  toFunctionalBuilderOps(reads1)

val canBuild2: CanBuild2[String, String] = reads1FunctionalBuilderOps.~(reads2)

Tweet.apply _ is a scala syntax to create a FunctionN using method with N arguments:

val func: (String, String) => Tweet = Tweet.apply _

There is an apply method in CanBuild2[A, B]. It accepts (A, B) => C and returns Reads[C] (in this case):

implicit val tweetReads: Reads[Tweet] = canBuild2.apply(func)

Actually there are also implicit parameters in JsPath#read, toFunctionalBuilderOps and CanBuild2#apply methods. With that parameters:

val reads1: Reads[String] = path1.read[String](Reads.StringReads)

...
val reads1FunctionalBuilderOps: FunctionalBuilderOps[Reads, String] =
  toFunctionalBuilderOps(reads1)(
    functionalCanBuildApplicative(
      Reads.applicative(JsResult.applicativeJsResult)))

...
implicit val tweetReads: Reads[Tweet] =
  canBuild2.apply(func)(functorReads)


回答2:

I would just add that this is called applicative syntax (for applicative functors). It can be seen for example in scala parser combinator library

If you want to know how to use it with json try to read this. Another example of applicative functors in play framework is form handling. Or you can try to read about scalaz Validation.