Scala3の予習（型クラス）

# 編集
Dotty　0.26から言語仕様が変わってしまっているので、[Scala3.0.0-M3向けに書き直しました](https://qiita.com/souchan-t/items/3789e649fe76a8914133)。

# はじめに

自身の予習を残しておくクソエントリーです。

2020年はScalaが大幅リニューアルされて、Scala3がリリースされるかも。ようやくScala2に慣れてきたというのに・・・という事でDotty（Scalaの実験的実装）で予習を行う。

特に型クラス周り(Scala2でいう`implicit`を使うところ）は大幅に変更になっているので、まずはそこを中心に予習。

幾度となくコンパイルエラーを叩き出してようやくたどり着いたコードなので、間違いもあるかもしれません。

なお、仕様は頻繁に変わっているので、必ずしも今のDottyの言語仕様がScala3に導入されるとは限らない。

# 予習環境
- dotty 0.26.0-RC1

# 予習の流れ
- ファンクタ、モナド型クラスを作り
- `Maybe`モナドもどきを作り
- `Either`モナドもどきを作り
- モナドを利用する流れを確認

Eitherは名前の衝突を避けるため`Both`に変更している。

## 型クラスの定義
Scalaでは型クラスを定義するのにトレイトを用いる。普通の型クラスではなく、型パラメータ`F[_]`や`M[_]`をとる型クラスを定義しています。

ファンクタやモナドの定義方法は、様々あるので、あくまで一例ですし、これが最適かどうかは勉強不足です。



```scala
//ファンクタ型クラス
trait Functor[F[_]]{

    def map[A,B](m:F[A])(fn:A => B):F[B]

    //Functorになる型(Maybe)にmap生やす
    extension [A,B](f:F[A]):
        def map(fn: A => B)(using functor:Functor[F]):F[B]=
            functor.map(f)(fn)
}
//モナド型クラス
trait Monad[M[_]] extends Functor[M]{

    def flatMap[A,B](m:M[A])(fn:A => M[B]):M[B]

    def pure[A](x:A):M[A]

    //mapのデフォルト実装
    override def map[A,B](m:M[A])(fn:A => B):M[B] =
        flatMap(m)((x:A) => pure(fn(x)))

    //Monadになる型に(Maybe)にflatMapを生やす
    extension [A,B](m:M[A]):
        def flatMap(fn: A => M[B])(using monad:Monad[M]):M[B] =
            monad.flatMap(m)(fn)
}
```

### ポイント1（extension)

従来は`implicit class`を使っていたところが、`extension`になった。わざわざ`RichXXX`などと名前を付けなくても、無名でできる。

```scala
//scala2
object Implicits{
  implicit class RichInt(x:Int){
    def times(action: () => Unit):Unit = for (i <- 0 to x) action()
  }
}
```

```scala
//scala3（予定）
object Implicits{
  extension (x:Int){
    def times(action: () => Unit):Unit = for (i <- 0 to x) action()
  }
}
```

### ポイント2(using)
従来は`implicit仮引数` を使っていたところを、`using`に置き換わった。

```scala
//scala2
def show[T](x:T)(implicit s:Show[T]):String = s.show(x)
```

```scala
//scala3(予定)
def show[T](x:T)(using s:Show[T]):String = s.show(x)
```

## Maybeモナドの実装

```scala
//Maybe型をenumで定義
enum Maybe[+A]{
    case Just(value:A)
    case Empty

    //Maybeのメソッド例
    def getOrElse[B >: A](_else: => B):B = this match {
        case Just(v) => v
        case Empty => _else
    }
}

object Maybe{
    def apply[A](value:A):Maybe[A] =
        if (value != null) Just(value) else Empty

    //Monad型クラスのインスタンス
    given Monad[Maybe] {
        //Maybeモナドにおけるpure関数の実装
        def pure[A](x:A):Maybe[A] = Maybe(x)

        //MaybeモナドにおけるflatMap関数の実装
        def flatMap[A,B](m:Maybe[A])(fn: A => Maybe[B]):Maybe[B] = {
            m match {
                case Just(v) => fn(v)
                case Empty => Empty
            }
        }
    }
}
```

### ポイント３(enum)

Scala3でenumが追加される予定。普通の列挙型の使い方以外に、代数的データ型というものの定義に利用できるそうです。

```scala
//Scala2
abstract sealed class Maybe[+A]
case class Just[+A](value:A) extends Maybe[A]
case class Empty() extends Maybe[Nothing]
```

```scala
//Scala3
enum Maybe[+A] {
  case Just(value:A)
  case Empty
}
```

### ポイント4(given)
型クラスのインスタンスを作るときの`implicit`が`given`に変更になった。こちらも無名でいけるし、型パラメータもとれる。

```scala

//scala2
object Maybe{
  implicit val maybeMonad = new Monad[Maybe] {
    def pure[A](x:A):Maybe[A] = ???
    def flatMap[A,B](m:Maybe[A])(fn:A => Maybe[B]):Maybe[B] = ???
  }
}
```

```scala
//scala3
object Maybe{
  //given maybeMonad as Monad[Maybe] でもいけるが、以下の様に無名でもOK
  given Monad[Maybe] {
    def pure[A](x:A):Maybe[A] = ???
    def flatMap[A,B](m:Maybe[A])(fn:A => Maybe[B]):Maybe[B] = ???
  }
}
```

## Eitherモナドの実装


```scala
// Scala3
//Eitherに近い型パラメータが２つあるケース
enum Both[+L,+R]{
    case LeftCase(err:L)
    case RightCase(value:R)
}

object Both{
    //LeftCaseとRigthCaseを作る関数
    def left[L](err:L):Both[L,Nothing] = LeftCase(err)
    def right[R](value:R):Both[Nothing,R] = RightCase(value)

    //型ラムダ
    type BT[L] = [R] =>> Both[L,R]

    //型パラメータを持った型クラスインスタンス
    given [L] as Monad[BT[L]]{
        def pure[R](x:R):BT[L][R] = RightCase(x)
        def flatMap[R,A](m:BT[L][R])(fn: R => BT[L][A]):BT[L][A] = {
            m match {
                case LeftCase(err) => LeftCase(err)
                case RightCase(value) => fn(value)
            }
        }
    }
}
```

### ポイント5(型ラムダ)
これが一番理解に苦しんだ。公式の説明短すぎるやろと。

`=>>` でラムダ式の様に記述し型を返す関数の様な物を作れる。Monad型クラスの型パラメータの引数の数は１つ（`F[_]`)なのに対し、Bothは型パラメータが２つ。なので、`Monad[F[_,_]]`としなければならないところを、カリー化することでアリティを合わせる事ができる。（と理解しているが、難しい）

```scala
//scala3
type BT[L] = [R] =>> Both[L,R] //よってBT[String][Int]型 == Both[String,Int]型となる
```

### ポイント6(型ラムダを組み合わせた型クラスインスタンス)
もはや、パズルみたいになっているが・・・。`given`で作る型クラスインスタンスは型パラメータも受け取れる。

```scala
object Both{
  type BT[L] = [R] =>> Both[L,R]
  given [L] as Monad[BT[L]] {
    def pure[R](x:R):BT[L][R] = ???
    def flatMap[R,A](m:BT[L][R])(fn: R => BT[L][A]):BT[L][A] = ???
```

## モナドの利用
これで晴れて、モナドもどきを使える。

```scala
object Main{
  def main(args:Array[String]):Unit = {
    val b1:Both[String,Int] = Both.right(10)
    val b2:Both[String,Int] = Both.left("Error!")

    val b3 = for {
      x <- b1
      y <- b2
    } yield (x + y)      

    println(b3) // => LeftCase("Error")
```

### コンパイラの気持ちになる
ここからはコンパイラの気持ちになって、コードをトレースしていきます。
あっているかは、かなり自信がないです。

```scala
val b1:Both[String,Int] = Both.right(10)
val b2:Both[String,Int] = Both.left("Error!")
```
- `def right[R](value:R):Both[Nothing,R]`というシグネチャで、`value`は`10`なので`R`は`Int型`やな。
- なので戻り値の型は`Both[Nothing,Int]`やな
- `Both[+R,+L]`で共変だから、具体型が`L`,`R`のサブタイプでも代入可能やん
- ほな`Nothing`は`String`のサブタイプなので、`b1`の`Both[String,Int]`型は`Both[Nothing,Int]`で代入可能ですやん。
- 同様に`b2`の`Both[String,Int]`型は`Both[Nothing,Int]`型で代入可能っと。

```scala
val b3 = for {
  x <- b1
  y <- b2
} yield(x + y)
```
- おっ、`yield`のある`for式`やから、`flatMap`,`map`に変換しよう。

```scala
val b3 = b1.flatMap(x => b2.map(y => x + y))
```

- `b1:Both[String,Int]`に`flatMap`なんていうメソッド定義されてないぞ。
- どこかに拡張メソッドがあるかもしれへんから探しに行こう。

```scala
given [L] as Monad[BT[L]]
```

- `Both`コンパニオンオブジェクトに、`Monad[BT[L]]`型のインスタンスがあるけど、これはつまるところ`Monad[Both[String,_]]`型のインスタンスやな。
- おやMonad型インスタンスに拡張メソッド`extension`があるぞ。


```scala
trait Monad[M[_]] extends Functor[M]{
    //（中略）
    extension [A,B](m:M[A]):
        def flatMap(fn: A => M[B])(using monad:Monad[M]):M[B] =
            monad.flatMap(m)(fn)
```

- ここでの`M`は`BT[String]`
- `extension`の`m:M[A]`は`m:BT[String][A]`。`A`が`Int`なら、`BT[String][Int] = Both[String,Int]`になり探している目的の`b1:Both[String,Int]`と型が一致する。
- `flatMap`見つけたで！

```scala
def flatMap(fn:Int => BT[String][Int])(using monad:Monad[BT[String]]):BT[String][Int]
```

- さらに`BT[L][R]`を`Both[L,R]`に置き換えると・・・

```scala
def flatMap(fn:Int => Both[String,Int])(using monad:Monad[BT[String]]):Both[String,Int]
```

- はて、`monad:Monad[BT[String]]`がないけれど、どこにあるんだろう。
- `using`な引数なので、探しに行こう。

```scala
object Both{
    //(中略)
    //型ラムダ
    type BT[L] = [R] =>> Both[L,R]

    //型パラメータを持った型クラスインスタンス
    given [L] as Monad[BT[L]]{
```

- `Both`コンパニオンオブジェクトに`given`で`Monad[BT[L]]`インスタンスがあるな。これを暗黙的にわたそう。

という流れではなかろうかと・・・。