Scala 3 マクロ入門

in

はじめに

マクロは楽しくかつ強力なツールだが、使いすぎは害もある。責任を持って適度にマクロを楽しんでほしい。

マクロとは何だろうか? よくある説明はマクロはコードを入力として受け取り、コードを出力するプログラムだとされる。それ自体は正しいが、map {...} のような高階関数や名前渡しパラメータのように一見コードのブロックを渡して回っている機能に親しんでいる Scala プログラマには「コードを入力として受け取る」の意味が一見分かりづらいかもしれない。

以下は、僕が Scala 3 にも移植した Expecty という assersion マクロの用例だ:

scala> import com.eed3si9n.expecty.Expecty.assert
import com.eed3si9n.expecty.Expecty.assert
 
scala> assert(person.say(word1, word2) == "pong pong")
java.lang.AssertionError: assertion failed
 
assert(person.say(word1, word2) == "pong pong")
       |      |   |      |      |
       |      |   ping   pong   false
       |      ping pong
       Person(Fred,42)
 
  at com.eed3si9n.expecty.Expecty$ExpectyListener.expressionRecorded(Expecty.scala:35)
  at com.eed3si9n.expecty.RecorderRuntime.recordExpression(RecorderRuntime.scala:39)
  ... 36 elided

例えば assert(...) で名前渡しの引数を使ったとしたら、その値を得るタイミングは制御できるが false しか得ることができない。一方マクロでは、person.say(word1, word2) == "pong pong" というソースコードの形そのものを受け取り、全ての式の評価値を含んだエラーメッセージを自動生成するということができる。頑張って書こうと思えば Predef.assert(...) を使っても手でこのようなエラーメッセージを書くことができるが、非常に退屈な作業となる。マクロの全貌はこれだけでは無い。

よくありがちな考え方としてコンパイラはソースコードをマシンコードへと翻訳するものだとものがある。確かにそういう側面もあるが、コンパイラは他にも多くの事を行っている。型検査 (type checking) はそのうちの一つだ。バイトコード (や JS) を最後に生成する他に、Scala コンパイラはライトウェイトな証明システムとして振る舞い、タイポや引数の型合わせなど様々なエラーを事前にキャッチする。Java の仮想機械は、Scala の型システムが何を行っているかをほとんど知らない。この情報のロスは、何か悪いことかのように型消去とも呼ばれるが、この型とランタイムという二元性によって Scala が JVM、JS、Native 上にゲスト・プログラミング言語として存在することができる。

Scala において、マクロはコンパイル時にアクションを取る方法を提供してくれ、これは Scala の型システムと直接話すことができるホットラインだ。具体例で説明すると、型 A があるとき、ランタイム上からこれが case class であるかを正確に確認する方法は無いと思う。マクロを使うとこれが 5行で書ける:

import scala.quoted.*
 
inline def isCaseClass[A]: Boolean = ${ isCaseClassImpl[A] }
private def isCaseClassImpl[A: Type](using qctx: Quotes) : Expr[Boolean] =
  import qctx.reflect.*
  val sym = TypeRepr.of[A].typeSymbol
  Expr(sym.isClassDef && sym.flags.is(Flags.Case))

上記の ${ isCaseClassImpl[A] } は Scala 3 マクロの一例で、スプライスと呼ばれる。

酢鶏、パート1

in

実験的 sbt として、酢鶏 (sudori) という小さなプロジェクトを作っている。当面の予定はマクロ周りを Scala 3 に移植することだ。sbt のマクロを分解して、土台から作り直すという課題だ。これは Scala 2 と 3 でも上級者向けのトピックで、僕自身も試行錯誤しながらやっているので、覚え書きのようなものだと思ってほしい。

参考:
- Scala 3 Reference: Metaprogramming

Scala, Python quick reference

syntax Scala Python
immutable variable val x = 1 Starlark:
REV = "1.1.0"
lazy variable lazy val x = 1 n/a
mutable variable var x = 1 in function:
x = 1
if expression if (x > 1) "a" else "b" "a" if x > 1 else "b"

Bintray から JFrog Artifactory へのマイグレーションと sbt 1.5.0

sbt 1.5.1 パッチリリースをアナウンスする。リリースノートの完全版はここにある - https://github.com/sbt/sbt/releases/tag/v1.5.1

sbt 1.5.1 がリリースされたことで、マイグレーションは完了したと思う。

猫番: 19日目

in

猫番: 19日目。FunctionK という Rúnar さんによるランク2多相性のエンコーディング、そして高ランク多相が可能にすると予見された Resource データ型に関して。

統一スラッシュ構文のための syntactic Scalafix rule

in

build.sbt を統一スラッシュ構文へと変換する syntactic Scalafix rule をちゃちゃっと作ったのでここで紹介する。

用法

プロジェクトが git で管理されているか、バックアップを取ること。


$ cs install scalafix
$ export PATH="$PATH:$HOME/Library/Application Support/Coursier/bin"
$ scalafix --rules=https://gist.githubusercontent.com/eed3si9n/57e83f5330592d968ce49f0d5030d4d5/raw/7f576f16a90e432baa49911c9a66204c354947bb/Sbt0_13BuildSyntax.scala .sbt project/.scala

完全には正確じゃないが、手動で全部やるよりはマシだと思う。

scala/scala の git bisect

git bisect はバグの入った場所を特定するのに有用なテクニックだ。
特に scala/scala の場合は、bisect.sh はビルド済みのコンパイラを Scala CI Artifactory から利用することで時間を節約できる。

Syndicate content