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

Tuples and Mirrors in Scala3 and Higher-Kinded Data

Tuples and Mirrors in Scala3 and Higher-Kinded Data

D1c239babc57d9bbb8314f72c09844c1?s=128

Kazuhiro Ichikawa

March 19, 2022
Tweet

More Decks by Kazuhiro Ichikawa

Other Decks in Programming

Transcript

  1. Tuples and Mirrors in Scala3 and Higher-Kinded Data @phenan

  2. Abstract u Repository l https://github.com/phenan/hkdms u We have developed the

    following types without macros l HKD l HKTree l HKForest ),% ),5SFF ),'PSFTUͱ͍͏ܕͷ࿩Λ͢Δ ͜ΕΒ͸4DBMBͰϚΫϩͳ͠Ͱ࣮૷Մೳ
  3. HKD: Higher-Kinded Data u HKD[T,F] is a data type like

    T, but all fields are wrapped by F ),%<5 '>͸σʔλܕ5ͷશϑΟʔϧυΛ'Ͱϥοϓͨ͠ܕ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option](Some("Name"), None) val name: Option[String] = hkd.name val age: Option[Int] = hkd.age
  4. HKD: Higher-Kinded Data u HKD[T,F] is a data type like

    T, but all fields are wrapped by F ྫ͑͹),%<6TFS1SPGJMF 0QUJPO>͸ 6TFS1SPG0QUͷΑ͏ͳܕͱΈͳͤΔ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option](Some("Name"), None) val name: Option[String] = hkd.name val age: Option[Int] = hkd.age case class UserProfOpt(name: Option[String], age: Option[String])
  5. HKD: Higher-Kinded Data u HKD[T,F] is a data type like

    T, but all fields are wrapped by F ͪΌΜͱϑΟʔϧυ໊ͰΞΫηεͰ͖Δ ܕ΋͍͍ͭͯΔ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option](Some("Name"), None) val name: Option[String] = hkd.name val age: Option[Int] = hkd.age case class UserProfOpt(name: Option[String], age: Option[String]) we can safely access fields by name
  6. HKD: Higher-Kinded Data u HKD[T,F] is a data type like

    T, but all fields are wrapped by F Πϯελϯεੜ੒Ͱ͸໊લ෇͖Ҿ਺΋࢖͑Δ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option]( name = Some("Name"), age = None ) val name: Option[String] = hkd.name val age: Option[Int] = hkd.age named arguments are also available
  7. hmap operation u hmap takes F~>G and translates HKD[T,F] into

    HKD[T,G] INBQʹࣗવม׵Λ༩͑Δͱ ),%<5 '>Λ),%<5 (>ʹม׵Ͱ͖Δ val hkd = HKD[UserProfile, Option]( name = Some("Name"), age = None ) val hkd2: HKD[UserProfile, [t] =>> Either[String, t]] = hkd.hmap { [t] => (opt: Option[t]) => opt.toRight("missing field") }
  8. fold operation u fold returns F[T] from HKD[T,F] l F

    should be an instance of InvariantMonoidal GPMEϝιουʹΑΓ),%<5 '>͔Β'<5>͕ಘΒΕΔ ͨͩ͠'͕*OWBSJBOU.POPJEBMͰ͋Δඞཁ͕͋Δ val hkd = HKD[UserProfile, Option]( name = Some("Name"), age = Some(50) ) val profileOpt: Option[UserProfile] = hkd.fold println(profileOpt) // Some(UserProfile("Name", 50))
  9. Use case of HKD u HKD can be used for

    Applicative composition! u And it also can be used for contravariant version: Divisible ),%͸"QQMJDBUJWFͷ߹੒ʹར༻Ͱ͖Δ ίϯτϥόϦΞϯτ൛ͷ%JWJTJCMFͰ΋ར༻Մೳ def validate(in: FormValue): Validated[UserProfile] = { HKD[UserProfile, Validated]( name = validateName(), age = validateAge() ).fold }
  10. How to implement HKD u Mirror provides isomorphism between case

    classes and tuples u Tuple.Map transforms (T1,...,Tn) into (F[T1],...,F[Tn]) ),%ΛͲ͏࣮૷͢Δ͔ .JSSPSʹΑΔಉܕࣸ૾ͱ5VQMF.BQΛར༻͢Δ UserProfile (String, Int) Mirror (F[String], F[Int]) Tuple.Map HKD[UserProfile,F]
  11. Mirrors in Scala3 u provide relationship between ADTs and Tuples

    4DBMBͷ.JSSPS͸୅਺తσʔλܕͱλϓϧΛؔ܎͚ͮΔ case class UserProfile(name: String, age: Int) val mirror = summon[Mirror.ProductOf[UserProfile]] // mirror.MirroredElemTypes = (String, Int) val tuple: mirror.MirroredElemTypes = ("Name", 30)
  12. Isomorphism between ADT and Tuple u We can define mutual

    conversion between ADTs and Tuples .JSSPSFE&MFN5ZQFTͱݩͷσʔλܕ͸ಉܕ def adtToTuple[T <: Product] (using m: Mirror.ProductOf[T]) (value: T): m.METs = Tuple.fromProductTyped(value) def tupleToADT[T <: Product] (using m: Mirror.ProductOf[T]) (tuple: m.METs): T = m.fromProduct(tuple)
  13. Tuple.Map in Scala3 u Type-level map function for Tuples u

    Transform (T1,...,Tn) into (F[T1],...,F[Tn]) 4DBMBͰ͸5VQMF.BQͱ͍͏ܕϨϕϧؔ਺Λ࢖͑Δ /* Tuple.Map[(String, Int), Option] * = (Option[String], Option[Int]) */ val mapped: Tuple.Map[(String, Int), Option] = (Some("Name"), None)
  14. Use Mirror with Tuple.Map u It can express data types

    with all fields wrapped by Option .JSSPSͱ5VQMF.BQΛ૊Έ߹ΘͤΔͱ શͯͷϑΟʔϧυΛ0QUJPOͰϥοϓͨ͠ܕ͕ಘΒΕΔ case class UserProfile(name: String, age: Int) /* Tuple.Map[m.MirroredElemTypes, Option] * = Tuple.Map[(String, Int), Option] * = (Option[String], Option[Int]) */ val m = summon[Mirror.ProductOf[UserProfile]] val mapped: Tuple.Map[m.MirroredElemTypes, Option] = (Some("Name"), None)
  15. Definition of HKD u HKD takes Mirror and MirroredElemTypes mapped

    by F .JSSPSͱ5VQMF.BQΛ྆ํอ࣋͢Δ΋ͷΛ ),%ͱͯ͠ఆٛ͢Δ class HKD[T <: Product, F[_]] (m: Mirror.ProductOf[T]) (tuple: Tuple.Map[m.MirroredElemTypes, F]) val hkd: HKD[UserProfile, Option] = new HKD(summon[Mirror.ProductOf[UserProfile]]) (Some("Name"), None)
  16. Field access by name u We want to access each

    field of HKD by field name u And static type check! ϑΟʔϧυ໊Ͱͷܕ҆શͳΞΫηε͸ Ͳ͏΍࣮ͬͯݱ͢Δʁ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option](Some("Name"), None) val name: Option[String] = hkd.name val age: Option[Int] = hkd.age
  17. Dynamic enables us to implement them u If obj implements

    Dynamic, obj.foo is translated to the method call obj.selectDynamic("foo") l Contrary to its name, the method call statically checks the type u Mirror provides field names as tuple of literal types %ZOBNJDΛར༻͢Ε͹࣮૷Ͱ͖Δʂ %ZOBNJD͸໊લʹ൓ͯ͠੩తʹݕࠪ͞ΕΔ val name: Option[String] = hkd.name // is equivalent to val name: Option[String] = hkd.selectDynamic("name") The static type is decidable because the argument has a literal type "name"
  18. Named arguments of constructor u How about named arguments of

    a constructor? Πϯελϯεੜ੒࣌ͷ໊લ෇͖Ҿ਺͸ Ͳ͏΍࣮ͬͯݱ͢Δʁ case class UserProfile(name: String, age: Int) val hkd = HKD[UserProfile, Option]( name = Some("Name"), age = None )
  19. Dynamic is also available u HKD(x, y) à HKD.applyDynamic("apply")(x, y)

    u HKD(a = x, b = y) à HKD.applyDynamicNamed("apply")(("a",x),("b",y)) ͜Εʹ΋%ZOBNJDΛ࢖͑Δ
  20. Dynamic is also available u HKD(x, y) à HKD.applyDynamic("apply")(x, y)

    u HKD(a = x, b = y) à HKD.applyDynamicNamed("apply")(("a",x),("b",y)) Ҿ਺ͷܕ͸͜Μͳײ͡ͷܕʹͳΔ ෳ਺Ҿ਺͸ BVUPUVQMJOH ͰλϓϧԽ͞ΕΔ Tuple.Map[m.MirroredElemTypes, F] parameter auto tupling Tuple.Zip[m.MirroredElemLabels, Tuple.Map[m.MirroredElemTypes, F]] parameter auto tupling
  21. HKTree: Recursive HKD u Tree structure whose leaf nodes are

    wrapped by F u HKTree provides intuitive constructor syntax like HKD l named arguments are available u hmap and fold are also available l fold requires F is an instance of Applicative ໦ߏ଄ͷશͯͷ༿͕'Ͱϥοϓ͞Εͨܕ),5SFF ͜Ε͸࠶ؼత),%Ͱɺ),%ͱಉ༷ͷϝιουΛ࣮૷Մೳ F[A] F[B] F[C]
  22. Separate logic structure and semantics u Semantics can be injected

    later by hmap and fold! ),5SFFΛ࢖ͬͯ࿦ཧͷߏ଄ͱηϚϯςΟΫεΛ෼཭͢Δ ηϚϯςΟΫε͸INBQͱGPMEͰޙ͔Β஫ೖͰ͖Δ val hktree = HKStruct[User, [t]=>>Either[String,t]]( id = Left("user id should be positive"), profile = HKStruct[UserProfile, [t]=>>Either[String,t]]( name = Right("Name"), age = Left("age should be smaller than 1000") ) ) val validated = hktree.hmap(toValidated).fold build logic structure run on the semantics of Validated
  23. HKForest: Another variant of Recursive HKD u HKTree: Use subtype

    relationship l HKTree[Sub, F] <: HKTree[Super, F] l covariant u HKForest: Encode as a branching l HKForest[Super,F] = HKForest[Sub1,F] +...+ HKForest[Subn,F] l invariant / F should support sum operation ௚࿨ܕͷѻ͍ํ͸௨Γ͋ΔɻαϒλΠϓͱΈͳ࣮ͨ͠૷ ͕),5SFF ࢬ෼͔ΕͱΈͳ࣮ͨ͠૷͕),'PSFTU
  24. Separate logic structure and semantics u We can inject both

    covariant and contravariant semantics! *OWBSJBOUͳͷͰɺ$PWBSJBOU$POUSBWBSJBOUͷ ͲͪΒͷηϚϯςΟΫεͰ΋஫ೖՄೳ val syntax: HKForest[UserProfile, Syntax] = { word("UserProfile(") *>: HKProduct[UserProfile, Syntax]( name = word("name: ") *>: string :<* word(", "), age = word("age: ") *>: int ) :<* word(")") } val printer: Printer[UserProfile] = syntax.hmap(syntaxToPrinter).fold val parser: Parser[UserProfile] = syntax.hmap(syntaxToParser).fold invariant logic structure We can share the same logic structure between parser and printer!
  25. Constraint of fold operation of HKForest u F must be

    an instance of InvariantSemiringal l InvariantMonoidal + sum operation u F must be an instance of Defer l To handle recursive data without infinite recursion GPMEϝιουΛར༻͢Δʹ͸ '͕*OWBSJBOU4FNJSJOHBM͔ͭ%FGFSͰ͋Δඞཁ͕͋Δ
  26. Conclusion u Higher-Kinded Data: HKD l HKD is useful for

    Applicative composition l Mirror (isomorphism) + Tuple.Map (apply F) + Dynamic (intuitive API) u Recursive HKD: HKTree, HKForest l They enable us to separate logic structure and semantics l We can inject both Alternative and Decidable semantics for HKForest .JSSPS 5VQMF.BQ %ZOBNJDͰ),%͕࡞ΕΔ ࠶ؼత),%͸ߏ଄ͱηϚϯςΟΫεͷ෼཭ʹ໾ཱͭ repo: https://github.com/phenan/hkdms