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

cats in practice

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

cats in practice

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

Avatar for Takayuki Sakai

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