Takayuki Sakai
December 06, 2017
500

# cats in practice

## Takayuki Sakai

December 06, 2017

## Transcript

1. cats in practice
੨ࢁΤϯδχΞษڧަྲྀձʙScalaษڧձʙ
ؔ਺ܕϥΠϒϥϦcatsͷΞυςΫ࣮ӡ༻Ͱͷ࢖༻ྫ

2. ञҪ ਸࢸ
- ౦େӃ ৘ใཧ޻ தୀ
- 2016/01- F@N Communicationsגࣜձࣾ
- Scala Engineer / Data Scientist
- Slack & Raspberry PiͰΤΞίϯ͚ͭͨΓ

3. લఏͱ͍ͯ͠Δ஌ࣝ
- جຊతͳScalaͷ஌ࣝ
- ߏจ
- Option, List, Either, TryͳͲͷ
جຊతͳϞφυͷѻ͍ํ

4. ࿩͞ͳ͍͜ͱ
- ਂ͍ؔ਺ܕϓϩάϥϛϯάͷ࿩
- ϞφϞφͨ͜͠ͱͱ͔
- IOϞφυͱ͔

5. What is cats?

6. GitHub

7. GitHub ελʔ਺͸ͪΐ͍

8. catsͱ͸
- Scalaʹ͓͚Δؔ਺ܕϥΠϒϥϦͷσϑΝΫτελϯμʔυ
- ҎԼͷΑ͏ͳؔ਺ܕ৭ͷڧ͍ϥΠϒϥϦ͕಺෦Ͱ
catsʹґଘ͍ͯ͠Δ
- circeʢJSONॲཧʣ
- doobieʢDBΞΫηεʣ
- ﬁnchʢHTTPϧʔςΟϯάʣ
- reﬁnedʢม਺ͷ஋ʹΑΔݫ֨ͳܕ੍໿ʣ

9. ঺հ͢Δ΋ͷ
- syntax.option
- syntax.either
- NonEmptyList
- sequence, traverse
- OptionT, EitherT

10. syntax.option

11. syntax.option._
// ී௨
val i: Option[Int] = Some(1)
val j: Option[Int] = None
——————————————————————————————
// cats
import cats.syntax.option._
val i: Option[Int] = 1.some
val j: Option[Int] = none

12. syntax.either

13. syntax.either._
val a2c : A => C
val b2c : B => C
val b2d : B => D
val either : Either[A, B]
either.leftMap(a2C) : Either[C, B]
either.bimap(a2c, b2d) : Either[C, D]
either.bifoldMap(a2c, b2c) : C

14. “leftMap” in practice
// jsonจࣈྻΛܕAʹύʔε͢Δɻࣦഊ͢Δͱͦͷཧ༝Λॻ͍ͨ
String͕ฦͬͯ͘Δɻ
val either: Either[String, A] = Json.decode(str)
// StringͰ͸ҙຯ͕ᐆດͳͷͰɺErrorܕʹͯ͠Left͕Ͳ͏͍
͏ҙຯͳͷ͔໌֬ʹ͢Δɻ
either.leftMap(s => new CustomError(s))
: Either[CustomError, A]

15. “bifoldMap” in practice
// ݁ՌΛϩάʹྲྀؔ͢਺ɻ੒ޭ͢Δͱtrueɻ
// ϩάʹྲྀ͢

16. NonEmptyList

17. NonEmptyList (a.k.a “Nel”)
- ۭͰ͸ͳ͍͜ͱ͕อূ͞Ε͍ͯΔList
scala> import cats.syntax.list._
scala> List(1,2,3).toNel
res0: Option[cats.data.NonEmptyList[Int]] =
Some(NonEmptyList(1, 2, 3))
scala> List.empty[Int].toNel
res1: Option[cats.data.NonEmptyList[Int]] = None

18. List vs NonEmptyList
val list : List[Int]
list.tail : List[Int] // unsafe
val nel : NonEmptyList[Int]
nel.tail : List[Int] // safe

