$30 off During Our Annual Pro Sale. View Details »

Tuples and Mirrors in Scala3 and Higher-Kinded Data

Tuples and Mirrors in Scala3 and Higher-Kinded Data

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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
    }

    View Slide

  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]

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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

    View Slide

  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"

    View Slide

  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
    )

    View Slide

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

    View Slide

  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

    View Slide

  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]

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

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

    View Slide

  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

    View Slide