Repr
Repr
は、その型の項をどのように表示するかを指示する型クラスです。
たとえば、以下のように新しく構造体 Point
を定義したとき、何も指定しなくても Point
の項を #eval
で表示することはできますが、実は裏で Repr
インスタンスを利用しています。
-- 平面上の点を表す構造体
structure Point (α : Type) : Type where
x : α
y : α
-- 原点
def origin : Point Nat := ⟨0, 0⟩
-- `origin` の中身を表示することができる
/- info: { x := 0, y := 0 } -/
#eval origin
-- Repr インスタンスを暗黙的に生成しないように設定
set_option eval.derive.repr false
-- 表示できずにエラーになった!
/-
error: could not synthesize a 'ToExpr', 'Repr', or 'ToString' instance for type
Point Nat
-/
#eval origin
Repr が満たすべきルール
Repr
の出力は Lean のコードとしてパース可能なものに可能な限り近くなければならない、つまり Lean のコードとして実行可能であることが期待されます。このルールは 次のように Repr
のドキュメントコメントに書かれています。
The standard way of turning values of some type into
Format
.When rendered this
Format
should be as close as possible to something that can be parsed as the input value.
Repr インスタンスの実装方法
Repr
型クラスの定義は次のようになっています。
class Repr.{u} (α : Type u) where
/--
`α` 型の項を、与えられた優先度で `Format` に変換する。
優先度は、括弧を付けるかどうかの判断に使用される。
-/
reprPrec : α → Nat → Std.Format
したがって Repr
のインスタンスを実装するには Std.Format
の項を構成する必要がありそうですが、実は必ずしもこれを明示的に構成する必要はありません。
deriving を使う
deriving
コマンドで Lean に Repr
インスタンスを自動生成させることができます。
deriving instance Repr for Point
/- info: { x := 0, y := 0 } -/
#eval origin
あるいは、そもそも型を定義する際に deriving
句を用いて生成しても良いでしょう。
structure Point' (α : Type) : Type where
x : α
y : α
deriving Repr
def origin' : Point' Nat := ⟨0, 0⟩
-- 評価できる
#eval origin'
なお Repr
の実装が満たすべきルールとして「出力は実行可能な Lean のコードでなければならない」というものがあるので、自分で構文を用意しない限り deriving
を使わずに Repr
インスタンスを実装する機会はないはずです。
ToString インスタンスから作る
ToString
クラスのインスタンスから、Repr
のインスタンスを得ることができます。
instance {α : Type} [ToString α] : Repr α where
reprPrec x _ := toString x
実装すべきメソッド Repr.reprPrec
の型は α → Nat → Std.Format
なので型が合わないようですが、上記のコードが通るのは String
から Format
への型強制が存在するためです。
-- `String → Format` という型強制が存在する
#synth Coe String Std.Format
これを利用すると Repr
の実装が手軽に得られます。
以下に紹介する例は少し長くて複雑ですが、macro_rules
コマンドを使用して見やすい構文を用意した後、Repr
の出力がその構文になるように Repr
インスタンスを定義する例です。
/-- 2項演算の集合 -/
inductive Op where
/-- 加法 -/
| add
/-- 乗法 -/
| mul
deriving BEq
namespace Op
-- ## ToString インスタンスの定義
protected def toString : Op → String
| add => "+"
| mul => "*"
instance : ToString Op := ⟨Op.toString⟩
end Op
/-- 数式 -/
inductive Expr where
/-- 数値リテラル -/
| val (n : Nat)
/-- 演算子の適用 -/
| app (op : Op) (left right : Expr)
deriving BEq
namespace Expr
/- ## Expr の項を定義するための見やすい構文を用意する -/
/-- `Expr` のための構文カテゴリ -/
declare_syntax_cat expr
/-- `Expr` を見やすく定義するための構文 -/
syntax "expr!{" expr "}" : term
syntax:max num : expr
syntax:30 expr:30 " + " expr:31 : expr
syntax:35 expr:35 " * " expr:36 : expr
syntax:max "(" expr ")" : expr
macro_rules
| `(expr!{$n:num}) => `(Expr.val $n)
| `(expr!{$l:expr + $r:expr}) => `(Expr.app Op.add expr!{$l} expr!{$r})
| `(expr!{$l:expr * $r:expr}) => `(Expr.app Op.mul expr!{$l} expr!{$r})
| `(expr!{($e:expr)}) => `(expr!{$e})
-- 構文が正しく動作しているかテスト
#guard
let expected := Expr.app Op.add (app Op.mul (val 1) (val 2)) (val 3)
let actual := expr!{1 * 2 + 3}
expected == actual
end Expr
namespace Expr
/- ## ToString インスタンスを定義する -/
protected def toString : Expr → String
| .val x => toString x
| .app op l r =>
brak l ++ " " ++ toString op ++ " " ++ brak r
where
/-- 式全体を括弧で囲うことを回避するための補助関数 -/
brak : Expr → String
| .val n => toString n
| e => "(" ++ Expr.toString e ++ ")"
instance : ToString Expr := ⟨Expr.toString⟩
-- toString インスタンスのテスト
#guard toString expr!{1 + 2 * 3} = "1 + (2 * 3)"
#guard toString expr!{1 + (2 + 3 * 4)} = "1 + (2 + (3 * 4))"
end Expr
namespace Expr
/- ## Repr インスタンスを定義する -/
-- `ToString` インスタンスを利用して `Repr` インスタンスを実装する
instance : Repr Expr where
reprPrec e _ := "expr!{" ++ toString e ++ "}"
-- Repr インスタンスのテスト
/- info: expr!{1 + (2 * 3)} -/
#eval expr!{1 + (2 * 3)}
end Expr