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

shapelessと代数的データ型

 shapelessと代数的データ型

GeoLogicの社内勉強会でshapelessと代数的データ型について発表しました。
shapelessの機能の一つには代数的データ型とジェネリックな型を相互に行き来するものがあります。
代数的データ型は直積型と直和型のデータを組み合わせて表現するデータ型であり。Scalaではcase classで直積型を表し、traitを定義しそれを継承したクラスを定義することで直和型を表すことができます。
shapelessではこれらの代数的データ型をHeterogeneous Listに変換し、コードの再利用性を高めることができます。

KentarouSuzuki

February 28, 2021
Tweet

More Decks by KentarouSuzuki

Other Decks in Technology

Transcript

  1. ジェネリック型 List("hoge", "fuga", "piyo") # ⽂字列のリスト List(1, 2, 3) #

    数値のリスト どちらも要素が連なっているという構造なので、要素となっている型をパラメーター化して List[T] とする。 shapelessって?
  2. ジェネリックメソッド def max(x: Int, y: Int): Int = { if

    (x > y) x else y } def max(x: Float, y: Float): Float = { if (x > y) x else y } このように扱う引数や返り値の型が違うだけで、アルゴリズムが同じ関数は以下のように抽 象化する。 def max(x: T, y: T): T = { if (x > y) x else y } shapelessって?
  3. 例: 座標 2次元上の座標をproduct typeで表す Scala case class Point(x: Int, y:

    Int) type Point = (Int, Int) Haskell data Point = Point Int Int 代数的データ型
  4. 例: 図形 半径を持つ円と底辺・⾼さを持つ⻑⽅形を図形としてsum typeで表す sealed trait Shape case class Circle(raidus:

    Double) extends Shape case class Rectangle(width: Double, Double) extends Shape type Circle = Double type Rectangle = (Double, Double) type Shape = Either[Either[Rectangle, Circle], Triangle] // Scala3 enum Shape: case Circle(radius: Int) case Rectangle(width: Int, height: Int) data Shape = Circle Double | Rectangle Double Double 代数的データ型
  5. 列挙型は直和型なのか? 例えば、⾚・⻩・⻘⾊を持つ列挙型 sealed trait Color case object Red extends Color

    case object Yellow extends Color case object Blue extends Color この場合、コンストラクタが引数を取らないので、直和型とは⾔えない。 代数的データ型
  6. shapelessの機能の⼀つにheterogeneous listを使って、ADTとジェネリックな型を⾏き来す る機能がある。 heterogeneous list: 異質の, 異成分からなる 代数的データ型(ADT) case class

    Point(x: Int, y: Int) trait Shape case class Circle(redion: Int) extends Shape case class Rectangle(width: Int, height: Int): extends Shape ジェネリックな型 Heterogeneous List Int :: Int :: HNil type Point = (Int, Int) type Circle = (Int) type Rectangle = (Int, Int) type Shape = Either[Circle, Rectangle] shapeless shapelessと代数的データ型
  7. 例えば、直積型の場合は case class Hoge(foo: Int, bar: String, baz: Boolean) val

    gen = Generic[Hoge] gen.to(Hoge(1, "fuga", true)) //res: gen.Repr = 1 :: "fuga" :: true ::HNil gen.from(1::"fuga"::true) //res: Hoge = Hoge(1, "fuga", true) で、ADTとHeterogeneous Listを⾏き来することができる。 shapelessと代数的データ型
  8. また、直和型の場合は sealed trait MetaSyntax final case class Hoge(fuga: Int, piyo:

    String) extends MetaSyntax final case class Foo(bar: String, baz: Boolean) extends MetaSyntax val gen = Generic[MetaSyntax] gen.to(Hoge(1, "piyo")) //res: gen.Repr = Inr(Inl(Hoge(1,piyo))) で、変換することができる。 shapelessと代数的データ型
  9. EntityとDTOの変換 DBとアプリケーションの間でデータのやりとりをするときにEntityとは別にDTOを使ってい る場合に、EntityとDTOを変換する時に使える。 case class HogeEntity(foo: Int, bar: String, baz:

    Boolean) case class HogeDTO(foo: Int, bar: String, baz: Boolean) def getRepr[A](value: A)(implicit gen: Generic[A]) = gen.to(value) def toEntity(dto: HogeDTO): HogeEntity = Generic[HogeEntity].from(getRepr(dto)) def toDto(entity: HogeEntity): HogeDTO = Generic[HogeDTO].from(getRepr(entity)) toEntity(HogeDTO(1, "fuga", true)) //res: HogeEntity = HogeEntity(1, "fuga", true) toDto(HogeEntity(2, "piyo", false)) //res: HogeDto = HogeDto(2, "piyo", false) shapelessと代数的データ型
  10. 似たデータの構造を受け取り、変換する関数の実装 例えば、同じデータ構造のclassからCSV形式の⽂字列に変換する関数を以下のように実装で きる。 case class HogeEntity(foo: Int, bar: String, baz:

    Boolean) case class HogeDTO(foo: Int, bar: String, baz: Boolean) def getRepr[A](value: A)(implicit gen: Generic[A]) = gen.to(value) def toCsv(row: Int :: String :: Boolean :: HNil): List[String] = List(row(0).toString, row(1), row(2).toString) val genEntity = getRepr(HogeEntity(1, "fuga", true)) toCsv(genEntity) //res: List[String] = List("1", "fuga", "true") val genDto = getRepr(HogeDTO(2, "piyo", false)) toCsv(genDto) //res: List[String] = List("2", "piyo", "false") shapelessと代数的データ型
  11. 参考⽂献 Guide to Shapeless Qiita 「ADT, 直和・直積, State Machine」 Haskell

    wiki「Algebraic data type」 shapelessと代数的データ型