忙しい人のためのScala3予習メモ

この記事は、 [FOLIO Advent calendar 2020](https://adventar.org/calendars/5553) の22日目の記事です。

## これは何?

「Scala3の予習したいけど時間がない！」「まだM3だしガチでキャッチアップするのはちょっと…」という人のために、公式ドキュメントを斜め読みした自分が仕事で影響が大きそうなScala3の変更点をまとめたものです。

ほとんどのサンプルコードは、[Dottyの公式ドキュメント](https://dotty.epfl.ch/docs/usage/scala3doc/)から引用しております。

## val,def,型エイリアスがトップレベルに書けるようになった

Scala2では、全てのval,def,型エイリアスは何かしらのclass,trait,objectなどの中に書かなければエラーになりましたが、Scala3では(まるでREPLのように)class外にdefやvalを定義することができるようになりました。`package object`が要らなくなりますね。

```scala:example/foo.scala
package example

def f = println("Hello, World!")
```

## インデントベースでコードが書ける

Scala3ではPythonのようにインデントで構文を書くことができます。これは少し前に話題になりましたね。

```scala:greet.scala
def greet =
  println("Hello")
  println("World!")
```

`-noindent`フラグで一応無効もできるようです。

## Intersection Types と Union Types

TypeScriptなど最近の言語ではおなじみとなった`Intersection Types` と `Union Types`がScalaでも使えるようになりました。

#### Intersection Types

`&`オペレータを使い、`A & B`と書くことで`AでありBでもある型の値`を表現することができます。  
以下は、`Resettable`と`Growable[String]`両方を満たす型の値を受け取る関数`f`を定義しているサンプルコードです。

```scala
trait Resettable:
   def reset(): Unit

trait Growable[T]:
   def add(t: T): Unit

def f(x: Resettable & Growable[String]) =
   x.reset()
   x.add("first")
```

#### Union Types

こちらは、`|`オペレータを使い、`A | B`と書くことで`AとBに属する全ての値を持つ型` を表現することができます。

```scala
case class UserName(name: String)
case class Password(hash: Hash)

def help(id: UserName | Password) =
   val user = id match
      case UserName(name) => lookupName(name)
      case Password(hash) => lookupPassword(hash)
   ...
```

## enumが追加された

Scala2でもいくつか列挙型の定義方法がありましたが、新たに`enum`が追加されました。  

```scala
enum Color:
   case Red, Green, Blue
```

上記コードは、`Color.Red`, `Color.Green`, `Color.Blue`の3つの値を持つ`Color`という名前の`sealed class`を定義します。値はコンパニオンオブジェクトの中に定義されます。

`enum`は`sealed trait`と同じようにメソッドを生やすことができます。

```scala
enum Color:
   case Red, Green, Blue
   def isRed: Boolean = this == Color.Red
```

#### パラメータ付きenum

以下のように、パラメータ付きの`enum`を定義することもできます。`sealed abstract class` と `sealed trait`を使い分ける必要がなくなりますね。

```scala
enum Color(val rgb: Int):
   case Red   extends Color(0xFF0000)
   case Green extends Color(0x00FF00)
   case Blue  extends Color(0x0000FF)
```

enumの値はcaseの並び順で0から採番されており、`ordinal`で整数値に変換したり`fromOrdinal`でenumの値に戻すことができます。

```scala
scala> Color.Red.ordinal
val res2: Int = 0
scala> Color.fromOrdinal(res2)
val res3: Color = Red
```

それ以外にも、文字列から値を作る`valueOf`や全ての値を得ることができる`values`などのAPIも用意されており、まるでボイラープレートの少ない[enumeratum](https://github.com/lloydmeta/enumeratum)のような機能となっています。

```scala
scala> Color.valueOf("Blue")
val res0: Color = Blue
scala> Color.values
val res1: Array[Color] = Array(Red, Green, Blue)
```

#### 代数的データ型(ADT)としてのenum

`enum`はADTを定義するのにも利用できます。以下は、`enum`でOptionを定義するコードです。

```scala
enum Option[+T]:
   case Some(x: T)
   case None
```

パラメータ付きの`case Some(x: T)`は、`Option[T]`を継承する`case class`を簡潔に定義するための記法です。 
` case None`は、Tが共変のため、`Option[Nothing]`に推論されています。

`extends`句は以下のように明示的に書くこともできます。

```scala
enum Option[+T]:
   case Some(x: T) extends Option[T]
   case None       extends Option[Nothing]
```



## traitがパラメータを持てるようになった

Scala2ではパラメータのありなしで`abstract class`と`trait`使い分けたりしてましたが、その必要がなくなりそうです。

```scala
trait Greeting(val name: String):
   def msg = s"How are you, $name"

class C extends Greeting("Bob"):
   println(msg)
```


## Implicitが色々変わった

今までややこしくて「怖い」などと言われていたImplicitが整理されました。

#### Implicit Parametersはgivenとusingになった

`implicit`パラメータの定義はgiven、使う側はusingと書くようになりました。

以下は、`Ordering`ライクな型クラス`Ord`と`Ord[Int]`,`Ord[List[T]]`のgivenを定義するサンプルコードです。

```scala
trait Ord[T]:
   def compare(x: T, y: T): Int
   extension (x: T) def < (y: T) = compare(x, y) < 0
   extension (x: T) def > (y: T) = compare(x, y) > 0

given intOrd: Ord[Int] with
   def compare(x: Int, y: Int) =
      if x < y then -1 else if x > y then +1 else 0

given listOrd[T](using ord: Ord[T]): Ord[List[T]] with

   def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match
      case (Nil, Nil) => 0
      case (Nil, _) => -1
      case (_, Nil) => +1
      case (x :: xs1, y :: ys1) =>
         val fst = ord.compare(x, y)
         if fst != 0 then fst else compare(xs1, ys1)
```

givenは無名でも定義することができます。

```scala
given Ord[Int] with
   ...
given [T](using Ord[T]): Ord[List[T]] with
   ...
```

### Alias givens

エイリアスでgivenインスタンスを定義することもできます。
この例では、最初の`global`へのアクセス時のみ`ForkJoinPool()`が評価され、以降のアクセスはそれを返却します。

```scala
given global: ExecutionContext = ForkJoinPool()
```


## Implicit Conversionは、Conversionという型クラスになった

Scala3ではConversionのgivenインスタンスを定義することで、Implicit Conversionが使用できます。
以下のサンプルコードでは、`printPrice`の中で`Int`から`Price`への暗黙的型変換を行っています。

```scala
case class Price(value: Int):
  def show: String = s"${value.toString}円"

given Conversion[Int, Price] = Price(_)

def printPrice(intPrice: Int)(using Conversion[Int, Price]): Unit = println((intPrice: Price).show)
```


## ==がまともになった

`==`と`!=`が`CanEqual[A, B]`のgivenインスタンスを使って比較するようになりました。間違って異なる型の値同士を比較していたり(警告出ますが)、`canEqual`が実装されてなくてオブジェクトIDで比較していたなんてことが減るので安全性が向上しますね。

```scala
scala> 1 == "foo"
1 |1 == "foo"
  |^^^^^^^^^^
  |Values of types Int and String cannot be compared with == or !=

```

## Explicit Nulls

こちらは`-Yexplicit-nulls`を付与すると有効化されるオプトイン機能ですが、Scalaの型システムに変更を加え、`AnyRef`を継承する値をnon-nullableにできます。堅く作りたいうちの会社のシステムなんかには合いそうな気がします。

```scala
val x: String = null // error: found `Null`,  but required `String`
```

Nullableな値を表現したい場合は、Union Typesを使います。

```scala
val x: String | Null = null // ok
```

以下は有効化後の型ヒエラルキーの図です。NullがAnyRefから独立した型になっているのが判ります。

![explicit-nulls-type-hierarchy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172612/d7d713cc-15c4-c579-a4da-e250a1ba13d4.png)


## 2. `@main` でエントリーポイントが定義できるようになった

Scala3ではdefに`@main`アノテーションを付与すれば、それがエントリーポイントになります。

```scala
@main def greet = println(s"Hello, World!")
```

```
> run
[info] running greet
Hello, World!
```

`@main` で定義したエントリポイントは、引数でコマンドライン引数を受け取ることができます。


```scala
@main def greet(name: String) = println(s"Hello $name!")

```

```
> run John
[info] running greet John
Hello John!
[success] Total time: 0 s, completed 2020/12/28 1:00:19

> run
[info] running greet
Illegal command line: more arguments expected
[success] Total time: 1 s, completed 2020/12/28 1:28:03
```

## Tupleの展開がちょっと便利になった

Tupleのそれぞれのフィールドの値を`map`などの高階関数の中で変数に展開したい場合にcaseを書かなくてもよくなりました。

```scala
scala> val xs: List[(Int, Int)] = (1, 2) :: (3, 4) :: Nil
     |
     | xs.map {
     |   (x, y) => x + y
     | }
val xs: List[(Int, Int)] = List((1,2), (3,4))
val res6: List[Int] = List(3, 7)
```

## Scala3で削除される機能たち (一部抜粋)

#### XMLリテラル

まだ使えますが、近いうちに `xml"..."` 補間子に置き換えられるとのこと。

#### Scala2マクロ

Scala2の実験的マクロシステムは削除されました。
代わりとして、Scala3で新たに実装された `inline` と `'{ ... }`/`${ ... }` によるコード生成が利用できるようです(よくわかっていない)。

#### Symbolリテラル

既に削除されており、使えなくなっています。
あまりプロダクションのコードで使ってるのを見たことがありませんが、ScalaTestの記法などに影響がありそうです。

```scala
scala> 'a
1 |'a
  |^
  |symbol literal 'a is no longer supported,
  |use a string literal "a" or an application Symbol("a") instead,
  |or enclose in braces '{a} if you want a quoted expression.
```

#### 22制限

Tupleのフィールド数やFunctionのパラメータ数は最大22までに制限されてましたが、Scala3では任意の数をもたせることができるようになります。  
22を超えるとFunctionは`scala.FunctionXXL`、Tupleは`scala.TupleXXL`になるようです。

```scala
scala> val tuple23 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)
val tuple23: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
  Int
, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23)

scala> tuple23.getClass.getName
val res5: String = scala.runtime.TupleXXL
```

#### Auto-Application

`()` 付きのdefは`()`なしでは呼べなくなりました。

```scala
scala> def hello() = println("Hello, World!")
def hello(): Unit

scala> hello
1 |hello
  |^^^^^
  |method hello must be called with () argument
```

#### private[this] と protected[this]

非推奨になり、段階的に削除予定。

#### package object

まだ使用できますが、いずれ非推奨になり、その後削除されるようです。
トップレベルになんでも書けるようになったので要らないですね。

## 最後に

ここに書いた変更点は全部ではありません。しっかり学びたいと思った方は[公式ドキュメント](https://dotty.epfl.ch/docs/usage/scala3doc/)や[サンプルコード](https://github.com/scala/scala3-example-project)を読まれるのをおすすめします！