Scala
Dotty

[Scala] Dotty 0.5-RC1 を触ってみた

はじめに

Scalaの次世代コンパイラ(?)であるDottyが2017/11/30時点で0.5-RC1までリリースされていたので、試しに触ってみました。
この記事では、公式の説明を見ても良く分からなかったり、(多分)書いていないので気になったところをいくつか書いています。

Intersection Types で明らかに不可能な交差がある場合

Intersection Types とは「AでもBでもある型」をA & Bのように記述できる機能です。
で、例えばInt & Stringとか書くと「IntでもStringでもある型」になるわけですが、実際にそんな型の値を生成することは(多分)不可能ですので、どうなるか見てみました。

def aaa(a: Int & String) = a.toString

aaa(3)
aaa("aaa") 

コンパイルすると以下のような感じになりました。

1 |aaa(3)
  |    ^
  |    found:    Int(3)
  |    required: Int & String
  |    
1 |aaa("aaa")
  |    ^^^^^
  |    found:    String("aaa")
  |    required: Int & String
  |    

Int & Stringという定義自体は特にエラーとならないようです。
ただし、IntもStringも受け付けてくれません。

いやそんな型出てこないでしょ…

戻り型以外が同じメソッドを持つ2つのトレイトをミックスインすると、そのメソッドの戻り型が2つの戻り型のIntersection Typesとなるため、以下のような状況でInt & Stringが出てきます。

trait A { def aaa(a: Int) = 3 }
trait B { def aaa(a: Int) = "3" }

trait C extends A with B { override def aaa(a: Int) = 3 }
1 |trait C extends A with B { override def aaa(a: Int) = 3 }
  |                                                      ^
  |                                                    found:    Int(3)
  |                                                    required: String & Int

出てきたらどうすればいいんでしょうね…

UnionTypes で組み合わせた(何の関係も無い)2つの型が同じシグネチャのメソッドを持っていた場合

UnionTypes とは「AまたはBである型」をA | Bのように記述できる機能です。
で、通常はパターンマッチで型を特定してから処理することになりますが、もし両方の型が偶然同じシグネチャのメソッドを持っていたら直接呼び出せるのか見てみました。

trait A { def aaa(a: Int) = 3 }
trait B { def aaa(a: Int) = 3 }

def sample(ab: A | B) = ab.aaa(1)

コンパイルすると以下のような感じになりました。

1 |def sample(ab: A | B) = ab.aaa(1)
  |                        ^^^^^^
  |                        value `aaa` is not a member of A | B

偶然同じでもダメみたいですね。
もちろん、共通のスーパータイプを持つ場合、そのスーパータイプのメソッドを直接呼び出すことは可能です。

trait SuperType { def superMethod(a: Int) = 1 }
trait SubType1 extends SuperType { def subMethod1(a: Int) = 2 }
trait SubType2 extends SuperType { def subMethod2(a: Int) = 3 }

def aaa(a: SubType1 | SubType2) = a.superMethod(1)

Phantom Types + Implicit Function Types

Phantom Types とは、コンパイル時専用の型を定義できる機能です(多分)。
Implicit Function Types とは、暗黙の引数を取る関数をメソッドの引数等に指定できる機能です。
で、Implicit Function Types が取る暗黙の引数を Phantom Types にしたらどうかなーと思ったので見てみました。

object SamplePhantomTypes {
  import Phantom1._
  def apply =
    first {
      second {
        third {
          forth
        }
      }
    }
  def first(a: implicit PhantomA => Unit) = {
    implicit val pA = phantomA
    a
  }
  def second(a: implicit PhantomB => Unit)(implicit pA: PhantomA) = {
    implicit val pB = phantomB
    a
  }
  def third(a: implicit PhantomC => Unit)(implicit pB: PhantomB) = {
    implicit val pC = phantomC
    a
  }
  def forth(implicit pC: PhantomC) = {
    ()
  }
}

object Phantom1 extends Phantom {
  type PhantomA <: this.Any
  type PhantomB <: this.Any
  type PhantomC <: this.Any

  def phantomA: PhantomA = assume
  def phantomB: PhantomB = assume
  def phantomC: PhantomC = assume
}

コンパイルすると以下のような感じになりました。

13 |  def first(a: implicit PhantomA => Unit) = {
   |                        ^
   |Type argument Phantom1.PhantomA does not conform to upper bound Any 
   | (Note that the types are in different universes, see Phantom types)
17 |  def second(a: implicit PhantomB => Unit)(implicit pA: PhantomA) = {
   |                         ^
   |Type argument Phantom1.PhantomB does not conform to upper bound Any 
   | (Note that the types are in different universes, see Phantom types)
21 |  def third(a: implicit PhantomC => Unit)(implicit pB: PhantomB) = {
   |                        ^
   |Type argument Phantom1.PhantomC does not conform to upper bound Any 
   | (Note that the types are in different universes, see Phantom types)

Implicit Function Types(の実体として存在するであろうImplicitFunction1みたいなやつ)の型パラメータはscala.Anyのサブタイプでなければならないが、Phantom Typesはscala.Anyのサブタイプではないからエラーとなるみたいですね。

Literal Singleton Types + Union Types

Literal Singleton Types とは、1とか"sample"のようなプリミティブな値そのものを型として扱うことができる機能です。
で、これをUnion Typesと組み合わせれば、型レベルで特定の値しか取らなかったり、特定の値しか返さないメソッドが定義できるのでは、と思ったので見てみました。

  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2

コンパイルすると以下のような感じになりました。

5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |           ^
  |           Singleton type Int(1) is not allowed in a union type
5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |               ^
  |               Singleton type Int(2) is not allowed in a union type
5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |                   ^
  |                   Singleton type Int(3) is not allowed in a union type
5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |                       ^
  |                      Singleton type Int(2) is not allowed in a union type
5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |                           ^
  |                      Singleton type Int(4) is not allowed in a union type
5 |  def b(i: 1 | 2 | 3): 2 | 4 | 6 = i * 2
  |                               ^
  |                      Singleton type Int(6) is not allowed in a union type

Union TypesでLiteral Singleton Typesを使用することはできないみたいですね。
ググった限りでは、もともとLiteral Singleton Types + Union Typesにするとコンパイル時の型チェックがうまくいかない問題があったみたいです。
それで現時点では使えないようになっているのか、恒久的に使えないのかは良く分かりませんでした…

終わりに

ここでは取り上げていませんが、Dottyには他にも
- 列挙型・代数的データ型を簡潔に定義できる(+コンパイル時にパターンマッチの網羅性を検証できる)機能
- 異なる型同士を==!=で比較するとコンパイルエラーになる機能
- トレイトがパラメータを取れる機能
- 暗黙の引数を名前渡しにできる機能
等様々な新機能があるようです。
正式リリース前にもっと検証していけたらと思います。