AUXパターンをDOTTYで解決する
自己紹介
麻植泰輔
ScalaMatsuri 座長
Septeni Original, Inc. 技術アドバイザー
7/6 新宿 Geek Lounge で 「Real World Android Akka」を 日本語で 喋ります!
BONX, INC. 所属
将軍家の皆さま、ご協賛ありがとうございました!
来年春頃(予定) ScalaMatsuri 2018 もぜひよろしくお願いします m(_ _)m
もしScalaMatsuri経由で入社した方が居た場合、ぜひ記事化させてください
@OE_uia
今日のトピック
Dottyの何が嬉しいの? の一部を紹介する
型クラスの型パラメーター同士に依存関係がある場合
Scala 2.x => Auxパターン
Dotty a.k.a. Scala 3.x => ???
型クラス
既存の型に対し、共通の振る舞いを後から定義する (アドホック多相を実現する)
ためのデザインパターン
trait Semigroup[A]{
def append(a1:A,a2:A):A
}
object Semigroup{
implicit val intGroup:Semigroup[Int] = new Semigroup[Int]{
def append(a1:Int,a2:Int):Int = a1 + a2
}
}
object SemigroupSyntax{
def append[A](a1:A, a2:A)(implicit S:Semigroup[A]):A = S.append(a1,a2)
}
import SemigroupSyntax._
append(1,2) // 3
DEPENDENT METHOD TYPE
引数の型に依存して、メソッドのシグネチャ(のうち、多くの場合は戻り値の型)を変化させることができる
型クラスと組み合わせるパターンを、Magnet Patternと呼ぶ
引数の型にマグネットがくっついて、戻り値の型が変化するイメージ?
型クラス + DEPENDENT METHOD TYPEの例
Measurableという型クラスを使って、Dependent Method Typeを活用する例
trait Measurable[A]{
type Size
def sizeOf(a:A):Size
}
object Measurable{
implicit val intSize:Measurable[Int] = new Measurable[Int]{
type Size = Int
def sizeOf(i:Int):Int = i
}
implicit def seqSize[A]:Measurable[Seq[A]] = new Measurable[Seq[A]]{
type Size = Int
def sizeOf(s:Seq[A]):Int = s.size
}
}
object MeasurableSyntax{
def measure[A](a:A)(implicit M:Measurable[A]):M.Size = M.sizeOf(a)
}
import MeasurableSyntax._
measure(Seq(1,2,3)) // 3
measure(1) // 1
型クラスを組み合わせたい…
一つの引数リストに両方入れる?
さっきのSemigroupとMeasurableを組み合わせると、こんな感じ…?
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A], S:Semigroup[M.Size]):M.Size
S.append(M.sizeOf(a1),M.sizeOf(a2))
コンパイルが通らない!
同じ引数リスト内の引数を参照できない
scala> def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A], S:Semigroup[M.Size]):
| S.append(M.measure(a1),M.measure(a2))
<console>:32: error: illegal dependent method type:
parameter may only be referenced in a subsequent parameter section
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A], S:Semigroup[M.Size]):
^
型クラスを組み合わせたい…(TAKE 2)
複数の暗黙の引数リストを使う?
じゃあ subsequent parameter section ということは、こんな感じ?
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A])(implicit S:Semigroup[M.Size
S.append(M.sizeOf(a1),M.sizeOf(a2))
コンパイルが通らない!
暗黙の引数リストは1つまで
<console>:1: error: an implicit parameter section must be last
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A])(implicit S:Semigroup[M.Size
^
<console>:1: error: multiple implicit parameter sections are not allowed
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A])(implicit S:Semigroup[M.Size
^
型クラスを組み合わせたい…(TAKE 3)
AUXパターン!!
型メンバを型パラメーターへマッピングすることで、暗黙のパラメーターが持つ型パラメーター同士で依存させることができる
trait Measurable[A]{
type Size
def sizeOf(a:A):Size
}
//ここまで同じ
object Measurable{
type Aux[A0,B0] = Measurable[A0]{type Size = B0}
implicit val intAux:Measurable.Aux[Int,Int] = new Measurable[Int]{
type Size = Int
def sizeOf(i:Int):Int = i
}
}
def sumSizes[A,Size](a1:A,a2:A)(implicit M:Measurable.Aux[A,Size], S:Semigroup[Size
S.append(M.sizeOf(a1),M.sizeOf(a2))
コンパイル通る!
…けど、ちょっとhacky。
DOTTY A.K.A. SCALA 3.0
EPFLの小田好先生の研究室で開発を主導
現在pre-alpha
日々アクティブに開発されてるので、
以下、 scalaVersion := "0.1.1-bin-20170511-cc4533d-NIGHTLY" で実験
Nightly Buildsを試せる!
IMPLICIT FUNCTION TYPE
暗黙の引数を取る関数、を型で表現
trait Semigroup[A]{
def append(a1:A,a2:A):A
}
object Semigroup{
implicit val intGroup:Semigroup[Int] = new Semigroup[Int]{
def append(a1:Int,a2:Int):Int = a1 + a2
}
}
//ここまで先程の例と一緒
object SemigroupSyntax{
type Semigroupish[A] = implicit Semigroup[A] => A
def append[A](a1:A, a2:A):Semigroupish[A] = {implicit S => S.append(a1,a2)}
}
object SemigroupMeasurableSyntax {
import SemigroupSyntax._
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A]):Semigroupish[M.Size] = append(
}
MULTIPLE IMPLICIT PARAMETER LISTS
Implicit Function Typeのメソッド表現
いずれかの暗黙の引数 のみ に明示的に値を渡す場合、どの暗黙の引数リストに渡すか決定する構文が欲しい
f.explicitly(...) ... ?
まだ提案段階だが、入る可能性高そう?
//今(5/11 nightly builds)はまだ出来ないが、そのうちこうやって書けるようになるはず
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A])(implicit S:Semigroup[M.Size
S.append(M.sizeOf(a1),M.sizeOf(a2))
でも実は...
一つの引数リストに両方入れる
Scala 2.x系ではコンパイルは通らなかったが…
object SemigroupMeasurableSyntax {
def sumSizes[A](a1:A,a2:A)(implicit M:Measurable[A], S:Semigroup[M.Size]):M.Size
S.append(M.sizeOf(a1),M.sizeOf(a2))
}
/(^O^)
defined trait Measurable
defined module Measurable
defined module MeasurableSyntax
defined trait Semigroup
defined module Semigroup
defined module SemigroupSyntax
defined module SemigroupMeasurableSyntax <- !?!?!?
Dottyではコンパイル通る!!!
結論
DottyになればAuxパターンは不要になる(ハズ) :tada:
型クラスの型パラメーター同士に依存関係がある場合
Scala 2.x => Auxパターン
Dotty a.k.a. Scala 3.x => 共通の暗黙の引数リスト || Implicit Function Type
注: Implicit Function Typeは、Auxパターンをなくすこと「だけ」が目的では有りません
文脈から値を取り出すことを抽象化する、大変強力な構文です
をご参照のこと。小田好先生のブログ

AuxパターンをDottyで解決する