Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scala 3のsummonって何?

Scala 3にはsummonという関数がある。普段はあまり使うことがないが、知っておくとusingまわりのデバッグにも使えるとても便利なやつだ。この記事ではsummonの便利な利用法を紹介する。

docs.scala-lang.org

型の一致を確認する

summonの典型的なユースケースは、2つの型が一致するかを確認することだ。

type I = Int
summon[I =:= Int] // コンパイルが通る

summon[A =:= B]と書くと、型ABが等しい場合にはコンパイルが通り、そうではない場合にはコンパイルに失敗する。サブタイプかどうかを見てほしいときは<:<を使える。

summon[String =:= Int]
-- [E172] Type Error: ----------------------------------------------------------
1 |summon[String =:= Int]
  |                      ^
  |                      Cannot prove that String =:= Int.
1 error found

どういうときに2つの型が一致するか確認したいのかというと、例えば型レベル計算がうまくいっているかを知りたいときに便利だ。要するにこの型ってこの型に展開されるんだよね?というのがわかるのだ。

例えば、型パラメータに渡したIntのぶん次元を持つベクトルVec型を次のように実装したとする(中身は難しいから今は知らなくていい)。

import scala.compiletime.ops.int._
type Vec[N <: Int] = N match {
  case 0 => EmptyTuple
  case _ => Tuple.Append[Vec[N - 1], Double]
}

面白いことにこうするとVec[4]と書くだけで(Double, Double, Double, Double)が表現できる。でもこのコードを書いている間は本当にそうなっているか不安なので、summonで確かめることができる:

summon[Vec[4] =:= (Double, Double, Double, Double)] // コンパイルが通る
summon[Vec[4] =:= (Double, Double)] // コンパイルが通らない

usingできることを確認する

もう一つの典型的なユースケースは、CirceなどのJSONライブラリにおけるEncoderやDoobieなどのDBライブラリにおけるMetaといった、何かを変換するために必要なインスタンスを、usingで呼び出せるか確認することだ。

つまり、どういうわけかcase class Foo(b: Bar)がJSONになってくれない、おかしいなあ、といったときにEncoder[Foo]Encoder[Bar]があることをサッと確認する、といった流れで使う。

import io.circe._, io.circe.generic.auto._

case class Foo(n: Int)

// ...

summon[Encoder[Foo]] // Encoder[Foo]をusingできるならコンパイルが通る

というか、usingできるはずのものをあえて明示的に召喚することからsummonという名前がついているのだ。

ちなみにsummonの定義はシンプルそのもの。

  /** Summon a given value of type `T`. Usually, the argument is not passed explicitly.
   *
   *  @tparam T the type of the value to be summoned
   *  @return the given value typed: the provided type parameter
   */
  transparent inline def summon[T](using x: T): x.type = x

=:= の仕組み

賢い読者はこう思ったに違いない。

usingできるものを明示的に呼び出しているのは分かったけど、じゃあ型が一致してるかどうかをsummon[A =:= B]で判定できるのはどうしてなんだよ?

その種明かしをしよう。=:=は、実は=:=[A, B]という形で定義されたクラスで、ABとが等しい場合にのみインスタンスが導出されるような巧妙な仕掛けになっている。かなり巧妙*1なのでいったんここでは踏み込まない。ちなみに<:<も同様の仕組みになっている。

まぁ今回は使い方を説明するということで、なんとか・・・

*1:これだけで1記事必要

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?