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

Scalaで関数型 再入門

Scalaで関数型 再入門

よく遭遇する、Scala標準ライブラリだけだと大変なケースでも、
Catsを使うとうまく関心事を分離して、クリーンかつ拡張に対して頑強になることがあります。

このスライドでは、そういった場合のCatsの使い所について、初学者向けに解説をしています。

2019年7月26日のセプテーニ・オリジナルでの社内勉強会資料です。

Taisuke Oe

July 26, 2019
Tweet

More Decks by Taisuke Oe

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ Taisuke Oe / ຑ২ ହี Twitter: @OE_uia GitHub: @taisukeoe

    Septeni Original, Inc. ٕज़ΞυόΠβʔ ScalaMatsuri ࠲௕
  2. Catsͷ࢖͍ํ build.sbt ʹҎԼͷ2ߦΛ௥Ճ Cats 1.6.1͸࠷৽stable൛ͰɺScala 2.11, 2.12ʹରԠɻ Scala 2.13Λ࢖༻͢Δ৔߹͸ɺCats 2.0.0-M4͕࠷৽

    scalacOptions += "-Ypartial-unification" libraryDependencies += "org.typelevel" %% "cats-core" % "1.6.1"
  3. List[Either[Error, Int]] val eitherList: List[Either[Error, Int]] = List(Right(1), Right(2), Right(3))

    eitherList.foldLeft(Right(0):Either[Error, Int]) { case (Right(v1), Right(v2)) => Right(v1 + v2) case (Right(_), l @ Left(_)) => l case (l @ Left(_), _) => l } foldLeft಺Ͱɺཁૉܕͷߏ଄ͷ෼ղ͕ඞཁ ߏ଄͕ਂ͘ωετ͢Δ΄Ͳɺίʔυྔ͕૿͑Δɻ ࠶ར༻͠ʹ͘͘ͳΔɻ
  4. ଍͠ࢉ Either[Error, Int]ܕ ͷ଍͠ࢉʹ͸ɺߏ଄ͷ ෼ղ͕ඞཁ Right(1) + Right(2) / /

    Right(3) ੒ޭ஋Int͕ɺ஋Ϋϥε(ྫ: Price)ʹมΘͬ ͯ΋ɺ࠶ར༻͍ͨ͠
  5. Semigroupͷ࢖͍ํ import cats._ import cats.implicits._ 1 |+| 2 // 3

    Option(1) |+| Option(2) // Some(3) (Right(1): Either[Error, Int]) |+| (Right(2): Either[Error, Int]) // Right(3)
  6. OptionܕΛฦ͢.some import cats._ import cats.implicits._ 1.some |+| 2.some //Some(3) 1.some

    |+| none //Some(1) 1.asRight[Error] |+| 2.asRight[Error] //Right(3)
  7. ஋Ϋϥε Price case class Price(private val value: Int) extends AnyVal

    { def +(p: Price): Price = this.copy(value = value + p.value) } implicit val priceMonoid: Monoid[Price] = new Monoid[Price] { override def empty: Price = Price(0) override def combine(x: Price, y: Price): Price = x + y } import cats._, cats.implicits._ Foldable[List].fold( List(Price(100).asRight[Error], Price(200).asRight[Error]) ) //Price(300)
  8. Լ४උ trait Product case class GoodProduct(id: Long, price: Price) extends

    Product case class NiceProduct(id: Long, price: Price) extends Product trait ProductRepository[F[_], T <: Product] { def resolveById(id: Long): F[T] } val goodRepo = new ProductRepository[Future, GoodProduct]{ override def resolveById(id: Long): Future[GoodProduct] = ??? } val niceRepo = new ProductRepository[Future, NiceProduct]{ override def resolveById(id: Long): Future[NiceProduct] = ??? } def taxing(product: Product): Future[Price] = ???
  9. Futureͷ߹੒: ௚ྻ FutureΠϯελϯεͷੜ੒͕ɺଞͷFutureͷܭࢉ݁Ռʹґଘ ͍ͯ͠Δ৔߹ forࣜͰ؆୯ʹ߹੒Ͱ͖Δɻ val taxedPrice = for {

    product <- goodRepo.resolveById(1L) taxed <- taxing(product) } yield taxed ͋Δ঎඼ͷՁ֨(੫ࠐ)Λܭࢉ
  10. ෳ਺ͷ௚ྻܭࢉΛฒྻʹ ՄೳͳݶΓฒྻʹ߹੒ͯ͠ΈΔ val niceF = niceRepo.resolveById(2L) val both = for

    { good <- goodRepo.resolveById(1L) taxedGoodF = taxing(good) nice <- niceF taxedNice <- taxing(nice) taxedGood <- taxedGoodF } yield taxedGood + taxedNice ෳ਺ͷ঎඼ͷՁ֨(੫ࠐ)Λ߹ܭ͢Δɻ
  11. ෳ਺ͷ௚ྻܭࢉΛฒྻʹ forࣜΛ෼ׂ͢Δͱɺز෼Ϛγɻ val taxedGood = for { product <- goodRepo.resolveById(1L)

    taxed <- taxing(product) } yield taxed val taxedNice = for { product <- niceRepo.resolveById(2L) taxed <- taxing(product) } yield taxed for { goodPrice <- taxedGood nicePrice <- taxedNice } yield goodPrice + nicePrice ෳ਺ͷ঎඼ͷՁ֨(੫ࠐ)Λ߹ܭ͢Δɻ
  12. ฒྻܭࢉΛ߹੒ ApplicativeελΠϧͰ͸ฒྻܭࢉΛ߹੒͠΍͍͢ import cats._ import cats.implicits._ (goodRepo.resolveById(1L).flatMap(taxing), niceRepo.resolveById(2L).flatMap(taxing)).mapN(_ + _)

    ෳ਺ͷ঎඼ͷՁ֨(੫ࠐ)Λ߹ܭ͢Δɻ Applicative[Future].map2( goodRepo.resolveById(1L).flatMap(taxing), niceRepo.resolveById(2L).flatMap(taxing))(_ + _)
  13. ฒྻܭࢉΛ߹੒ ฒྻͯ͠औಘ/ݕূͨ͠ܭࢉΛɺϑΝΫτϦϝ ιουʹ؆୯ʹྲྀ͠ࠐΊΔ import cats._ import cats.implicits._ case class User(name:

    String, age: Int) val nameFuture: Future[String] = ??? val ageFuture: Future[Int] = ??? (nameFuture, ageFuture).mapN(User.apply)
  14. ͜͜ͰRepositoryͷվम ΤϥʔΛEitherܕͰදݱ͢Δ͜ͱʹͳͬͨ Ͳ͏͢Ε͹ܭࢉ݁ՌΛ߹੒Ͱ͖Δʁ type EitherE[T] = Either[Error, T] type FutureEitherE[T]

    = Future[EitherE[T]] val goodERepo = new ProductRepository[FutureEitherE, GoodProduct]{ override def resolveById(id: Long): FutureEitherE[GoodProduct] = ??? } val niceERepo = new ProductRepository[FutureEitherE, NiceProduct]{ override def resolveById(id: Long): FutureEitherE[NiceProduct] = ??? }
  15. EitherT[Future, A, B] type EitherTFuture[T] = EitherT[Future, Error, T] val

    goodETRepo = new ProductRepository[EitherTFuture, GoodProduct] { override def resolveById(id: Long): EitherTFuture[GoodProduct] = ??? } val niceETRepo = new ProductRepository[EitherTFuture, NiceProduct] { override def resolveById(id: Long): EitherTFuture[NiceProduct] = ??? }
  16. EitherT[Future, A, B] Applicative[EitherTFuture].map2( goodETRepo.resolveById(1L).flatMap(taxing), niceETRepo.resolveById(2L).flatMap(taxing))(_ + _) EitherT͸Monadɻ શͯͷMonad͸ApplicativeͰ΋͋ΔͷͰɺEitherT͸ApplicativeͰ

    ΋͋Δɻ Ͱ΋taxingͷγάωνϟ͕ҰகͤͣɺίϯύΠϧͰ͖ͳ͍ def taxingEF(product: Product): EitherTFuture[Price] = ??? EitherTFuture൛ͷtaxing͕ཉ͍͠
  17. EitherT Future൛ ׬੒! val taxingEF = ɹɹ (taxing _).andThen(f =>

    EitherT(f.map(_.asRight[Error]))) Applicative[EitherTFuture].map2( goodETRepo.resolveById(1L).flatMap(taxingEF), niceETRepo.resolveById(2L).flatMap(taxingEF))(_ + _) ࠓճͷtaxingͷΑ͏ͳɺMonadΛฦؔ͢਺Λ ߹੒ɾ֦ு͍ͨ͠
  18. Kleisli Kleisli[F, A, B] ͸ A => F[B] ͷϥούʔ MonadΛੜ੒͢Δؔ਺Λϥοϓ͢Δ͜ͱ͕ଟ͍

    ͜ͷΑ͏ͳؔ਺Λศརʹѻ͏ͨΊͷϝιου͕ଟ ਺ɻ runϝιουͰɺதͷA => F[B]΁ΞΫηεՄೳ F͕MonadͰ͋Ε͹ɺKleisli[F, A, B]΋Monad
  19. Kleisli ߹੒ def taxing(product: Product): Future[Price] = ??? def exchange(jpy:

    Price): Future[PriceInUSD] = ??? // Function1ͷandThenʹΑΔ߹੒͸Ͱ͖ͳ͍ɻίϯύΠϧΤϥʔɻ // (taxing _) andThen exchange val taxingPriceInUSD = Kleisli(taxing) andThen exchange _ val taxingEFinUSD = taxingInUSD.mapF(f => EitherT(f.map(_.asRight[Error]))) Applicative[EitherTFuture].map2( goodETRepo.resolveById(1L).flatMap(taxingEFinUSD.run), niceETRepo.resolveById(2L).flatMap(taxingEFinUSD.run))(_ + _)
  20. ࣗྗͰsequence val sequenced: Either[Error, List[Price]] = list.foldRight(List.empty[Price].asRight[Error]){ (e, acc) =>

    for { value <- e valueList <- acc } yield value :: valueList } ґଘؔ܎ͷͳ͍(ฒྻ)ܭࢉ͕ొ৔͢Δɻ Applicativeͷ೏͍……