macro

macro は、その名の通りマクロを定義するための簡便なコマンドです。ただしマクロとは、構文を構文に変換する機能のことです。

open Lean Parser in

/-- `s : String` をパースして `Syntax` の項を得る。`cat` は構文カテゴリ。-/
def parse (cat : Name) (s : String) : MetaM Syntax := do
  ofExcept <| runParserCategory (← getEnv) cat s

-- 最初は `#greet` が未定義なので、合法的なLeanのコマンドとして認識されない
/-- error: <input>:1:0: expected command -/
#guard_msgs in
  #eval parse `command "#greet"

-- `#greet` コマンドを定義する
macro "#greet " : command => `(command| #eval "Hello World!")

-- `#greet` コマンドが使用可能になった
/-- info: "Hello World!" -/
#guard_msgs in #greet

マクロ作例

マクロでどのようなことができるのかを理解するには、例を見るのが最高の近道です。以下に、macro コマンドでマクロを定義する例をいくつか示します。なお macro_rules コマンドを使用すれば、より複雑なマクロも定義できます。

引数を取るマクロ

冒頭の例の #greet コマンドは引数を持ちませんが、引数を取るようなものも定義できます。引数は $ を付けることでマクロ内で展開することができます。

-- 引数を取って、引数に対して挨拶するコマンドを定義する
-- 引数は `$` を付けると展開できる
macro "#hello " id:term : command => `(command| #eval s!"Hello, {$id}!")

/-- info: "Hello, Lean!" -/
#guard_msgs in #hello "Lean"

タクティクを自作する

マクロを使用すると、コマンドだけでなくタクティクの定義も行うことができます。

-- 平方根の計算
example : √4 = 2 := by
  rw [Real.sqrt_eq_cases]
  norm_num

-- 平方根の簡約
example : √18 = 3 * √ 2 := by
  rw [Real.sqrt_eq_cases]
  ring_nf
  norm_num

-- 新たなタクティクを定義する
macro "norm_sqrt" : tactic => `(tactic| with_reducible
  rw [Real.sqrt_eq_cases]
  try ring_nf
  norm_num)

-- 新しいタクティクにより一発で証明が終わるようになった!
example : √4 = 2 := by norm_sqrt
example : √18 = 3 * √ 2 := by norm_sqrt

do 構文を追加する

マクロで do 構文を追加することができます。

/-- 自前で定義した累積代入構文 -/
macro x:ident " += " e:term : doElem => `(doElem| ($x) := ($x) + $e)

/-- 0からnまでの和 -/
def sum (n : Nat) : Nat := Id.run do
  let mut sum := 0
  for i in [0:n+1] do
    sum += i
  return sum

#guard sum 3 = 6
#guard sum 4 = 10

引数の値ではなく名前を参照

マクロ展開時に、マクロの引数として与えられた変数の値だけでなく、名前も参照することができます。

-- 通常の dbg_trace の挙動。
-- 与えられた式の値だけを返し、与えられた式が何だったかは教えてくれない
/-- info: 1 -/
#guard_msgs in
  #eval
    let x := 1
    dbg_trace x
    return ()

/-- 与えられている変数の名前も出力するような `dbg_trace` の変種 -/
macro "dbg_trace!" x:ident body:term : term =>
  -- 与えられている引数 `x` の名前を取得する
  let ident := Lean.quote x.getId.toString
  `(term| dbg_trace s!"{$ident} = {$x}"; $body)

-- 与えられた変数の名前を出力するようになった!
/-- info: y = 1 -/
#guard_msgs in
  #eval
    let y := 1
    dbg_trace! y
    return ()