Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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ͰϚΫϩͳ͠Ͱ࣮૷Մೳ

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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])

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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") }

Slide 8

Slide 8 text

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))

Slide 9

Slide 9 text

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 }

Slide 10

Slide 10 text

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]

Slide 11

Slide 11 text

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)

Slide 12

Slide 12 text

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)

Slide 13

Slide 13 text

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)

Slide 14

Slide 14 text

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)

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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"

Slide 18

Slide 18 text

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 )

Slide 19

Slide 19 text

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Λ࢖͑Δ

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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]

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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!

Slide 25

Slide 25 text

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Ͱ͋Δඞཁ͕͋Δ

Slide 26

Slide 26 text

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