1
1

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 3 years have passed since last update.

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

Last updated at Posted at 2021-03-07

はじめに

ちょっと前に、Scala3の予習として、Dottyで型クラスを使ったモナド実装の記事を書いたが、また言語仕様が変わり、以前のものが動作しなくなったので、改めて以下に記載する。

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

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

環境

% scalac -version
Scala compiler version 3.0.0-M3 -- Copyright 2002-2020, LAMP/EPFL

ファンクタとモナドの型クラスの定義

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 classextensionになった。

また前(Dotty v0.26)と違い、

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

Maybeモナドの実装

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 defimplicit object,implicit valの代わりにgivenで型クラスのインスタンスを定義する。
  • その際、無名にできる。

また前(Dotty v0.26)と違って

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

Eitherモナドもどきの実装

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式で使えるようになる。

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コミュニティを分断しかねない
    • ブレース構文と混ざると読みにくい
    • 2種類あると初学者が混乱する
    • (今のところ)LinterやFormatterが対応していない
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?