Scala3の予習（型クラス）Ver3.0.0-M3対応版

# はじめに
ちょっと前に、Scala3の予習として、[Dottyで型クラスを使ったモナド実装の記事](https://qiita.com/souchan-t/items/0cbfc571ce155f4b89c9)を書いたが、また言語仕様が変わり、以前のものが動作しなくなったので、改めて以下に記載する。

なおかつ、（私はあまり好みではないが）インデント構文で書いてみる。

内容は以前の記事の方が説明的で、今回はソース中心・・・。

## 環境
```shell
% scalac -version
Scala compiler version 3.0.0-M3 -- Copyright 2002-2020, LAMP/EPFL
```
## ファンクタとモナドの型クラスの定義

```scala:Monad.scala
package monad

trait Functor[F[_]]:
  extension[A,B](m:F[A])
    def map(fn:A => B):F[B]

trait Monad[F[_]] extends Functor[F]:
  def pure[A](x:A):F[A]
  extension[A,B](m:F[A])
    def flatMap(fn:A => F[B]):F[B]
    //mapのデフォルト実装
    override def map(fn:A => B):F[B] = flatMap(x => pure(fn(x)))
```

Scala2と比較して

- `implicit class`は`extension`になった。

また前（Dotty v0.26）と違い、

- `extension`の中の拡張メソッドで型パラメータが使えなくなっていた。
- 拡張メソッドと同名のメソッドが定義できなくなった。
- そのため、拡張メソッドのみ定義。

## Maybeモナドの実装

```scala:Maybe.scala
package monad

enum Maybe[+T]:
  case Empty
  case Just(value:T)
  def isEmpty:Boolean = this match
    case Empty => true
    case Just(_) => false

  def getOrElse[A >: T](_else: => A):A =
    this match
      case Empty => _else
      case Just(value) => value

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

  given Monad[Maybe] with
    override def pure[A](value:A):Maybe[A] = Maybe(value)

    extension[A,B](m:Maybe[A])
      override def flatMap(fn:A => Maybe[B]):Maybe[B] =
        m match
          case Empty => Empty
          case Just(value) => fn(value)
```
Scala2と比較して・・・

- 代数型定義に`abstract sealed class`ではなく`enum`を利用できる
- `implicit def`や`implicit object`,`implicit val`の代わりに`given`で型クラスのインスタンスを定義する。
- その際、無名にできる。

また前（Dotty　v0.2６）と違って

- `given [名前] as [型クラス]`ではなく`given [名前]:[型クラス] with`になった(てかこの`with`なんやねん、分かりにくいわ！いらんやろ！)

## Eitherモナドもどきの実装

```scala:Either2.scala
package monad

//代数的データ型をenumで定義
enum Either2[+L,+R]:
  case LeftCase(error:L)
  case RightCase(value:R)

object Either2:
  def left[L](error:L):Either2[L,Nothing] = LeftCase(error)
  def right[R](value:R):Either2[Nothing,R] = RightCase(value)

  //Monad[F[_]]の型パラメータ数と合うようにするため型ラムダを定義
  type ET2[L] = [R] =>> Either2[L,R]

  //Either2のMonadインスタンス
  given [L] : Monad[ET2[L]] with
    override def pure[R](value:R):ET2[L][R] = right(value)

    extension[R,B](m:Monad[ET2[L][R])
      override def flatMap(fn:R => ET2[L][B]):ET2[L][B] =
        m match
          case LeftCase(error) => LeftCase(error)
          case RightCase(value) => fn(value)
```

Scala2と違い
- Type Lambda（型ラムダ）を定義できるようになった

## 使ってみる
Either2オブジェクトに、`map`,`flatMap`が拡張メソッドとして存在するので`for式`で使えるようになる。

```scala:Main.scala
import monad._

object Main:
  def main(args:Array[String]):Unit =
    val e1:Either2[String,Int] = Either2.left("error!")
    val e2:Either2[String,Int] = Either2.right(10)

    //yieldのあるfor式はmap,flatMapに変換される
    val e3 = for
      x <- e1
      y <- e2
    yield
      x + y

    println(e3) // => LeftCase("error!")
```

## 感想

- `given`の後につける`with`は正直気持ち悪い。なぜ、`with`なんだ。
- とはいえ、Scala2の`implicit`は初心者殺しだったので、マシにはなった
- 正直いうとインデント構文は以下の理由により、あまり好みではない。
    - それでなくても大きくないScalaコミュニティを分断しかねない
    - ブレース構文と混ざると読みにくい
    - ２種類あると初学者が混乱する
    - （今のところ）LinterやFormatterが対応していない