やることとか前提
-
Dotty Documentation の Reference 項の内容を頭から順番にやっていきます
- 対象のdottyバージョンは dotty-0.11.0-RC1 です
- 対象ドキュメントは、2018/12/03 時点のウェブサイトのものです。
- が、2018/10 中旬に一度作ったのモノに追記して作っているので、古くなっている部分があるかもしれません
- サンプルコードは、ドキュメントそのままのモノと、独自に書いたモノが混ざっています
- コンパイル・実行可能なコードは、githubのリポジトリから取得可能です
- この記事を書いているタイミングのgit hashは673ed88640bfです
New Types
Literal Singleton Types
何故かドキュメントへのリンクが目次に見当たりませんでした。
リテラルを、型として取り扱う事ができます。
object LiteralSingletonTypes {
val t: 42 = 42
var x: "Jedi" = "Jedi"
trait Mat[N <: Singleton, M <: Singleton] {
def add(aThat: Mat[N, M]): Mat[N, M]
}
val m1: Mat[2, 3] = ???
val m2: Mat[2, 3] = ???
val m3: Mat[4, 3] = ???
m1.add(m2)
// m1.add(m3) // コンパイルエラー
}
Intersection Types
A
型でもB
型でもある型を表します。
with
は&
と等価となり、そのうち消えるらしい。
object IntersectionTypes {
trait A
trait B
// with も残るが、deprecatedになって、そのうち消える
def withAB(ab: A with B): Unit = ()
def withBA(ba: B with A): Unit = ()
def andAB(ab: A & B): Unit = ()
def andBA(ba: B & A): Unit = ()
}
Union Types
A
型であるか、B
型である型を表します。
残念ながら、現状literal type + union type
は現状エラーとなります。1
object UnionTypes {
def method(a: Int | String): Unit = a match {
case i: Int => println("int!")
case s: String => println("string!")
}
// Singleton type Int(2) is not allowed in a union type
// def method2(a: 1 | 2): Unit = ()
}
Type Lambdas
({type C[A] = Map[String, A]})#C
という記法から開放されます。
個人的には、上記記法では直接表現できなかった、任意の型で型引数一つを埋める
ことが出来るようになったのが注目点(下記のMap2
やMap3
やState#St
)。
object TypeLambdas {
type Map0[K, V] = Map[K, V]
type Map1 = [K, V] => Map[K, V]
type Map2[K] = [V] => Map[K, V]
type Map3 = [K] => [V] => Map[K, V]
}
object RunState {
def main(args: Array[String]): Unit = {
val tState: State.St[String][Int] = State {s => (s, 1)}
val tInt = Functor.map(tState)(_ + 1).run("")._2
println(tInt) // 2
}
}
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def map[F[_]: Functor, A, B](fa: F[A])(f: A => B): F[B] = implicitly[Functor[F]].map(fa)(f)
}
case class State[S, A](run: S => (S, A))
object State {
type St[S] = [A] => State[S, A]
implicit def stateFunctor[S]: Functor[St[S]] = new Functor[St[S]] {
def map[A, B](fa: St[S][A])(f: A => B): St[S][B] = State { s0 =>
val (s1, a) = fa.run(s0)
(s1, f(a))
}
}
}
Match Types
type member
の引数ごとに、違う型を返すことが出来ます。
現行のScalaでも、型クラスを使うと似たようなことが出来ましたが、より直接的に表現できるようになります。
object MatchTypes {
type Elem[X] = X match {
case String => Char
case Array[t] => t
case Iterable[t] => t
}
// Elem[String] =:= Char
// Elem[Array[Int]] =:= Int
// Elem[List[Float]] =:= Float
// Elem[Nil] =:= Nothing
}
再帰的にmatch
することも可能です。
object RecursiveMatchTypes {
// Tupleの結合
type Concat[+Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {
case Unit => Ys
case x *: xs => x *: Concat[xs, Ys]
}
val t1: Concat[(String, Int), Unit] = ("a", 1)
val t2: Concat[(String, Int), (String, Int)] = ("a", 1, "b", 2)
// 独自Tupleでもやってみる
sealed trait MyTuple
class **:[A, B <: MyTuple] extends MyTuple
object MyNil extends MyTuple
// Tupleの一番右の型を取り出す
type Last[X <: MyTuple] = X match {
case h **: MyNil.type => h
case h **: t => Last[t]
}
val double: Last[Int **: String **: Double **: MyNil.type] = 1.0
}
Implicit Function Types
implicitな引数を持つ関数
を型で表すことができるようになります。
object ImplicitFunctionTypes {
type ImplicitFunc = implicit String => Int
def method(f: ImplicitFunc): Int = f("hoge")
val result = method {
// 暗黙的に、"hoge"が渡ってきている
implicitly[String].length
}
}
これを利用すると、暗黙的なコンテキストをimplicitに渡して動作するDSLを書きやすくなります。
object RunTableDSL {
def main(args: Array[String]): Unit = {
import TableDSL._
val tTable = table {
row {
cell("1-1")
cell("1-2")
cell("1-3")
}
// cell("aa") // rowの外でcellを呼ぶと、implicitなRowが無いのでコンパイルエラー
row {
cell("2-1")
cell("2-2")
cell("2-3")
}
}
println(tTable)
// Table(
// Row(1-1 1-2 1-3)
// Row(2-1 2-2 2-3)
// )
}
}
object TableDSL {
import scala.collection.mutable.ArrayBuffer
type TableF = implicit Table => Table
type RowF = implicit Row => Row
def table(f: TableF): Table = {
f(new Table)
}
def row(f: RowF)(implicit t: Table): Table = {
t.addRow(f(new Row))
t
}
def cell(s: String)(implicit r: Row): Row = {
r.addCell(Cell(s))
r
}
class Table {
private val mRows = new ArrayBuffer[Row]
def addRow(aRow: Row): this.type = {
mRows += aRow
this
}
override def toString(): String = {
def rows = mRows.mkString("\n")
s"Table(\n${rows}\n)"
}
}
class Row {
private val mCells = new ArrayBuffer[Cell]
def addCell(aCel: Cell): this.type = {
mCells += aCel
this
}
override def toString(): String = {
s" Row(${mCells.map(_.v).mkString("\t")})"
}
}
case class Cell(v: String)
}
Dependent Function Types
method dependent types
に対応する記述が、関数型で可能となります。
object DependentFunctionTypes {
trait Container {
type Elem
val get: Elem
}
def getElem(c: Container): c.Elem = c.get
val getElemF: (c: Container) => c.Elem = _.get
case class StringC(get: String) extends Container {
type Elem = String
}
getElem(StringC("hoge")): String
getElemF(StringC("fuga")): String
}
Enums
Enumerations
列挙型が追加されます。
object Enums {
enum Color {
case Red, Green, Blue
}
// 大体以下と同じ
sealed trait Color2
object Color2 {
case object Red extends Color2
case object Green extends Color2
case object Blue extends Color2
}
}
列挙型には、いくつかの便利メソッドが定義されています。
object RunEnums {
def main(args: Array[String]): Unit = {
// enumValues などの便利メソッドが生えている
Enums.Color.enumValues.foreach { e =>
println(s"enum: ${e}, tag: ${e.enumTag}")
}
// enum: Red, tag: 0
// enum: Green, tag: 1
// enum: Blue, tag: 2
Enums.Color.enumValue.foreach { (i, e) =>
println(s"i: ${i}, enum: ${e}, tag: ${e.enumTag}")
}
// i: 0, enum: Red, tag: 0
// i: 1, enum: Green, tag: 1
// i: 2, enum: Blue, tag: 2
Enums.Color.enumValueNamed.foreach { (n, e) =>
println(s"name: ${n}, enum: ${e}, tag: ${e.enumTag}")
}
// name: Red, enum: Red, tag: 0
// name: Green, enum: Green, tag: 1
// name: Blue, enum: Blue, tag: 2
}
}
Algebraic Data Types
enum
を用いて、代数的データ型の記述を楽にすることができるようになります。
// 代数的データ型
object AlgebraicDataTypes {
enum Option[+T] {
case Some(x: T)
case None
}
// 大体以下と同じ
trait Option2[+T]
object Option2 {
case class Some[T](t: T) extends Option2[T]
case object None extends Option2[Nothing]
}
}
Other New Features
Multiversal Equality
==
による比較が、Eq
型クラスによる比較に変更されます。
デフォルトでは、eqAny
によって、現状と同じ挙動となります。2
object MultiversalEquality {
// import scala.language.strictEquality
// ドキュメントによると↑をimportしない限り eqAnyが有効らしいんだけど、
// 関係なく無効になってるっぽい
implicit def eqAny[A, B]: Eq[A, B] = scala.Eq.eqAny[A, B]
val t1 = "1" == 1 // Values of types String and Int cannot be compared with == or !=
val t2 = 1 == 1
}
Trait Parameters
trait がコンストラクタ引数を受け取れるようになります。
object TraitParameters {
trait Greeting(val name: String) {
def msg = s"How are you, $name"
}
class C(val s: String) extends Greeting(s) {
println(msg)
}
}
これによって、traitの型引数が、型クラスのインスタンスになっている
という制約を(protected valなどにせず)直接書けるようになります。
object TraitParameters2 {
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def map[F[_]: Functor, A, B](fa: F[A])(f: A => B): F[B] = implicitly[Functor[F]].map(fa)(f)
}
trait FunctorParam[F[_]: Functor] {
def map[A, B](fa: F[A])(f: A => B): F[B] =
Functor.map(fa)(f)
}
}
Inline
これまでの、@inline
アノテーションよりも強力な、inline
キーワードが追加されました。
inline
はval
とdef
の両方に宣言可能です。
object Inline {
// inline val の右側は定数式でなければならない
inline val flag = false
// right-hand side of inline value readLine must be a constant expression
// inline val readLine = System.in.read()
inline def checkFlag[T](ifTrue: =>T, ifFalse: =>T): T = {
if(flag) ifTrue else ifFalse
}
inline def inlineArg(inline v: Int): Int = v + 2
val v = inlineArg(4)
// argument to inline parameter must be a constant expression
// inlineArg("4".toInt)
}
inline
の展開は、単純なインライン展開でなく、コンパイラが必要なところだけを残してくれます。
object InlinePow {
inline def power(x: Double, n: Int): Double =
if (n == 0) 1.0
else if (n == 1) x
else {
val y = power(x, n / 2)
if (n % 2 == 0) y * y else y * y * x
}
def expr = "10.0".toDouble
val pow10_10 = power(expr, 10)
// 以下のようなコードにコンパイルされる
// double x = expr();
// double y = x;
// double y = y * y;
// double y = y * y * x;
// this.pow10_10 = (y * y);
}
Principled Meta Programming
'
、~
を用いた、コンパイル時メタプログラミングが追加されます。
'
により、通常の値をExpr
に変換します。
~
により、Expr
を通常の値に変換します。 3
object MetaPrograming {
import scala.quoted._
def booleanExpr: Expr[Boolean] = '(true)
inline def trueImpl = ~booleanExpr
val tBooleanType: Type[Boolean] = '[Boolean]
inline def trueOrFalse: Boolean = ~trueOrFalseImpl
def trueOrFalseImpl: Expr[Boolean] = '{
type Bool = ~tBooleanType
val tTrue: Bool = true
tTrue || false
}
// splice outside quotes or inline method
// val tBool = ~tBooleanExpr
// type Bool = ~tBooleanType
}
これを用いて、これまでのdef
マクロと比べて、遥かに簡単に、マクロを定義できます。
以下の例では、map
メソッドを、ループで構築し、インライン展開するようなマクロを定義しています。
object MetaListMap {
import scala.quoted._
inline def map[A, B](aList: List[A])(f: A => B): List[B] = {
~mapImpl('[B], '(aList), '(f))
}
private def mapImpl[A: Type, B](
aTB: Type[B],
aList: Expr[List[A]],
f: Expr[A => B]) = '{
var tList = ~aList
val tLen = tList.length
val tBuffer = new scala.collection.mutable.ListBuffer[~aTB]
while(tList.nonEmpty) {
tBuffer.append(~f('(tList.head)))
tList = tList.tail
}
tBuffer.result
}
}
object UseMetaListMap {
def baseList = List(1,2,3)
def mappedList = MetaListMap.map(baseList)(_ + 2)
def main(args: Array[String]): Unit = {
println(mappedList)
// List(3, 4, 5)
}
}
javap結果(長いので折りたたみ)
// mapがループで構成されている
// public scala.collection.immutable.List<java.lang.Object> mappedList();
// Code:
// 0: aload_0
// 1: invokevirtual #47 // Method baseList:()Lscala/collection/immutable/List;
// 4: astore_1
// 5: aload_0
// 6: invokedynamic #64, 0 // InvokeDynamic #0:apply$mcII$sp:(Ljp/seraphr/sandbox/dotty/UseMetaListMap$;)Lscala/compat/java8/JFunction1$mcII$sp;
// 11: astore_2
// 12: aload_1
// 13: astore_3
// 14: aload_3
// 15: invokeinterface #70, 1 // InterfaceMethod scala/collection/LinearSeqOptimized.length:()I
// 20: istore 4
// 22: new #72 // class scala/collection/mutable/ListBuffer
// 25: dup
// 26: invokespecial #73 // Method scala/collection/mutable/ListBuffer."<init>":()V
// 29: astore 5
// 31: aload_3 *** if(tList.nonEmpty)
// 32: invokeinterface #79, 1 // InterfaceMethod scala/collection/TraversableOnce.nonEmpty:()Z
// 37: ifeq 90
// 40: aload 5 *** このあたりから、appendの引数になるArray作成
// 42: getstatic #32 // Field scala/Predef$.MODULE$:Lscala/Predef$;
// 45: iconst_1
// 46: newarray int
// 48: dup
// 49: iconst_0
// 50: aload_3
// *** start f(tList.head)
// 51: invokevirtual #85 // Method scala/collection/immutable/List.head:()Ljava/lang/Object;
// 54: invokestatic #91 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
// 57: istore 6
// 59: aload_2
// 60: iload 6
// 62: invokestatic #95 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
// 65: invokeinterface #100, 2 // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
// *** end f(tList.head)
// 70: invokestatic #91 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
// 73: iastore
// 74: invokevirtual #38 // Method scala/LowPriorityImplicits.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
// *** ここまで、appendのArray作成
// 77: invokeinterface #106, 2 // InterfaceMethod scala/collection/mutable/BufferLike.append:(Lscala/collection/Seq;)V
// 82: aload_3
// 83: invokevirtual #109 // Method scala/collection/immutable/List.tail:()Lscala/collection/immutable/List;
// 86: astore_3
// 87: goto 31 *** ループ
// 90: aload 5
// 92: invokevirtual #112 // Method scala/collection/mutable/ListBuffer.result:()Lscala/collection/immutable/List;
// 95: areturn
TASTy Reflect
Principled Meta Programming
だけでは不可能な、構文木の分解と構築を可能とします。
object TastyReflect {
import scala.quoted._
import scala.tasty._
inline def natConst(x: Int): Int = ~natConstImpl('(x))
def natConstImpl(x: Expr[Int])(implicit reflection: Reflection): Expr[Int] = {
import reflection._
// ドキュメント上は unseal になっている。 最近のコミット( 1af9030c8a01727d )で unseal になったっぽい
val xTree: Term = x.reflect
xTree match {
case Term.Literal(Constant.Int(n)) =>
if (n <= 0)
throw new QuoteError("Parameter must be natural number")
n.toExpr
case _ =>
throw new QuoteError("Parameter must be a known constant")
}
}
}
object NatConst {
val one = TastyReflect.natConst(1)
// コンパイルエラー Parameter must be natural number
// val error = TastyReflect.natConst(0)
}
// 以下のような感じのコードになる
// public final class NatConst$
// {
// private final int one;
// public int one()
// {
// return this.one;
// }
// public NatConst$()
// {
// this.one = 1;
// }
// }
また、dottyがコンパイル時に出力しているtastyファイルに対してアクセスすることも可能です。
ただし、実行時の依存ライブラリに、dotty-compiler
が必要になります。
// InspectTastyFile 用 object
// コンパイル単位をわけないと、no class file was found
object Hoge {
val fuga = 1
}
object InspectTastyFile {
import scala.tasty._
import scala.tasty.file._
class Consumer extends TastyConsumer {
final def apply(reflect: Reflection)(root: reflect.Tree): Unit = {
import reflect._
println(root.showCode)
}
}
}
object InspectTastyFileMain {
// Windows では、ファイルパスの取り扱いに失敗するらしく実行時エラー
def main(args: Array[String]): Unit = {
import scala.tasty.file._
import InspectTastyFile.Consumer
ConsumeTasty("", List("jp.seraphr.sandbox.dotty.Hoge"), new Consumer)
// 以下がprintされる
// package sandbox.dotty {
// object Hoge {
// val fuga: scala.Int = 1
// }
// }
}
}
Opaque Type Aliases
別の型として取り扱われる、type aliasが追加されます。
object OpaqueTypeAliases {
// opaque type は、コンパニオンオブジェクト内でのみ、同じ型とみなされる
opaque type UserId = String
object UserId {
def apply(base: String): UserId = base
implicit class UserIdOps(id: UserId) {
def asString: String = id
}
}
opaque type ItemId = String
object ItemId {
def apply(base: String): ItemId = base
implicit class UserIdOps(id: ItemId) {
def asString: String = id
}
}
}
object UserOpaqueType {
import OpaqueTypeAliases._
val tString = "string"
val tUserId = UserId("user1")
val tItemId = ItemId("item1")
// コメントアウトしているものはコンパイルエラー
val tId1: UserId = tUserId
// val tId2: String = tUserId
// val tId3: ItemId = tUserId
// val tId4: UserId = tItemId
// val tId5: String = tItemId
val tId6: ItemId = tItemId
// val tId7: UserId = tString
val tId8: String = tString
// val tId9: ItemId = tString
val tId10: String = tUserId.asString
val tId11: String = tItemId.asString
}
Implicit By-Name Parameters
メソッドのimplicit引数を、名前呼びにすることが出来るようになります。
現状では、implicit探索が発散してしまうようなパターンの一部がコンパイル可能となります。
// scala 2.13にも入る
object ImplicitByNameParameters {
trait Foo {
def next: Foo
}
// rec を by-nameにしないとコンパイルエラー
// in dotty 0.10
// object Foo produces a diverging implicit search
// in scala 2.12.6
// diverging implicit expansion for type ImplicitByNameParameters.Foo
object Foo {
implicit def foo(implicit rec: =>Foo): Foo =
new Foo { def next = rec }
}
val foo = implicitly[Foo]
def main(args: Array[String]): Unit = {
println(foo == foo.next) // false
}
}
Automatic Tupling of Function Parameters
http://dotty.epfl.ch/docs/reference/auto-parameter-tupling.html
http://dotty.epfl.ch/docs/reference/auto-parameter-tupling-spec.html
case
でパターンマッチをしなければならなかった、パターンの一部がcase
なしで書けるようになります。
object AutoParameterTupling {
val tList = List((1,2), (2,3), (3,4))
val tOld = tList.map {
case (l, r) => l + r
}
// scala 2ではコンパイルエラー
val tNew = tList.map {
(l, r) => l + r
}
val tNew2 = tList.map(_ + _)
// こちらは駄目
// val f: (Int, Int) => Int = _ + _
// tList.map(f)
}
Named Type Arguments
メソッドの名前付き引数のように、名前を指定して型引数を埋めることが出来るようになります。
object NamedTypeArguments {
def construct[Elem, Coll[_]](xs: Elem*): Coll[Elem] = ???
val xs2 = construct[Coll = List, Elem = Int](1, 2, 3)
// 一部だけ指定して残りを推論させられる!!
val xs3 = construct[Coll = List](1, 2, 3)
}
Erased Terms
コンパイル時にのみ必要となる、implicit引数を、除去した状態でコンパイル出来るようになります。
object ErasedTerms {
trait ON
trait OFF
case class Switch[S]() {
def on(implicit erased ev: =:=[S, OFF]) = Switch[ON]()
def off(implicit erased ev: S =:= ON) = Switch[OFF]()
}
Switch[OFF]()
.on
.off
// コンパイル結果は以下のように evが消えている
// ErasedTerms.Switch..MODULE$.apply().on().off();
// erased 無しだと
// ErasedTerms.Switch..MODULE$.apply()
// .on(Predef..eq.colon.eq..MODULE$.tpEquals())
// .off(Predef..eq.colon.eq..MODULE$.tpEquals());
}
Kind Polymorphism
あらゆるkind4の型を受け取る型引数を宣言出来るようになります。
// "-Ykind-polymorphism" が必要
object KindPolymorphism {
trait Foo[+A <: AnyKind] {
def rank: Int
}
case class FooImpl(rank: Int) extends Foo[Nothing]
case class Triple[A, B, C]()
object Foo {
implicit def foo1: Foo[List] = FooImpl(1)
implicit def foo2: Foo[Map] = FooImpl(2)
implicit def foo3: Foo[Triple] = FooImpl(3)
}
def foo[A <: AnyKind](implicit ev: Foo[A]): Int = ev.rank
def main(args: Array[String]): Unit = {
println(foo[List]) // => 1
println(foo[Map]) // => 2
println(foo[Triple]) // => 3
}
}
Changed Features
Lazy Vals
lazy val
に対して、スレッドセーフな実装が生成されなくなります。
object VolatileLazyVals {
// lazy val がスレッドセーフじゃなくなりました
lazy val hoge = 10
// thread safeにしたいときは volatileつけましょう
@volatile
lazy val fuga = 10
}
Programmatic Structural Types
JavaVM以外のプラットフォームで、うまく構造的部分型が取り扱えなかった問題に対応するため、構造的部分型が、Javaのリフレクションに依存したものから、より抽象的なものに分離されました。
構造的部分型は、Selectable
型に依存するようになります。
object StructuralTypes {
// JavaVM以外のプラットフォームで、うまく構造的部分型が取り扱えなかった問題に対応するため
// 構造的部分型が、Javaのリフレクションに依存したものから、より抽象的なものに分離されました。
// implicit に Selectable が見えていないと、コンパイルエラーになります。
import scala.reflect.Selectable.reflectiveSelectable
// Selectableは以下のような型
// trait Selectable extends Any {
// def selectDynamic(name: String): Any
// def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
// new UnsupportedOperationException("selectDynamicMethod")
// }
type HasClose = {
def close(): Unit
}
class Hoge {
def close(): Unit = ()
}
val tHoge: HasClose = new Hoge
tHoge.close()
}
Changes in Type Checking
ドキュメントが空っぽなので、謎。
Changes in Type Inference
ドキュメントが空っぽなので、謎。
だけど、いくつか把握しているものがあるので列挙します。
method independent typeが、同じ引数リスト内で使えるようになった
object MethodIndependentType {
trait A {
type T
}
object B extends A {type T = String}
object C extends A {type T = Int}
def method(a: A, t: a.T): Unit = ()
method(B, "")
method(C, 1)
}
同じ引数リスト内で、前の引数の推論結果を使用可能になった
object SameArgList {
def foldLeft[A, B](c: List[A], zero: B, f: (B, A) => B): B = ???
// scala2.12 ではコンパイルエラー missing parameter type
foldLeft(List(1), "", _ + _)
}
別の後ろの引数リストでの型推論結果が、前の引数リストにフィードバックされるようになった
object LubDiffArgList {
def fold[A, B](o: Option[A])(zero: B)(f: A => B): B = ???
// scala2.12 ではコンパイルエラー error: type mismatch
val list = fold(Option(1))(Nil)(List(_))
}
さようなら AUXパターン
// さようなら AUXパターン
object ByeByeAuxPattern {
trait HasSize[A] {
type Size
def size(a:A): Size
}
trait Monoid[A] {
val zero: A
def append(l: A, r: A): A
}
// scala2.12 ではコンパイルエラー error: illegal dependent method type
def method[A](a1: A, a2: A)(implicit S: HasSize[A], M: Monoid[S.Size]): S.Size =
M.append(S.size(a1), S.size(a2))
}
Changes in Implicit Resolution
メンバの implicit val には、型を明示しないといけない
implicit val hoge: Int = 10
スコープのネストが、探索順位に影響するようになった
// scala2.12 ではコンパイルエラー
trait Z
def f(implicit i: Z) = {
def g(implicit j: Z) = {
implicitly[Z]
}
}
implicit探索の失敗伝播
再帰的なimplicit 探索で、implicitな候補が複数見つかって失敗した時に、その失敗が伝播するようになりました。
以下のimplicitly[C]
は scala 2.12ではコンパイルが通る(def cが選ばれる)が、dottyではエラーとなります。
trait I {
class A
class B extends C
class C
implicit def a1: A
implicit def a2: A
implicit def b(implicit a: A): B
implicit def c: C
// implicitly[C]
// 上記の影響で、「Aが失敗したらBが成功する」というのが実現できなくなったためか、Notが導入
// らしいが、scala2でうまく行って、dottyでうまく行かない、具体例が作れなかった…
implicitly[scala.implicits.Not[C]]
}
implicit searchが発散した場合の取り扱いが変わった
implicit searchが発散した場合(implicitの展開が再帰的に行われてしまうような場合など)の失敗が、通常の探索の失敗と同じ扱いになりました(= 他の候補の探索を継続する)。
scala 2では発散した場合(殆どの場合)失敗していた。5
Implicit Conversions
http://dotty.epfl.ch/docs/reference/changed/implicit-conversions.html
http://dotty.epfl.ch/docs/reference/changed/implicit-conversions-spec.html
abstract class ImplicitConverter[-T, +U] extends Function1[T, U]
が導入され、型の暗黙的変換は、ImplicitConverter
が無いとダメになりました。
と以前のドキュメントには書いてあったんだけど、2018/12/03時点のドキュメント見ると、また変わりそう?
とりあえず、0.11.0-RC1
時点では、まだ挙動は変わってなさそう?
object ImplicitConversions {
import scala.language.implicitConversions
// def ng(implicit ev: Int => String): String = 10
def ok(implicit ev: ImplicitConverter[Int, String]): String = 10
}
Vararg Patterns
可変長のパターンマッチの記法が変わりました。
また、パターンマッチの定義時、unapplySeq
と同じことが、unapply
で出来るようになりました。
object VarargPatterns {
List(1,2,3,4) match {
// case List(1,2, _*) =>
// case List(1,2, xs @ _*) =>
case List(1,2, _: _*) =>
case List(2,3, xs: _*) =>
case _ =>
}
// unapplyで unapplySeqと同じことができるようになった
class Person(val name: String, val children: Person *)
object Person {
def unapply(p: Person) = Some((p.name, p.children))
// def unapplySeq(p: Person) = Some((p.name, p.children))
}
def childCount(p: Person) = p match {
case Person(_, ns : _*) => ns.length
}
}
Option-less pattern matching
パターンマッチの定義時の制約が全般に緩くなりました。
ただ、ドキュメントによると、また(よりシンプルに)変わりそうな雰囲気?
boolean パターン
// unapplyでBooleanを返す
object Even {
def unapply(s: String): Boolean = s.size % 2 == 0
}
Product Pattern
// unapplyでProductを継承して def / valで_Nの名前を持つオブジェクトを返す
class FirstChars(s: String) extends Product {
def _1 = s.charAt(0)
def _2 = s.charAt(1)
// Not used by pattern matching: Product is only used as a marker trait.
def canEqual(that: Any): Boolean = ???
def productArity: Int = ???
def productElement(n: Int): Any = ???
}
object FirstChars {
def unapply(s: String): FirstChars = new FirstChars(s)
}
Name-based Seq Pattern
// unapplySeqで以下のXを取得できるオブジェクトを返す
type T1
type T2 <: T1
type T3 <: T1
type X = {
def lengthCompare(len: Int): Int // or, `def length: Int`
def apply(i: Int): T1
def drop(n: Int): scala.Seq[T2]
def toSeq: scala.Seq[T3]
}
object CharList {
def unapplySeq(s: String): Option[Seq[Char]] = Some(s.toList)
}
Name-based Pattern
// unapplyで isEmpty と get を持っているオブジェクトを返す
// もしくは isEmptyと _N を持っているオブジェクトを返す
class Nat(val x: Int) {
def get: Int = x
def isEmpty = x < 0
}
Automatic Eta Expansion
eta-expansion時の_
が不要になりました。
ただし、空の引数リストを持つメソッドは、自動展開されません。
object EtaExpansion {
// _ が要らなくなった。 将来的にdeprecatedになる
def m(x: Boolean, y: String)(z: Int): List[Int] = List()
val f1 = m // (Boolean, String) => Int => List[Int]
val f2 = m(true, "abc") // Int => List[Int]
// 空の引数リストのメソッドは、自動的には展開されない
// Auto-Applicationと競合するため
def m2(): Int = 10
// val f3 = m2
}
Changes in Compiler Plugins
省略
Dropped Features
Delayedinit
DelayedInit
がなくなり、それに伴いApp
クラスも、旧Application
クラス相当のモノになります。6
そのため、普通に main
メソッドを定義しましょう。
Macros
今までのマクロは消えます。
inline
によるマクロが実装されました。
また、コンパイル前のソースコードを生成を行うmeta
キーワードによるメタプログラミングが、Scalametaで実装されます。7
Existential Types
forSome
による存在型がなくなり、_
による、ワイルドカードのみとなります。
object ExistentialTypes {
// _ のみになる
type A = List[_]
// 一応 forSomeがなくなるとできなくなることはあるが、まず困らない
// 以下は、Scala 2.12のコードだが、 これの T1が定義できなくなる
// trait C[A] {
// def accept(a: A): Unit = ()
// }
// case object C1 extends C[Int]
// type T1 = C[C[A]] forSome { type A }
// type T2 = C[C[A] forSome { type A }] // == C[C[_]]
// def t1: Unit = {
// val v: T1 = ???
// v.accept(C1) // type mismatch; found: C1.type required: C[A]
// }
// def t2: Unit = {
// val v: T2 = ???
// v.accept(C1) // OK
// }
}
General Type Projection
abstract type に対する type projectionが禁止されます。8
object GeneralTypeProjection {
type A = {type X}
trait B {type X}
class C {type X}
type D = C
trait Hoge[AA <: A, BB <: B, CC <: C, DD <: D] {
type AX = A#X
type BX = B#X
type CX = C#X
type DX = D#X
// 以下は全てダメ scala2.12では全部OK
//type AAX = AA#X
//type BBX = BB#X
//type CCX = CC#X
//type DDX = DD#X
}
}
Procedure Syntax
ついに、Procedure Syntaxが消えます。9
object ProcedureSyntax {
// def hoge {}
def hoge = {}
}
Early Initializers
おそらく、使ったことのある人がめちゃくちゃ少ないEarly Initializers
が消えます。
trait parameters
を使いましょう。
object EarlyInitializers {
// 初期化順序をいじる構文。
// trait parameters 使えば良い
trait A {
val a: String
val b = a // 普通に継承すると、bがnullになる!
}
// class C extends { val a = "hoge"} with A
}
Class Shadowing
super type内で定義されているのと同名の型を定義できなくなりました
object ClassShadowing {
// super type と同名の型を定義できなくなった
trait Super {
class A
}
trait Sub extends Super {
// class A
}
}
Limit 22
関数型や、Tuple型の22制限がなくなりました。
object Limit22 {
trait A
def f: (
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A) => A = ???
def t: (
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A,
A, A, A, A, A) = ???
}
XML Literals
XMLリテラルがなくなりました。
object XMLLiterals {
// 代わりにXML string interpolation
// らしいが、多分まだ実装されてない
// https://github.com/scala/scala-xml/issues/248
// val x = xml"""<A></A>"""
}
Auto-Application
空の引数リストを持つメソッドを、()
なしで呼ぶ(自動的に()
が挿入される)ことができなくなりました。
ただし、Javaで定義されたメソッドや、Scala 2で定義されたメソッドは、AutoApplicationが残ります。
object AutoApplication {
def method() = ()
// val t = method // コンパイルエラー
// Java で定義されたメソッドは AutoApplicationが残る
val t = 1.toString
// scala 2で定義されたメソッドもAutoApplicationが残る
// Numeric#Ops の toIntは、何故か`()`が付いている
def toInt[N](n: N)(implicit ev: Numeric[N]): Int = {
import ev._
n.toInt
}
}
Weak Conformance
Javaのprimitiveな数値型と同等の変換を行うために定義されていた10WeakConformanceが削除されました。
代わりに、変換しても情報の欠落がないときだけ、型を拡張するようになります。
object WeakConformance {
inline val b = 33
def f(): Int = b + 1
List(b, 33, 'a') : List[Int]
List(b, 33, 'a', f()) : List[Int]
List(1.0f, 'a', 0) : List[Float]
List(1.0f, 1L) : List[Double]
List(1.0f, 1L, f()) : List[AnyVal] // fは定数では無いので、情報欠落が無いことを保証できない
List(1.0f, 1234567890): List[AnyVal] // 1234567890 は Floatでは表現出来ない
}
終わりに
思ったより長かった・・・
-
許すと、unsoundになるらしい…? https://github.com/lampepfl/dotty/issues/1551 ↩
-
とドキュメントには書いてあるが、現状の挙動は、常にstrictに見える ↩
-
ただし、メタ世界でしか利用できない ↩
-
型引数の数 ↩
-
らしいが、scala2でうまく行いかなくて、dottyでうまく行く、具体例が作れなかった… ↩
-
コマンドライン引数にアクセス出来ない & JIT効かない ↩
-
2018/10 時点で軽く調べた範囲だと、本当にどうなるのかはよく分からなかった… ↩
-
unsoundとのこと。 https://github.com/lampepfl/dotty/issues/1050 ↩
-
scalaを触り始めた2011年には既に非推奨だったな… ↩
-
多分… ↩