Lean 4(元)编程 Cookbook

在信息视图中显示🔗

本配方演示如何在 Lean 信息视图中显示消息,这是元编程时进行调试和向用户提供反馈的一项基本技术。

这里介绍的函数依赖于 MessageData,它让你能够记录富文本和表达式。为了方便地构造 MessageData 对象,Lean 提供了 m! 插值字符串宏,它能把纯文本和 Lean 项无缝组合成信息视图可以渲染的格式。

这些日志函数并不直接接受任意项。要在信息视图中打印某个东西,Lean 必须知道如何把它转换成 MessageData,通常是通过 ToMessageData 的一个实例。很多情况下这些实例是现成的。例如,ToFormat 实例可以自动提供 ToMessageData 实例,而 ToString 实例又可用于构造 ToFormat 实例。

Nat 这样的内置类型已经有了 ToMessageData 实例,所以 m!"The number is {42}" 开箱即用。对于自定义类型,你需要告诉 Lean 它应该如何在信息视图中显示:

structure Point where x : Nat y : Nat instance : ToMessageData Point where toMessageData p := m!"Point({p.x}, {p.y})" def showPoint : MetaM Unit := do let p : Point := { x := 3, y := 5 } logInfo m!"Current point: {p}"

这里,插值 {p} 之所以有效,是因为 ToMessageData Point 实例告诉了 Lean 如何把一个 Point 转换成可打印的东西。

logInfologWarninglogError🔗

三个主要的日志函数都在单子中运行,最常用在 CoreMMetaMTermElabMTacticM 单子中。

Lean.throwError 不同,这些日志函数都不会中断或终止执行。它们只是向信息视图发送一条消息,并让程序继续运行。

Lean.logInfo🔗

logInfo 在信息视图中显示标准的信息类消息。由于它在单子中运行,可以用在策略、命令以及其他精译上下文中。

def message (msg: String) : MetaM Unit := logInfo m!"Here is the message: {msg}" Here is the message: logInfo worked#eval message "logInfo worked"

下面是一个名为 readGoal 的策略示例,它用 getMainTarget 获取当前目标的期望类型,然后配合 m! 宏使用 logInfo,直接在信息视图中格式化打印该目标。

elab "readGoal" : tactic => do let goal getMainTarget logInfo m!"Current goal: {goal}" example : 2 + 3 = 5 := 2 + 3 = 5 Current goal: 2 + 3 = 52 + 3 = 5 All goals completed! 🐙

注意 m! 宏如何处理插值表达式:m!"Current goal: {goal}" 展开成一个 MessageData 对象,其中既包含一个文本部分("Current goal: "),又包含一个表达式部分(格式化打印后的 goal)。logInfo 接受这个对象并把它推送到信息视图。

Lean.logWarning🔗

logWarning 在信息视图中以黄色显示警告消息。它非常适合用来标记非关键性的问题或边界情况。

def warningMessage (msg : String) : CoreM Unit := do logWarning m!"Warning: {msg}" Warning: something might be wrong#eval warningMessage "something might be wrong"

在下面这个策略示例中,如果某个策略把状态拆分成多个目标,我们就用 logWarning 提醒用户:

elab "warnIfMultipleGoals" : tactic => do let goals getUnsolvedGoals if goals.length > 1 then logWarning m!"More than one goal left!" example : x : Nat, (x = x x - x = 0) := (x : Nat), x = x x - x = 0 x:Natx = x x - x = 0 x:Natx = x x - x = 0x:Natx - x = 0 x = x More than one goal left!x:Natx = x x - x = 0x:Natx - x = 0 x = x x:Natx = x x - x = 0 All goals completed! 🐙 x:Natx - x = 0 x = x All goals completed! 🐙

Lean.logError🔗

Lean.logError 在信息视图中以红色显示错误消息。虽然它会给相关代码标上表示错误的红色波浪线,但并不会中断执行。

def errorMessage (msg : String) : CoreM Unit := do logError m!"Error: {msg}" /-- error: Error: something went wrong -/ #guard_msgs in #eval errorMessage "something went wrong"

当你想报告一个错误但仍继续处理文件或命令的其余部分时,这尤其有用。例如,#requireProp 是一个命令,用来检查给定的项是否具有类型 Prop。如果不是,它会记录一个错误,但仍继续执行并记录该项的表达式:

elab "#requireProp" t:term : command => do Command.liftTermElabM do let tExpr Term.elabTerm t none unless isProp tExpr do logError m!"Goal must be a proposition: {tExpr}" logInfo m!"The expression of the term: {tExpr} " /-- error: Goal must be a proposition: Nat --- info: The expression of the term: Nat -/ #guard_msgs in #requireProp Nat /-- error: Goal must be a proposition: Nat → Nat --- info: The expression of the term: Nat → Nat -/ #guard_msgs in #requireProp (Nat Nat) The expression of the term: 2 = 0 #requireProp 2 = 0

如果把 Lean.logError 换成 Lean.throwError,注意其中的差别。由于 Lean.throwError 会立即中止执行,当项不是命题时,后面的 logInfo 永远不会运行:

elab "#requireProp" t:term : command => do Command.liftTermElabM do let tExpr Term.elabTerm t none unless isProp tExpr do throwError m!"Goal must be a proposition: {tExpr}" logInfo m!"The expression of the term: {tExpr}" /-- error: Goal must be a proposition: Nat -/ #guard_msgs in #requireProp Nat /-- error: Goal must be a proposition: Nat → Nat -/ #guard_msgs in #requireProp (Nat Nat) The expression of the term: 2 = 0#requireProp 2 = 0