19. “NonEmptyList” in practice
for {

20. sequence, traverse

21. sequence, traverse
scala> import cats.implicits._
scala> (List(Some(1), Some(2)): List[Option[Int]]).sequence
res0: Option[List[Int]] = Some(List(1, 2))
scala> (List(Some(1), None): List[Option[Int]]).sequence
res1: Option[List[Int]] = None
scala> (Some(List(1,2,3)): Option[List[Int]]).sequence
res2: List[Option[Int]] = List(Some(1), Some(2), Some(3))
scala> (None: Option[List[Int]]).sequence
res3: List[Option[Int]] = List(None)

22. sequence, traverse
scala> import cats.implicits._
scala> (List(Some(1), Some(2)): List[Option[Int]]).sequence
res0: Option[List[Int]] = Some(List(1, 2))
scala> (List(Some(1), None): List[Option[Int]]).sequence
res1: Option[List[Int]] = None
scala> (Some(List(1,2,3)): Option[List[Int]]).sequence
res2: List[Option[Int]] = List(Some(1), Some(2), Some(3))
scala> (None: Option[List[Int]]).sequence
res3: List[Option[Int]] = List(None)

23. “sequence” in practice
// csvͷ֤ߦΛAʹม׵ɻ1ߦͰ΋ࣦഊͨ͠ΒɺશମΛࣦഊʹ͢Δ
def parseCsv(
csv: List[String],
parse: String => Try[A],
): Try[List[A]] = {
val parsed: List[Try[A]] = csv.map(parse)
parsed.sequence
}

24. ύοͱݟɺΑͦ͞͏͚ͩͲ…

25. “sequence” in practice
// csvͷ֤ߦΛAʹม׵ɻ1ߦͰ΋ࣦഊͨ͠ΒɺશମΛࣦഊʹ͢Δ
def parseCsv(
csv: List[String],
parse: String => Try[A],
): Try[List[A]] = {
val parsed: List[Try[A]] = csv.map(parse)
parsed.sequence
}
ߦ໨Ͱࣦഊ͢Δ৔߹Ͱ΋ɺશߦQBSTFͯ͠͠·͏

26. ͜͏͍͏৔߹͸traverseʂ

27. “traverse” in practice
// csvͷ֤ߦΛAʹม׵ɻ1ߦͰ΋ࣦഊͨ͠ΒɺશମΛࣦഊʹ͢Δ
def parseCsv(
csv: List[String],
parse: String => Try[A],
): Try[List[A]] = {
// ࣦഊͨ͠ߦ͕͋ͬͨΒͦͷ࣌఺Ͱऴྃ
csv.traverse(parse)
}

28. OptionT, EitherT

29. OptionT, EitherT
// Redis͔ΒURLΛऔಘ
def getFromRedis(key: String): Future[Option[String]]
// ͦͷURLʹHTTPΞΫηεͯ͠IDΛऔಘ
def getViaHttp(url: String): Future[Int]
// ͦͷIDͰAΛݟ͚ͭΔ
def findByIdInCache(id: Int): Option[A]
//※్தͰFuture͕ࣦഊ or Option͕NoneͩͬͨΒɺͦ͜Ͱॲཧ͸ऴྃ
- ҎԼͷΑ͏ͳྫΛߟ͑Δ

30. OptionT, EitherT
.POBE͕ॏʹͳͬͯͨΓɺ
ҧ͏छྨͩͬͨΓͯ͠΍΍͍͜͠
// Redis͔ΒURLΛऔಘ
def getFromRedis(key: String): Future[Option[String]]
// ͦͷURLʹHTTPΞΫηεͯ͠IDΛऔಘ
def getViaHttp(url: String): Future[Int]
// ͦͷIDͰAΛݟ͚ͭΔ
def findByIdInCache(id: Int): Option[A]
//※్தͰFuture͕ࣦഊ or Option͕NoneͩͬͨΒɺͦ͜Ͱॲཧ͸ऴྃ

31. // Redis͔ΒURLΛऔಘ
def getFromRedis(key: String): Future[Option[String]]
// ͦͷURLʹHTTPΞΫηεͯ͠IDΛऔಘ
def getViaHttp(url: String): Future[Int]
// ͦͷIDͰAΛݟ͚ͭΔ
def findByIdInCache(id: Int): Option[A]
for {
url <- getFromRedis(“key”)
id <- getViaHttp(url)
a <- findByIdInCache(id)
} yield a
.POBEͷछྨ͕ҧ͏ͷͰ
GPSࣜͰճ͢͜ͱ΋Ͱ͖ͳ͍
OptionT, EitherT

32. ͦΕ͕Ͱ͖ΔΜͰ͢ʂ
… ͦ͏ɺOptionTͳΒͶ

33. import cats.data.OptionT
def getFromRedis(key: String): Future[Option[String]]
def getViaHttp(url: String): Future[Int]
def findByIdInCache(id: Int): Option[A]
val result: OptionT[Future, A] = for {
url: String <- OptionT(getFromRedis(“key”))
id : Int <- OptionT.liftF(getViaHttp(url))
a : A <- OptionT.fromOption(findByIdInCache(id))
} yield a
result.value // Future[Option[A]]

34. import cats.data.OptionT
def getFromRedis(key: String): Future[Option[String]]
def getViaHttp(url: String): Future[Int]
def findByIdInCache(id: Int): Option[A]
val result: OptionT[Future, A] = for {
url: String <- OptionT(getFromRedis(“key”))
id : Int <- OptionT.liftF(getViaHttp(url))
a : A <- OptionT.fromOption(findByIdInCache(id))
} yield a
result.value // Future[Option[A]]
0QUJPO5ͷؔ਺Λ࢖ͬͯɺ
શ෦0QUJPO5<'VUVSF ">ܕʹม׵

35. import cats.data.OptionT
def getFromRedis(key: String): Future[Option[String]]
def getViaHttp(url: String): Future[Int]
def findByIdInCache(id: Int): Option[A]
val result: OptionT[Future, A] = for {
url: String <- OptionT(getFromRedis(“key”))
id : Int <- OptionT.liftF(getViaHttp(url))
a : A <- OptionT.fromOption(findByIdInCache(id))
} yield a
result.value // Future[Option[A]]
࠷ޙʹWBMVFؔ਺Ͱ
'VUVSF<0QUJPO<">ʹ໭͢

36. import cats.data.EitherT
def getFromRedis(key: String): Future[Either[Error, String]]
def getViaHttp(url: String): Future[Int]
def findByIdInCache(id: Int): Either[Error, A]
val result: EitherT[Future, Error, A] = for {
url: String <- EitherT(getFromRedis(“key”))
id : Int <- EitherT.liftF(getViaHttp(url))
a : A <- EitherT.fromEither(findByIdInCache(id))
} yield a
result.value // Future[Either[Error, A]]
EitherT ͱ͍͏ͷ΋͋Γ·͢

37. ࠷ޙʹ
- ʮ͜ͷఔ౓ͷཧղͰcatsʹखΛग़͢ͳΜͯ…ʯ
ͱ͍͏ҙݟ΋͋Δͱࢥ͍·͢
- ·ͣ͸࢖ͬͯΈΔ͜ͱ΋େࣄʢʁʣ
- ࢖ͬͯΔ಺ʹͩΜͩΜཧղͯ͘͠Δ

38. ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