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]のように実装されます。

KentarouSuzuki

May 29, 2021
Tweet

More Decks by KentarouSuzuki

Other Decks in Programming

Transcript

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

    メソッド名を増やす Magnet Pattnerの仕組み ListGeneratorMagne implicit def Scala3でのMagnet Pattern
  2. 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が解決する問題
  3. 「名付け」の問題とは? 例えば、 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が解決する問題
  4. 「名付け」の問題とは? すると、実装としてはこんな感じになる 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が解決する問題
  5. 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が解決する問題
  6. このコードをコンパイルしようとすると... 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が解決する問題
  7. コンパイルエラーが... 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が解決する問題
  8. 型消去(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が解決する問題
  9. メソッド名を増やす 総称型を引数にとったメソッドに対して、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が解決する問題
  10. まずは、実装をご覧ください。 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の仕組み
  11. 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の仕組み
  12. 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の仕組み
  13. (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の仕組み
  14. 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の仕組み
  15. 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の仕組み