日本人で一番play2にコミットしてるはず?
(すでにHaskell界隈やScalaz界隈ではオワコン)
Haskell界隈の事情(Iterateeはオワコンらしいが、それ以外で乱立)
以下の様なことを話したいが、詰め込み過ぎたので、全部丁寧に話せるか謎
Applicative
と Monad
Reads
や Writes
などが、なぜあのような書き方をするのか?
これから話すことは、どのversionでもそれほど大きく変わってませんが、特に言及がない場合は、play2.2.3とします
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
(playの公式ドキュメントより引用)
これを理解できるように
implicit val locationReads: Reads[Location] = (
(__ \ "lat").read[Double](min(-90.0) keepAnd max(90.0)) and
(__ \ "long").read[Double](min(-180.0) keepAnd max(180.0))
)(Location.apply _)
implicit val residentReads: Reads[Resident] = (
(__ \ "name").read[String](minLength[String](2)) and
(__ \ "age").read[Int](min(0) keepAnd max(150)) and
(__ \ "role").readNullable[String]
)(Resident.apply _)
implicit val placeReads: Reads[Place] = (
(__ \ "name").read[String](minLength[String](2)) and
(__ \ "location").read[Location] and
(__ \ "residents").read[Seq[Resident]]
)(Place.apply _)
この2つの違い
implicit val placeReads: Reads[Place] = (
(__ \ "name").read[String](minLength[String](2)) and
(__ \ "location").read[Location] and
(__ \ "residents").read[Seq[Resident]]
)(Place.apply _)
implicit val placeReads: Reads[Place] = for{
name <- (__ \ "name").read[String](minLength[String](2))
location <- (__ \ "location").read[Location]
residents <- (__ \ "residents").read[Seq[Resident]]
} yield Place(name, location, residents)
for式 = モナド = カッコイイ!
と思ってる人はまだScala初心者(?)
あんな気持ち悪い(?)書き方をしているのは理由がある
Monoid, Reducer, Functor, InvariantFunctor, ContravariantFunctor, Applicative, Alternative, Reads, Writes, OWrites, Format, OFormat, Writeable, ContentTypeOf, Formetter, QueryStringBindable, PathBindable, JavascriptLitteral
Play内部には、意外と型クラスがいっぱいある
勝手に3種類くらいに分類
Monoid, Reducer, Functor, InvariantFunctor, ContravariantFunctor, Applicative, Alternative
その他
Writeable, ContentTypeOf, Formetter, QueryStringBindable, PathBindable, JavascriptLitteral
Writeable
やContentTypeOf
は、普通にやるぶんには、そこまで意識する必要ない
Haskellで表現(1)
-- ちょっと省略してるので不正確
data JsResult a = JsSuccess a | JsError
class Writes a where
writes :: a -> JsValue
class Writes a => OWrites a where
writes :: a -> JsObject -- Haskellでは本当はオーバーライドは不可能
Haskellで表現(2)
class Reads a where
reads :: JsValue -> JsResult a
class (Writes a, Reads a) => Format a where
class (OWrites a, Reads a) => OFormat a where
Haskellで表現(3)
-- https://github.com/ekmett/contravariant/blob/v0.5.2/Data/Functor/Contravariant.hs#L76
class ContravariantFunctor a where
contramap :: (a -> b) -> f b -> f a
Haskellで表現(4)
instance Alternative JsResult where
-- 実装は略
instance Alternative Reads where
-- a -> f b において、fがAlternativeなら
-- 自動的にa -> f bもAlternative
instance ContravariantFunctor Writes where
-- Writesは実質単なる a -> JsValue という関数と同型なので
-- ContravariantFunctorになるのは当たり前
Scalaでの型クラス
trait
やclass
で定義
型クラスのインスタンス定義方法は、以下のどれか
implicit val
(lazy
付く場合も)
implicit def
(引数ある場合とない場合と両方あり得る)
implicit object
implicit parameter
で受け取る
playでの具体例
型クラスのインスタンス定義
implicit object StringReads extends Reads[String] {
def reads(json: JsValue) = json match {
case JsString(s) => JsSuccess(s)
case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsstring"))))
}
}
なぜ型クラスを受け渡すのにimplicit
を使うのか?
(むしろ、そうでなければ型クラスと呼ばない?)
“型によって、値が一つに決まる”
ということは、
implicitly
や Context Bound)
“型によって、値が一つに決まる”
ということは、型パラメータをとらないimplicit
なものは型クラスではない
Play2内部の以下のclass
Application
Request
Lang
scala.concurrent.ExecutionContext
scala.io.Codec
一つに決まらない場合どうするのか?
Scalaでの解決策
スコープで制御できるならスコープで制御
親クラスのimplicit
のほうが優先度低い
実際仕事でjodatimeのDateTime型のReadsのインスタンスがReadsのコンパニオンに存在していて、しかしそれを使いたくない自体が発生して 面倒な状況に!
Monad
も Applicative
も単なる型クラスの1つ
Applicative
はあるが Monad
は現状では存在しない
Monad
は必ず Applicative
(Haskellでも次期versionで修正されるらしい)
なぜ Applicative
が重要か?
Monad
にも Applicative
にもなるが、Applicative
にする方法が2種類ある場合
(言い換えると) Monad
にすることもできるが、それと整合性がない Applicative
が存在する
JsResult
, Reads
Validation
なぜ Applicative
が重要か?
Monad
にはならないが Applicative
になるものが存在する
|@|
という謎な記号
やっとplayの話
Reads
は Applicative
type JsResult[+A] = Validation[Seq[Error], A] // ちょっと正確じゃない
trait Reads[A] {
def reads(json: JsValue): JsResult[A]
}
Reads
は JsValue => JsResult[A]
と同型
JsResult
は Applicative
A => F[B]
において、F[_]
が Applicative
ならば A => F[B]
自体も Applicative
Reads
は、Applicativeの仕組みによりエラーを蓄積する機能があるということ
JsResult
自体にエラーを含んでいるので、Reads
自体は例外を投げるべきではない
scalaz.NonEmptyList
や Semigroup
がないので、 JsResult
の失敗の場合がただの Seq
JsResult
はもっと抽象化できる
Reads
も Writes
も定義するなら、別々に定義するのではなく、Format
で一緒に定義したほうが整合性がとれて良い
apply
が複数あると使えなかったり、case classのフィールド名変えるだけでJsonのkey変わってしまうので微妙
ライブラリ作りました
Reads
のコンパニオンにOption
のReads
があるので)
readNullable[Int]
と read[Option[Int]]
ついでにもう一つライブラリ作ってます
ひたすら型クラスや難しい話をしたけど、型クラス以前にScalaにおける関数型プログラミングで大事なこと
これ素晴らしかったので、読むといいよ
おわり