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

Magnet Pattern と Method Overload

Magnet Pattern と Method Overload

GeoLogicの社内勉強会でMagnet Pattern型について発表しました。
Magnet PatternはAkka HTTPなどで実装されているScalaのデザインパターンで、一つの関数に対して様々な型の値を渡せるようなるものです。
このMagnet PatternはDependent Type Methodやimplicit def を使って実装をされています。Scala3ではimplicit defが廃止されgiven Conversion[In, Out]のように実装されます。

5df92c9a1b7abbf03f7b32a4f137441a?s=128

KentarouSuzuki

May 29, 2021
Tweet

Transcript

  1. Magnet Pattern と Method Overload

  2. 本⽇の内容 Magnet Patternとは? Magnet Patternが解決する問題 「名付け」の問題とは? Method Overloadを使って実装 型消去(type erasure)とは?

    メソッド名を増やす Magnet Pattnerの仕組み ListGeneratorMagne implicit def Scala3でのMagnet Pattern
  3. Magnet Patternとは? Magnet Patternとは?

  4. ScalaのFrameworkである、sprayのブログで発表されたデザインパターン。 sprayの後継であるAkka HTTPでもリクエストのパラメーターをパースする部分や、レスポン スを組み⽴てる関数で使われている。 Magnet Patternとは?

  5. Magnet Patternが解決する問題 Magnet Patternが解決する問題

  6. Magnet Patternが解決する問題 sprayのブログでは「名付けの問題」を解決すると述べている。 There are only two hard things in

    Computer Science: cache invalidation, naming things and off-by-1 errors. —Phil Karlton (slightly adapted) Magnet Patternが解決する問題
  7. 「名付け」の問題とは? 例えば、 ListGenerator に対して、以下の様な引数と返り値を想定する。 引数 返り値 1 Int型 List(1) "Hoge"

    String型 List(H, o, g, e) List(1, 2, 3) List Int 型 List(1, 2, 3) List("Hoge", "Fuga") List String 型 List(H, o, g, e, F, u, g, a) Magnet Patternが解決する問題
  8. 「名付け」の問題とは? すると、実装としてはこんな感じになる val fromInt = ListGenerator.generate(1) val fromStr = ListGenerator.generate("hoge")

    // val fromInt: List[Int] = List(1) // val fromStr: List[Char] = List(h, o, g, e) その際に、generateメソッドは適切な名前を付けなければいけない。 Magnet Patternが解決する問題
  9. MethodOverloadで実装する Scalaは静的型付け⾔語なので、関数やメソッドに渡せる値には型を付けなければならない。 なので、Method Overloadをしてgenerateメソッドを実装する。 object ListGenerator { def generate(x: Int):

    List[Int] = x::Nil def generate(x: String): List[Char] = x.map(_.toChar).toList } val fromInt = ListGenerator.generate(1) val fromStr = ListGenerator.generate("hoge") // val fromInt: List[Int] = List(1) // val fromStr: List[Char] = List(h, o, g, e) Magnet Patternが解決する問題
  10. いい感じですね Magnet Patternが解決する問題

  11. しかし、罠が Magnet Patternが解決する問題

  12. このコードをコンパイルしようとすると... object ListGenerator { def generate(x: Int): List[Int] = x::Nil

    def generate(x: String): List[Char] = x.map(_.toChar).toList def generate(x: List[Int]): List[Int] = x def generate(x: List[String]): List[Char] = x.flatMap(_.map(_.toChar).toList) } val fromInt = ListGenerator.generate(1) val fromStr = ListGenerator.generate("hoge") val fromIntList = ListGenerator.generate(List(1, 2, 3, 4)) val fromStrList = ListGenerator.generate(List("hoge", "fuga")) Magnet Patternが解決する問題
  13. コンパイルエラーが... double definition: def generate(x: List[Int]): List[Int] at line 4

    and def generate(x: List[String]): List[Char] at line 5 have same type after erasure: (x: List): List def generate(x: List[String]): List[Char] = x.flatMap(_.map(_.toChar).toList) ^ Compilation Failed Magnet Patternが解決する問題
  14. 理由: 型消去(type erasure) が起きたため Magnet Patternが解決する問題

  15. 型消去(type erasure)とは? Javaの仕様で、総称型をコンパイルする際にジェネリクスに指定した型パラメーターが消去 されること。 例えば、 def hoge(x: List[Int]): Unit =

    ??? def fuga(y: List[String]): Unit = ??? のようなコードは、コンパイル後には以下の様に定義される。 def hoge(x: List): Unit = ??? def fuga(x: List): Unit = ??? Magnet Patternが解決する問題
  16. したがって、総称型に対してMethod Overloadをすると、型の情報が衝突してしまう。 Magnet Patternが解決する問題

  17. メソッド名を増やす 総称型を引数にとったメソッドに対して、Method Overloadはできないために、仕様を実現 するには以下の様な実装が必要になる object ListGenerator { def generateFromInt(x: Int):

    List[Int] = x::Nil def generateFromStr(x: String): List[Char] = x.map(_.toChar).toList def generateFromIntList(x: List[Int]): List[Int] = x def generateFromStrList(x: List[String]): List[Char] = x.flatMap(_.map(_.toChar).toList) } val fromInt = ListGenerator.generateFromInt(1) val fromStr = ListGenerator.generateFromStr("hoge") val fromIntList = ListGenerator.generateFromIntList(List(1, 2, 3, 4)) val fromStrList = ListGenerator.generateFromStrList(List("hoge", "fuga")) // val fromInt: List[Int] = List(1) // val fromStr: List[Char] = List(h, o, g, e) // val fromIntList: List[Int] = List(1, 2, 3, 4) // val fromStrList: List[Char] = List(h, o, g, e, f, u, g, a) Magnet Patternが解決する問題
  18. generateFromXXX 多すぎ問題 「名付け」としては、美しくない そこで登場するのがMagnet Pattern Magnet Patternが解決する問題

  19. Magnet Pattnerの仕組み Magnet Pattnerの仕組み

  20. まずは、実装をご覧ください。 trait ListGeneratorMagnet { type Out def list(): Out }

    implicit def intMagnet(x: Int) = new ListGeneratorMagnet { type Out = List[Int] def list(): Out = x::Nil } implicit def strMagnet(x: String) = new ListGeneratorMagnet { type Out = List[Char] def list(): Out = x.map(_.toChar).toList } implicit def intListMagnet(x: List[Int]) = new ListGeneratorMagnet { type Out = List[Int] def list(): Out = x } implicit def strListMagnet(x: List[String]) = new ListGeneratorMagnet { type Out = List[Char] def list(): Out = x.flatMap(_.map(_.toChar).toList) } object ListGenerator { def generate(magnet: ListGeneratorMagnet): magnet.Out = magnet.list() } val fromInt = ListGenerator.generate(1) val fromStr = ListGenerator.generate("hoge") val fromIntList = ListGenerator.generate(List(1, 2, 3)) val fromStrList = ListGenerator.generate(List("hoge", "fuga")) // val fromInt: List[Int] = List(1) // val fromStr: List[Char] = List(h, o, g, e) // val fromIntList: List[Int] = List(1, 2, 3, 4) // val fromStrList: List[Char] = List(h, o, g, e, f, u, g, a) Magnet Pattnerの仕組み
  21. Magnet Patternの仕組みについて理解するには、次の2つのポイントを踏まえるとわかりやす い ListGeneratorMagnet implicit def Magnet Pattnerの仕組み

  22. ListGeneratorMagnet Magnet Pattnerの仕組み

  23. ListGeneratorMagnetとintMagetの実装 trait ListGeneratorMagnet { type Out def list(): Out }

    implicit def intMagnet(x: Int) = new ListGeneratorMagnet { type Out = List[Int] // 受け取ったx: Int を変換した先の型 def list(): Out = x::Nil // x: Int をtype Out に変換する具体的なロジック } Magnet Pattnerの仕組み
  24. type Out はScala 2.10から追加されたDependent Type Methodを使って、generatorに渡 した値を変換した後の型を定義する。今回実装した ListGenerator.generate に intMagnet

    を渡すとList Int が返り値の型として定義できる。また、 type Out の型を変更 することでgenerateメソッドに渡したmagnetごとに返り値の型を変更することができる。 implicit def intMagnet(x: Int) = new ListGeneratorMagnet { type Out = List[Int] // 受け取ったx: Int を変換した先の型 def list(): Out = x::Nil // x: Int をtype Out に変換する具体的なロジック } object ListGenerator { def generate(magnet: ListGeneratorMagnet): magnet.Out = magnet.list() } Magnet Pattnerの仕組み
  25. でも、generateメソッドに値を渡す時に、都度都度ListMagnet型に変換しないとだめなので は? Magnet Pattnerの仕組み

  26. implicit def Magnet Pattnerの仕組み

  27. (Scalaを完全に理解している⼈には復習) impilcit defとは、スコープの中にある値の型を⾃動的に変換する仕組み。例えば、以下のよ うなコードを書くことができる。 implicit def stringToInt(str: String): Int =

    str.length def printStringLength(length: Int): Unit = println(s"String length is $length") printStringLength("hogefugapiyo") //String length is 12 Magnet Pattnerの仕組み
  28. implicit defをgenerateメソッドのスコープの中に⼊れると、指定した値を ListGeneratorMagnet に変換することができる。 implicit def intMagnet(x: Int) = new

    ListGeneratorMagnet { type Out = List[Int] // 受け取ったx: Int を変換した先の型 def list(): Out = x::Nil // x: Int をtype Out に変換する具体的なロジック } object ListGenerator { def generate(magnet: ListGeneratorMagnet): magnet.Out = magnet.list() } //Int 型を渡しているが、implicit def intMagnet によってListGeneratorMagnet に変換されている ListGenerator.generate(1) Magnet Pattnerの仕組み
  29. Scala3でのMagnet Pattern Magnet Pattnerの仕組み

  30. 先⽇、Scala3がリリースされました。 Magnet Pattnerの仕組み

  31. Scala3ではimplicitの構⽂が廃⽌され、新しく別の実装が加えられました。 それを使って、書き直すと... Magnet Pattnerの仕組み

  32. import scala.language.implicitConversions trait ListGeneratorMagnet { type Out def list(): Out

    } given fromInt: Conversion[Int, ListGeneratorMagnet] with def apply(x: Int): ListGeneratorMagnet = new ListGeneratorMagnet { type Out = List[Int] def list(): Out = x::Nil } object ListGenerator { def generate(magnet: ListGeneratorMagnet): magnet.Out = magnet.list() } ListGenerator.generate(1) // val res0: ListGeneratorMagnet#Out = List(1) Magnet Pattnerの仕組み
  33. まとめ Magnet PatternはAkka HTTPでリクエストのパラメータの型づけやレスポンスの組み⽴ てに使われている Magnet Patternが解決する問題は「名付け」の問題を解決する Method Overloadで起きる、総称型のType Erasureが原因の型の衝突も回避できる

    Denendent Type Methodとimplicit defを使って実装される Scala3ではimplicit defが廃⽌されたのでConversion Int, Out を使って実装をする まとめ