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. ञҪ ਸࢸ - ౦େӃ ৘ใཧ޻ தୀ
 - 2016/01- F@N Communicationsגࣜձࣾ

    - Scala Engineer / Data Scientist - Slack & Raspberry PiͰΤΞίϯ͚ͭͨΓ
  2. 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
  3. 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
  4. “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]
  5. “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)
  6. 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
  7. 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
  8. “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)
  9. 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ͷॱ൪Λͻͬ͘Γฦ͢
  10. 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ͷॱ൪Λͻͬ͘Γฦ͢
  11. “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 }
  12. “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ͯ͠͠·͏
  13. “traverse” in practice // csvͷ֤ߦΛAʹม׵ɻ1ߦͰ΋ࣦഊͨ͠ΒɺશମΛࣦഊʹ͢Δ def parseCsv( csv: List[String], parse:

    String => Try[A], ): Try[List[A]] = { // ࣦഊͨ͠ߦ͕͋ͬͨΒͦͷ࣌఺Ͱऴྃ csv.traverse(parse) }
  14. 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ͷϘΠϥʔϓϨʔτΛݮΒ͢ - ҎԼͷΑ͏ͳྫΛߟ͑Δ
  15. 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ͩͬͨΒɺͦ͜Ͱॲཧ͸ऴྃ
  16. // 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
  17. 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]]
  18. 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 ">ܕʹม׵
  19. 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<">ʹ໭͢
  20. 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 ͱ͍͏ͷ΋͋Γ·͢