Upgrade to Pro — share decks privately, control downloads, hide ads and more …

cats in practice

cats in practice

関数型ライブラリcatsのアドテク実運用での使用例

Takayuki Sakai

December 06, 2017
Tweet

More Decks by Takayuki Sakai

Other Decks in Programming

Transcript

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

    View full-size slide

  2. ञҪ ਸࢸ
    - ౦େӃ ৘ใཧ޻ தୀ

    - 2016/01- F@N Communicationsגࣜձࣾ
    - Scala Engineer / Data Scientist
    - Slack & Raspberry PiͰΤΞίϯ͚ͭͨΓ

    View full-size slide

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

    جຊతͳϞφυͷѻ͍ํ

    View full-size slide

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

    View full-size slide

  5. What is cats?

    View full-size slide

  6. GitHub ελʔ਺͸ͪΐ͍

    View full-size slide

  7. catsͱ͸
    - Scalaʹ͓͚Δؔ਺ܕϥΠϒϥϦͷσϑΝΫτελϯμʔυ
    - ҎԼͷΑ͏ͳؔ਺ܕ৭ͷڧ͍ϥΠϒϥϦ͕಺෦Ͱ

    catsʹґଘ͍ͯ͠Δ
    - circeʢJSONॲཧʣ
    - doobieʢDBΞΫηεʣ
    - finchʢHTTPϧʔςΟϯάʣ
    - refinedʢม਺ͷ஋ʹΑΔݫ֨ͳܕ੍໿ʣ

    View full-size slide

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

    View full-size slide

  9. syntax.option

    View full-size slide

  10. 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

    View full-size slide

  11. syntax.either

    View full-size slide

  12. 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

    View full-size slide

  13. “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]

    View full-size slide

  14. “bifoldMap” in practice
    // ഑৴͍ͨ͠޿ࠂʢAdʣΛऔಘ͢Δɻ΋͠ͳ͍৔߹͸ɺԿނͳ͍ͷ͔
    ͦͷཧ༝Λҙຯ͢ΔNoAdReason͕ฦͬͯ͘Δɻ
    val result: Either[NoAdReason, Ad] = getAd
    // ݁ՌΛϩάʹྲྀؔ͢਺ɻ੒ޭ͢Δͱtrueɻ
    val logNoAdReason: NoAdReason => Boolean
    val logAd : Ad => Boolean
    // ϩάʹྲྀ͢
    val succeeded = result.bifoldMap(logNoAdReason, logAd)

    View full-size slide

  15. NonEmptyList

    View full-size slide

  16. 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

    View full-size slide

  17. List vs NonEmptyList
    val list : List[Int]
    list.head : Int // unsafe
    list.tail : List[Int] // unsafe
    list.headOption : Option[Int] // safe
    val nel : NonEmptyList[Int]
    nel.head : Int // safe
    nel.tail : List[Int] // safe

    View full-size slide

  18. “NonEmptyList” in practice
    def getCandidateAds: List[Ad] // ഑৴Ͱ͖Δ޿ࠂʢAdʣ
    def getAdditionalInfo: Option[Info] // ࠷దͳ޿ࠂΛܾΊΔͨΊͷ৘ใ
    def chooseBestAd(info: Info, ads: NonEmptyList[Ad]): Ad
    for {
    adNel <- getCandidateAds.toNel
    info <- getAdditionalInfoɹ// ഑৴Ͱ͖Δ޿ࠂ͕ͳ͚Ε͹ݺ͹Εͳ͍
    } yield chooseBestAd(info, adNel)

    View full-size slide

  19. sequence, traverse

    View full-size slide

  20. 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)
    - 2ॏʹแ·ΕͨMonadͷॱ൪Λͻͬ͘Γฦ͢

    View full-size slide

  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)
    - 2ॏʹแ·ΕͨMonadͷॱ൪Λͻͬ͘Γฦ͢

    View full-size slide

  22. “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
    }

    View full-size slide

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

    View full-size slide

  24. “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ͯ͠͠·͏

    View full-size slide

  25. ͜͏͍͏৔߹͸traverseʂ

    View full-size slide

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

    View full-size slide

  27. OptionT, EitherT

    View full-size slide

  28. 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ͩͬͨΒɺͦ͜Ͱॲཧ͸ऴྃ
    - 2ॏMonadͷϘΠϥʔϓϨʔτΛݮΒ͢
    - ҎԼͷΑ͏ͳྫΛߟ͑Δ

    View full-size slide

  29. 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ͩͬͨΒɺͦ͜Ͱॲཧ͸ऴྃ

    View full-size slide

  30. // 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

    View full-size slide

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

    View full-size slide

  32. 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]]

    View full-size slide

  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]]
    0QUJPO5ͷؔ਺Λ࢖ͬͯɺ

    શ෦0QUJPO5<'VUVSF ">ܕʹม׵

    View full-size slide

  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]]
    ࠷ޙʹWBMVFؔ਺Ͱ

    'VUVSF<0QUJPO<">ʹ໭͢

    View full-size slide

  35. 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 ͱ͍͏ͷ΋͋Γ·͢

    View full-size slide

  36. ࠷ޙʹ
    - ʮ͜ͷఔ౓ͷཧղͰcatsʹखΛग़͢ͳΜͯ…ʯ

    ͱ͍͏ҙݟ΋͋Δͱࢥ͍·͢
    - ·ͣ͸࢖ͬͯΈΔ͜ͱ΋େࣄʢʁʣ
    - ࢖ͬͯΔ಺ʹͩΜͩΜཧղͯ͘͠Δ

    View full-size slide

  37. ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ

    View full-size slide