20
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ScalaAdvent Calendar 2018

Day 4

dottyのドキュメントにある新機能を一通り試す(dotty-0.11.0-RC1)

Last updated at Posted at 2018-12-03

やることとか前提

  • 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やMap3State#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キーワードが追加されました。
inlinevaldefの両方に宣言可能です。

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では表現出来ない
}

終わりに

思ったより長かった・・・

  1. 許すと、unsoundになるらしい…? https://github.com/lampepfl/dotty/issues/1551

  2. とドキュメントには書いてあるが、現状の挙動は、常にstrictに見える

  3. ただし、メタ世界でしか利用できない

  4. 型引数の数

  5. らしいが、scala2でうまく行いかなくて、dottyでうまく行く、具体例が作れなかった…

  6. コマンドライン引数にアクセス出来ない & JIT効かない

  7. 2018/10 時点で軽く調べた範囲だと、本当にどうなるのかはよく分からなかった…

  8. unsoundとのこと。 https://github.com/lampepfl/dotty/issues/1050

  9. scalaを触り始めた2011年には既に非推奨だったな…

  10. 多分…

20
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?