Slide 1

Slide 1 text

Magnet Pattern と Method Overload

Slide 2

Slide 2 text

本⽇の内容 Magnet Patternとは? Magnet Patternが解決する問題 「名付け」の問題とは? Method Overloadを使って実装 型消去(type erasure)とは? メソッド名を増やす Magnet Pattnerの仕組み ListGeneratorMagne implicit def Scala3でのMagnet Pattern

Slide 3

Slide 3 text

Magnet Patternとは? Magnet Patternとは?

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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が解決する問題

Slide 7

Slide 7 text

「名付け」の問題とは? 例えば、 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が解決する問題

Slide 8

Slide 8 text

「名付け」の問題とは? すると、実装としてはこんな感じになる 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が解決する問題

Slide 9

Slide 9 text

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が解決する問題

Slide 10

Slide 10 text

いい感じですね Magnet Patternが解決する問題

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

このコードをコンパイルしようとすると... 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が解決する問題

Slide 13

Slide 13 text

コンパイルエラーが... 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が解決する問題

Slide 14

Slide 14 text

理由: 型消去(type erasure) が起きたため Magnet Patternが解決する問題

Slide 15

Slide 15 text

型消去(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が解決する問題

Slide 16

Slide 16 text

したがって、総称型に対してMethod Overloadをすると、型の情報が衝突してしまう。 Magnet Patternが解決する問題

Slide 17

Slide 17 text

メソッド名を増やす 総称型を引数にとったメソッドに対して、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が解決する問題

Slide 18

Slide 18 text

generateFromXXX 多すぎ問題 「名付け」としては、美しくない そこで登場するのがMagnet Pattern Magnet Patternが解決する問題

Slide 19

Slide 19 text

Magnet Pattnerの仕組み Magnet Pattnerの仕組み

Slide 20

Slide 20 text

まずは、実装をご覧ください。 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の仕組み

Slide 21

Slide 21 text

Magnet Patternの仕組みについて理解するには、次の2つのポイントを踏まえるとわかりやす い ListGeneratorMagnet implicit def Magnet Pattnerの仕組み

Slide 22

Slide 22 text

ListGeneratorMagnet Magnet Pattnerの仕組み

Slide 23

Slide 23 text

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の仕組み

Slide 24

Slide 24 text

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の仕組み

Slide 25

Slide 25 text

でも、generateメソッドに値を渡す時に、都度都度ListMagnet型に変換しないとだめなので は? Magnet Pattnerの仕組み

Slide 26

Slide 26 text

implicit def Magnet Pattnerの仕組み

Slide 27

Slide 27 text

(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の仕組み

Slide 28

Slide 28 text

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の仕組み

Slide 29

Slide 29 text

Scala3でのMagnet Pattern Magnet Pattnerの仕組み

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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の仕組み

Slide 33

Slide 33 text

まとめ Magnet PatternはAkka HTTPでリクエストのパラメータの型づけやレスポンスの組み⽴ てに使われている Magnet Patternが解決する問題は「名付け」の問題を解決する Method Overloadで起きる、総称型のType Erasureが原因の型の衝突も回避できる Denendent Type Methodとimplicit defを使って実装される Scala3ではimplicit defが廃⽌されたのでConversion Int, Out を使って実装をする まとめ