Lean 语言参考手册

3. 与 Lean 交互🔗

Lean 的设计目标是交互式使用,而不是采用批处理模式——在这种模式下,整个文件被一次性输入,再被转换为目标代码或错误消息。 许多为交互式使用而设计的编程语言都提供 REPL它是 R(读取)-E(求值)-P(打印)-L(循环)”的缩写,因为代码会被解析(“读取”)、求值并显示结果,而且这一过程可以按需重复任意多次。用户可以在其中输入并测试代码,也可以使用命令加载源文件、检查项的类型或查询环境。 Lean 的交互功能基于另一种范式。 Lean 并不在程序之外提供单独的命令提示符,而是在源文件的上下文中提供 命令来完成相同的任务。 依照惯例,供交互使用、而非作为持久代码制品一部分的命令,都以 # 为前缀。

Lean 命令产生的信息可在消息日志中查看;该日志会累积精译器的输出。 消息日志中的每个条目都与一段特定的源代码范围相关联,并具有一个严重程度。 严重程度共有三级:information 用于不表示存在问题的消息,warning 表示潜在问题,而 error 表示确定存在的问题。 对于交互式命令,结果通常作为信息消息返回,并与该命令开头的关键字相关联。

3.1. 对项求值🔗

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 命令用于将代码作为程序运行。 具体而言,它能够执行 IO 动作,采用按值调用的求值策略,会执行 partial 函数,并且类型与证明都会被擦除。 若要改用属于定义相等一部分的归约规则来归约项,请使用 Lean.reduceCmd : command`#reduce <expression>` reduces the expression `<expression>` to its normal form. This involves applying reduction rules until no further reduction is possible. By default, proofs and types within the expression are not reduced. Use modifiers `(proofs := true)` and `(types := true)` to reduce them. Recall that propositions are types in Lean. **Warning:** This can be a computationally expensive operation, especially for complex expressions. Consider using `#eval <expression>` for simple evaluation/execution of expressions. #reduce

语法对项求值
command ::= ...
    | `#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then
prints the resulting value.

* The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result.
* If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m`
  to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`.
  Users can define `MonadEval` instances to extend the list of supported monads.

The `#eval` command gracefully degrades in capability depending on what is imported.
Importing the `Lean.Elab.Command` module provides full capabilities.

Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly,
since the presence of `sorry` can lead to runtime instability and crashes.
This check can be overridden with the `#eval! e` command.

Options:
* If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the
  usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances.
* If `eval.type` is true (default: false) then pretty prints the type of the evaluated value.
* If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance
  when there is no other way to print the result.

See also: `#reduce e` for evaluation by term reduction.
#eval term
command ::= ...
    | `#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then
prints the resulting value.

* The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result.
* If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m`
  to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`.
  Users can define `MonadEval` instances to extend the list of supported monads.

The `#eval` command gracefully degrades in capability depending on what is imported.
Importing the `Lean.Elab.Command` module provides full capabilities.

Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly,
since the presence of `sorry` can lead to runtime instability and crashes.
This check can be overridden with the `#eval! e` command.

Options:
* If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the
  usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances.
* If `eval.type` is true (default: false) then pretty prints the type of the evaluated value.
* If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance
  when there is no other way to print the result.

See also: `#reduce e` for evaluation by term reduction.
#eval! term

#eval e 会编译表达式 e、运行编译后的代码并打印结果值。

  • 该命令会尝试使用 ToExprReprToString 实例来打印结果。

  • e 是类型为 m ty 的单子值,该命令会尝试把单子 m 适配为 #eval 支持的单子之一,包括 IOCoreMMetaMTermElabMCommandElabM。用户可定义 MonadEval 实例来扩充支持的单子列表。

#eval 的能力会随所导入内容而平稳降级。导入 Lean.Elab.Command 模块可获得完整能力。

出于可靠性考虑,#eval 拒绝求值直接或间接依赖 sorry 的表达式,因为 sorry 可能导致运行时不稳定或崩溃。可使用 #eval! e 命令越过此检查。

选项:

  • eval.pptrue(默认如此),则尝试使用 ToExpr 实例,以便采用通常的 美化打印器;否则仅尝试 ReprToString 实例。

  • eval.typetrue(默认为 false),则美化打印求值结果的类型。

  • eval.derive.reprtrue(默认如此),则在没有其他方式可打印结果时, 尝试自动派生 Repr 实例。

另见:使用 #reduce e 通过项归约进行求值。

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 总会先由 Lean 的精译器精译所提供的项,再进行编译。 随后,它会检查该项是否传递依赖于任何 sorry;若存在此类依赖,除非以 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval! 形式调用命令,否则求值将终止。 这是因为编译后的代码可能依赖编译期不变量(例如数组查找不能越界),而这些不变量由适当命题的证明保证;运行包含不完整证明的代码(或使用 sorry“证明”错误命题的代码)可能导致 Lean 自身崩溃。

代码的运行方式取决于其类型:

  • 如果类型位于 IO 单子中,则会在捕获标准输出标准错误并将其重定向到 Lean 消息日志的上下文中执行。 如果返回值的类型不是 Unit,则会像显示非单子表达式的结果那样显示它。

  • 如果类型位于 Lean 内部的某个元编程单子中(CommandElabMTermElabMMetaMCoreM),则会在当前上下文中运行。 例如,环境会包含调用 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 之处位于作用域内的定义。 与 IO 一样,所得值会像非单子表达式的结果那样显示。 当 Lean 在 Lake 下运行时,其工作目录(因而也是 IO 动作的工作目录)是当前工作区

  • 如果类型位于其他某个单子 m 中,并且存在 MonadLiftT m CommandElabMMonadEvalT m CommandElabM 实例,则会使用 MonadLiftT.monadLiftMonadEvalT.monadEval 将该单子转换为可由 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 运行的单子,之后照常运行。

  • 如果项的类型不位于任何受支持的单子中,则将它视为纯值。 编译后的代码会被运行,并显示结果。

精译 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 中的项所产生的辅助定义或其他环境修改都会被丢弃。 如果该项是元编程单子中的动作,那么运行此单子动作对环境所做的更改会被保留。

模块中使用时,Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 会揭示 Lean 语言服务器与 Lean 编译器处理文件方式之间的差异。 由于 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 会在编译期运行代码,因此要求其代码在元阶段可用。 为便于对模块进行实验,语言服务器会让所有已导入模块在元阶段可用,而编译器则严格遵守 Lean.Parser.Module.importmeta 声明。 因此,使用 Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgsLean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 嵌入轻量测试的模块,可能在语言服务器中精译成功,却在构建期间失败。 要修复此问题,可以在包含测试的模块中使用 Lean.Parser.Module.importmeta import 导入这些定义:

求值与元阶段
Eval/Even.leanmodule public section def isEven (n : Nat) : Bool := n % 2 = 0
Eval.leanmodule import Eval.Even /-- info: [true, false] -/ ❌️ Docstring on `#guard_msgs` does not match generated message: - info: [true, false] + error: Invalid `meta` definition `_eval`, `isEven` is not accessible here; consider adding `public meta import Eval.Even` #guard_msgs in Invalid `meta` definition `_eval`, `isEven` is not accessible here; consider adding `public meta import Eval.Even`#eval [isEven 4, isEven 5]
❌️ Docstring on `#guard_msgs` does not match generated message:

- info: [true, false]
+ error: Invalid `meta` definition `_eval`, `isEven` is not accessible here; consider adding `public meta import Eval.Even`

isEven 导入元阶段即可修复此问题:

Eval/Even.leanmodule public section def isEven (n : Nat) : Bool := n % 2 = 0
Eval.leanmodule meta import Eval.Even /-- info: [true, false] -/ #guard_msgs in #eval [isEven 4, isEven 5]

如果存在相应实例,结果会使用 ToExprToStringRepr 实例显示。 如果不存在,而 eval.derive.reprtrue,Lean 会尝试派生合适的 Repr 实例。 如果既找不到也无法派生合适的实例,就会报错。 将 eval.pp 设为 false,可禁止 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 使用 ToExpr 实例。

显示输出

Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 无法显示函数:

Could not synthesize a `ToExpr`, `Repr`, or `ToString` instance for type Nat Nat#eval fun x => x + 1
Could not synthesize a `ToExpr`, `Repr`, or `ToString` instance for type
  Nat  Nat

它能够派生实例,以显示没有 ToStringRepr 实例的输出:

inductive Quadrant where | nw | sw | se | ne Quadrant.nw#eval Quadrant.nw
Quadrant.nw

派生出的实例不会被保存。 禁用 eval.derive.repr 会导致 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 失败:

set_option eval.derive.repr false Could not synthesize a `ToExpr`, `Repr`, or `ToString` instance for type Quadrant#eval Quadrant.nw
Could not synthesize a `ToExpr`, `Repr`, or `ToString` instance for type
  Quadrant
🔗选项
eval.pp

默认值:true

启用后(默认),#eval 会尝试使用 ToExpr 实例,以便用通常的美化打印器输出结果; 禁用后则使用 ReprToString 实例。

🔗选项
eval.type

默认值:false

启用后(默认为禁用),#eval 会美化打印求值结果的类型。

🔗选项
eval.derive.repr

默认值:true

启用后(默认),#eval 会在没有其他输出方式时尝试自动派生 Repr 实例。

为单子定义合适的 MonadLift关于提升单子的章节介绍了 MonadLiftMonadEval 实例,即可使其能够在 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 中执行。 正如 MonadLiftTMonadLift 实例的传递闭包,MonadEvalT 也是 MonadEval 实例的传递闭包。 与 MonadLiftT 一样,用户不应直接定义额外的 MonadEvalT 实例。

🔗类型类
MonadEval.{u, v, w} (m : semiOutParam (Type u Type v)) (n : Type u Type w) : Type (max (max (u + 1) v) w)
MonadEval.{u, v, w} (m : semiOutParam (Type u Type v)) (n : Type u Type w) : Type (max (max (u + 1) v) w)

用于适配单子的类型类。它与 MonadLift 相似,但在必要时,实例合成可以使用默认状态 来合成这样的实例。每个 MonadLift 实例都会给出一个 MonadEval 实例。

该类服务于 #eval 命令;此命令会查找 MonadEval m CommandElabMMonadEval m IO 实例。

MonadEval.mk.{u, v, w}
monadEval : {α : Type u}  m α  n α

将单子 m 中的值求值到单子 n 中。

🔗类型类
MonadEvalT.{u, v, w} (m : Type u Type v) (n : Type u Type w) : Type (max (max (u + 1) v) w)
MonadEvalT.{u, v, w} (m : Type u Type v) (n : Type u Type w) : Type (max (max (u + 1) v) w)

MonadEval 的传递闭包。

MonadEvalT.mk.{u, v, w}
monadEval : {α : Type u}  m α  n α

将单子 m 中的值求值到单子 n 中。

3.2. 归约项🔗

Lean.reduceCmd : command`#reduce <expression>` reduces the expression `<expression>` to its normal form. This involves applying reduction rules until no further reduction is possible. By default, proofs and types within the expression are not reduced. Use modifiers `(proofs := true)` and `(types := true)` to reduce them. Recall that propositions are types in Lean. **Warning:** This can be a computationally expensive operation, especially for complex expressions. Consider using `#eval <expression>` for simple evaluation/execution of expressions. #reduce 命令会反复对项应用归约,直到无法再归约为止。 归约会在绑定器下进行;但为避免意外的性能下降,除非启用了 Lean.reduceCmd : command`#reduce <expression>` reduces the expression `<expression>` to its normal form. This involves applying reduction rules until no further reduction is possible. By default, proofs and types within the expression are not reduced. Use modifiers `(proofs := true)` and `(types := true)` to reduce them. Recall that propositions are types in Lean. **Warning:** This can be a computationally expensive operation, especially for complex expressions. Consider using `#eval <expression>` for simple evaluation/execution of expressions. #reduce 的相应选项,否则会跳过证明和类型。 与 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 命令不同,归约不能产生副作用,并且结果会显示为项,而不是通过 ToStringRepr 实例显示。

一般而言,Lean.reduceCmd : command`#reduce <expression>` reduces the expression `<expression>` to its normal form. This involves applying reduction rules until no further reduction is possible. By default, proofs and types within the expression are not reduced. Use modifiers `(proofs := true)` and `(types := true)` to reduce them. Recall that propositions are types in Lean. **Warning:** This can be a computationally expensive operation, especially for complex expressions. Consider using `#eval <expression>` for simple evaluation/execution of expressions. #reduce 主要用于诊断定义相等与证明项方面的问题,而 Lean.Parser.Command.eval : command`#eval e` evaluates the expression `e` by compiling it and running the compiled code. It then prints the resulting value. * The command attempts to use `ToExpr`, `Repr`, or `ToString` instances to print the result. * If `e` is a monadic value of type `m ty`, then the command tries to adapt the monad `m` to one of the monads that `#eval` supports, which include `IO`, `CoreM`, `MetaM`, `TermElabM`, and `CommandElabM`. Users can define `MonadEval` instances to extend the list of supported monads. The `#eval` command gracefully degrades in capability depending on what is imported. Importing the `Lean.Elab.Command` module provides full capabilities. Due to unsoundness, `#eval` refuses to evaluate expressions that depend on `sorry`, even indirectly, since the presence of `sorry` can lead to runtime instability and crashes. This check can be overridden with the `#eval! e` command. Options: * If `eval.pp` is true (default: true) then tries to use `ToExpr` instances to make use of the usual pretty printer. Otherwise, only tries using `Repr` and `ToString` instances. * If `eval.type` is true (default: false) then pretty prints the type of the evaluated value. * If `eval.derive.repr` is true (default: true) then attempts to auto-derive a `Repr` instance when there is no other way to print the result. See also: `#reduce e` for evaluation by term reduction. #eval 更适合计算项的值。 尤其是,使用良基递归定义或定义为部分不动点的函数,使用归约引擎计算时要么非常缓慢,要么根本不会归约。

语法归约项
command ::= ...
    | `#reduce <expression>` reduces the expression `<expression>` to its normal form. This
involves applying reduction rules until no further reduction is possible.

By default, proofs and types within the expression are not reduced. Use modifiers
`(proofs := true)`  and `(types := true)` to reduce them.
Recall that propositions are types in Lean.

**Warning:** This can be a computationally expensive operation,
especially for complex expressions.

Consider using `#eval <expression>` for simple evaluation/execution
of expressions.
#reduce Configuration for the `#reduce` command. ((ident := term))* term

#reduce <expression> 将表达式 <expression> 归约至范式,即持续应用归约规则, 直到无法继续归约。

默认不归约表达式中的证明和类型。使用修饰项 (proofs := true)(types := true) 可分别归约它们。请注意,在 Lean 中命题也是类型。

**警告:**这一操作的计算开销可能很大,复杂表达式尤其如此。

对表达式进行简单求值或执行时,请考虑使用 #eval <expression>

归约函数

归约一个项会得到它在 Lean 逻辑中的范式。 由于底层项先被归约再显示,因此不需要 ToStringRepr 实例。 函数也可以像其他任何项一样显示。

在某些情况下,此范式很短,并且类似于人会编写的项:

fun x => x.succ#reduce (fun x => x + 1)
fun x => x.succ

在另一些情况下,则会暴露诸如加法这类函数被精译到 Lean 核心逻辑中的细节,参见函数的精译

fun x => (Nat.rec fun x => x, PUnit.unit (fun n n_ih => fun x => (n_ih.1 x).succ, n_ih) x).1 1#reduce (fun x => 1 + x)
fun x => (Nat.rec fun x => x, PUnit.unit (fun n n_ih => fun x => (n_ih.1 x).succ, n_ih) x).1 1

3.3. 检查类型🔗

语法检查类型

#check 可用于精译一个项并检查其类型。

command ::= ...
    | #check term

如果所提供的项是一个标识符,且它是某个全局常量的名称,那么 #check 会打印其签名。 否则,该项会被精译为 Lean 项,并打印其类型。

Lean.Parser.Command.check : command#check 对项的精译并不要求该项被完全精译;其中可以包含元变量。 只要按原样书写的项可能具有某个类型,精译就会成功。 如果某个必需的实例绝无可能合成,则精译失败;由元变量导致的合成问题不会阻止精译。

#check 与未确定的类型

在此示例中,列表元素的类型尚未确定,因此类型中包含一个元变量:

fun x => [x] : ?m.4 List ?m.4#check fun x => [x]
fun x => [x] : ?m.4  List ?m.4

在此示例中,被相加项的类型和加法的结果类型都未知,因为 HAdd 允许不同类型的项相加。 在幕后,一个元变量表示未知的 HAdd 实例。

fun x => x + x : (x : ?m.7) ?m.8 x#check fun x => x + x
fun x => x + x : (x : ?m.7)  ?m.8 x
语法测试类型错误
command ::= ...
    | #check_failure term

Lean.Parser.Command.check : command#check 的这一变体使用与 Lean.Parser.Command.check : command#check 相同的过程来精译该项。 如果精译成功,则报错;如果精译失败,则不报错。 部分精译后的项以及所发现的任何类型信息都会添加到消息日志中。

检查类型错误

尝试把字符串与自然数相加会如预期般失败:

"one" + 1 : ?m.5#check_failure failed to synthesize instance of type class HAdd String Nat ?m.5 Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command."one" + 1
failed to synthesize instance of type class
  HAdd String Nat ?m.5

Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.

尽管如此,仍可得到一个部分精译的项:

"one" + 1 : ?m.5

3.4. 合成实例🔗

语法合成实例
command ::= ...
    | #synth term

Lean.Parser.Command.synth : command#synth 命令会调用 Lean 的类型类解析机制,并尝试执行实例合成,为给定类型类查找实例。 如果成功,则输出所得的实例项。

合成类型类实例

Lean 使用类型类重载加法等操作。 + 运算符是调用 HAdd.hAdd 的记法,而它是 HAdd 类型类中唯一的方法。 此示例表明,Lean 允许我们将两个整数相加,并且结果仍为整数:

instHAdd#synth HAdd Int Int Int
instHAdd

默认情况下,Lean 不会在输出项中显示隐式参数。 然而,实例参数本身是隐式的,这会降低此输出在理解实例合成时的实用性。 将选项 pp.explicit 设为 true 会使 Lean 显示隐式参数,包括实例:

set_option pp.explicit true in @instHAdd Int Int.instAdd#synth HAdd Int Int Int
@instHAdd Int Int.instAdd

如下类型类实例合成失败所示,Lean 不允许把整数与字符串相加:

failed to synthesize HAdd Int String String Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.#synth HAdd Int String String
failed to synthesize
  HAdd Int String String

Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command.

3.5. 查询上下文🔗

#print 命令族用于向 Lean 查询有关定义的信息。

语法打印定义
command ::= ...
    | #print ident

打印常量的定义。

使用 Lean.Parser.Command.print : command#print 打印定义时,会将定义打印为一个项。 使用策略证明的定理,在打印为项时可能非常庞大。

语法打印字符串
command ::= ...
    | #print str

将字符串字面量添加到 Lean 的消息日志

语法打印公理
command ::= ...
    | 显示一个声明直接或间接使用的公理。请参阅[参考手册](https://lean-lang.org/doc/reference/4.34.0-rc1/find/?domain=Verso.Genre.Manual.section&name=validating-proofs),了解如何解释输出。#print axioms ident

列出该常量传递依赖的所有公理。更多信息参见公理文档

打印公理

以下两个函数都交换一对位向量中的元素:

def swap (x y : BitVec 32) : BitVec 32 × BitVec 32 := (y, x) def swap' (x y : BitVec 32) : BitVec 32 × BitVec 32 := let x := x ^^^ y let y := x ^^^ y let x := x ^^^ y (x, y)

可以使用函数外延性化简器bv_decide 证明它们相等:

theorem swap_eq_swap' : swap = swap' := swap = swap' x:BitVec 32y:BitVec 32swap x y = swap' x y x:BitVec 32y:BitVec 32y = x ^^^ y ^^^ (x ^^^ y ^^^ y) x = x ^^^ y ^^^ y All goals completed! 🐙

所得证明使用了若干公理:

'swap_eq_swap'' depends on axioms: [propext, Classical.choice, Quot.sound, swap_eq_swap'._native.bv_decide.ax_3]#print axioms swap_eq_swap'
'swap_eq_swap'' depends on axioms: [propext, Classical.choice, Quot.sound, swap_eq_swap'._native.bv_decide.ax_3]

公理 swap_eq_swap'._native.bv_decide.ax_3bv_decide 生成,这表明使用了原生代码将外部证明证书转换为 Lean 证明项。

语法打印方程

命令 Lean.Parser.Command.printEqns : command#print equations(可缩写为 Lean.Parser.Command.printEqns : command#print eqns)会显示函数的方程引理

command ::= ...
    | #print equations ident
command ::= ...
    | #print eqns ident
打印方程
def intersperse (x : α) : List α List α | y :: z :: zs => y :: x :: intersperse x (z :: zs) | xs => xs equations: @[backward_defeq] theorem intersperse.eq_1.{u_1} : {α : Type u_1} (x y z : α) (zs : List α), intersperse x (y :: z :: zs) = y :: x :: intersperse x (z :: zs) theorem intersperse.eq_2.{u_1} : {α : Type u_1} (x : α) (x_1 : List α), (∀ (y z : α) (zs : List α), x_1 = y :: z :: zs False) intersperse x x_1 = x_1#print equations intersperse
equations:
@[backward_defeq] theorem intersperse.eq_1.{u_1} :  {α : Type u_1} (x y z : α) (zs : List α),
  intersperse x (y :: z :: zs) = y :: x :: intersperse x (z :: zs)
theorem intersperse.eq_2.{u_1} :  {α : Type u_1} (x : α) (x_1 : List α),
  (∀ (y z : α) (zs : List α), x_1 = y :: z :: zs  False)  intersperse x x_1 = x_1

它不会打印定义方程,也不会打印展开方程:

intersperse.eq_def.{u_1} {α : Type u_1} (x : α) (x✝ : List α) : intersperse x x✝ = match x✝ with | y :: z :: zs => y :: x :: intersperse x (z :: zs) | xs => xs#check intersperse.eq_def
intersperse.eq_def.{u_1} {α : Type u_1} (x : α) (x✝ : List α) :
  intersperse x x✝ =
    match x✝ with
    | y :: z :: zs => y :: x :: intersperse x (z :: zs)
    | xs => xs
intersperse.eq_unfold.{u_1} : @intersperse = fun {α} x x_1 => match x_1 with | y :: z :: zs => y :: x :: intersperse x (z :: zs) | xs => xs#check intersperse.eq_unfold
intersperse.eq_unfold.{u_1} :
  @intersperse = fun {α} x x_1 =>
    match x_1 with
    | y :: z :: zs => y :: x :: intersperse x (z :: zs)
    | xs => xs
语法作用域信息

#where 描述当前作用域的状态,包括当前命名空间、open 打开的命名空间、 universevariable 命令,以及由 set_option 设置的选项。

command ::= ...
    | `#where` gives a description of the state of the current scope scope.
This includes the current namespace, `open` namespaces, `universe` and `variable` commands,
and options set with `set_option`.
#where
作用域信息

Lean.Parser.Command.where : command`#where` gives a description of the state of the current scope scope. This includes the current namespace, `open` namespaces, `universe` and `variable` commands, and options set with `set_option`. #where 命令会显示对当前节作用域所做的全部修改,包括当前作用域与其嵌套所在的各层作用域中的修改。

public section open Nat namespace A variable (n : Nat) namespace B open List set_option pp.tagAppFns true public section namespace A.B open Nat List variable (n : Nat) set_option pp.tagAppFns true#where end A.B end
public section

namespace A.B

open Nat List

variable (n : Nat)

set_option pp.tagAppFns true
语法检查 Lean 版本

显示当前 Lean 版本、目标三元组与平台信息;其中版本号来自 Lean.versionString

command ::= ...
    | Shows the current Lean version. Prints `Lean.versionString`. #version

3.6. 使用 #guard_msgs 测试输出🔗

Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 命令可用于确保某条命令输出的消息符合预期。 配合本节中的交互命令,可以构造一个仅在输出符合预期时才会精译成功的文件;这样的文件可在 Lake 中用作测试驱动程序

语法记录预期输出
command ::= ...
    | `/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档
注释的内容匹配。

基本示例:
```lean
/--
error: Unknown identifier `x`
-/
#guard_msgs in
example : α := x
```
这会检查确有此错误,然后消费该消息。

默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告:
```lean
/--
warning: declaration uses 'sorry'
-/
#guard_msgs(warning) in
example : α := sorry
```
或只选择错误:
```lean
#guard_msgs(error) in
example : α := sorry
```
在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告:
```lean
#guard_msgs(error, drop warning) in
example : α := sorry
```

一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句:
```
#guard_msgs (configElt,*) in cmd
```
默认配置列表为
`(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。

消息过滤器按严重程度选择消息:
- `info`、`warning`、`error`:具有相应严重程度的非跟踪消息;
- `trace`:跟踪消息;
- `all`:所有消息。

过滤器可带有指定操作的前缀:
- `check`(默认):捕获并检查消息;
- `drop`:丢弃消息;
- `pass`:让消息继续传递。

若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式
添加 `pass all`。

空白处理(先去除开头和末尾的空白):
- `whitespace := exact` 要求空白完全匹配;
- `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行;
- `whitespace := lax` 在匹配前把连续空白压缩为一个空格。

消息排序:
- `ordering := exact` 使用消息的原始顺序(默认);
- `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。

位置信息:
- `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围;
- `positions := false` 不报告位置信息。

子串匹配:
- `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心
  消息一部分的情况;
- `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。

稳定输出:
消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。
使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留
`?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为
`?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的
松散自由变量名替换为 `_fvar._`。

例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。

命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码
检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令
运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。
因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。A `docComment` parses a "documentation comment" like `/-- foo -/`. This is not treated like
a regular comment (that is, as whitespace); it is parsed and forms part of the syntax tree structure.

At parse time, `docComment` checks the value of the `doc.verso` option. If it is true, the contents
are parsed as Verso markup. If not, the contents are treated as plain text or Markdown. Use
`plainDocComment` to always treat the contents as plain text.

A plain text doc comment node contains a `/--` atom and then the remainder of the comment, `foo -/`
in this example. Use `TSyntax.getDocString` to extract the body text from a doc string syntax node.
A Verso comment node contains the `/--` atom, the document's syntax tree, and a closing `-/` atom.
docComment?
      #guard_msgs ((guardMsgsSpecElt,*))? in
      command

/-- ... -/ #guard_msgs in cmd 捕获命令 cmd 生成的消息,并检查它们是否与文档 注释的内容匹配。

基本示例:

/-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x

这会检查确有此错误,然后消费该消息。

默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告:

/--
warning: declaration uses 'sorry'
-/
#guard_msgs(warning) in
example : α := sorry

或只选择错误:

#guard_msgs(error) in declaration uses `sorry`example : α := sorry

在上一个示例中,因为警告未被捕获,sorry 上仍会产生警告。可用下述写法彻底丢弃警告:

#guard_msgs(error, drop warning) in example : α := sorry

一般而言,#guard_msgs 接受一组置于圆括号内、以逗号分隔的配置子句:

#guard_msgs (

默认配置列表为 (check all, whitespace := normalized, ordering := exact, positions := false, substring := false)

消息过滤器按严重程度选择消息:

  • infowarningerror:具有相应严重程度的非跟踪消息;

  • trace:跟踪消息;

  • all:所有消息。

过滤器可带有指定操作的前缀:

  • check(默认):捕获并检查消息;

  • drop:丢弃消息;

  • pass:让消息继续传递。

若未指定过滤器,则假定为 check all。否则从左至右处理这些过滤器,并在末尾隐式 添加 pass all

空白处理(先去除开头和末尾的空白):

  • whitespace := exact 要求空白完全匹配;

  • whitespace := normalized 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行;

  • whitespace := lax 在匹配前把连续空白压缩为一个空格。

消息排序:

  • ordering := exact 使用消息的原始顺序(默认);

  • ordering := sorted 按字典序排列消息,便于测试消息顺序不确定的命令。

位置信息:

  • positions := true 报告所有消息相对于 #guard_msgs 所在行的范围;

  • positions := false 不报告位置信息。

子串匹配:

  • substring := true 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况;

  • substring := false(默认)要求精确匹配(允许空白归一化造成的差异)。

稳定输出: 消息含有自动生成的名称(例如元变量 ?m.47)时,输出可能随运行或 Lean 版本而变化。 使用 set_option pp.mvars.anonymous false 可把匿名元变量替换为 ?_,同时保留 ?a 等用户命名的元变量。也可使用 set_option pp.mvars false 把所有元变量替换为 ?_。类似地,set_option pp.fvars.anonymous false 会把 _fvar.22 之类的 松散自由变量名替换为 _fvar._

例如,#guard_msgs (error, drop all) in cmd 表示检查错误并丢弃其他一切消息。

命令精译器对 #guard_msgs 的代码检查有特殊支持。#guard_msgs 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 #guard_msgs 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 #guard_msgs 时,顶层命令精译器才运行代码检查器。

测试返回值

Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 命令可以确保一组测试用例通过:

def reverse : List α List α := helper [] where helper acc | [] => acc | x :: xs => helper (x :: acc) xs /-- info: [] -/ #guard_msgs in #eval reverse ([] : List Nat) /-- info: ['c', 'b', 'a'] -/ #guard_msgs in #eval reverse "abc".toList

Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 命令的行为可通过三种方式指定:

  1. 提供筛选器,选择要检查的消息子集

  2. 指定空白字符比较策略

  3. 决定按消息内容排序,还是按消息产生的顺序排序

这些配置选项写在圆括号中,并以逗号分隔。

语法指定 #guard_msgs 的行为
guardMsgsSpecElt ::=
    A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
guardMsgsFilter
guardMsgsSpecElt ::= ...
    | Whitespace handling for `#guard_msgs`:
- `whitespace := exact` requires an exact whitespace match.
- `whitespace := normalized` converts all newline characters to a space before matching
  (the default). This allows breaking long lines.
- `whitespace := lax` collapses whitespace to a single space before matching.
In all cases, leading and trailing whitespace is trimmed before matching.
whitespace := guardMsgsWhitespaceArg
guardMsgsSpecElt ::= ...
    | Message ordering for `#guard_msgs`:
- `ordering := exact` uses the exact ordering of the messages (the default).
- `ordering := sorted` sorts the messages in lexicographic order.
  This helps with testing commands that are non-deterministic in their ordering.
ordering := guardMsgsOrderingArg

Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 有三类选项:筛选器、空白字符比较策略和排序方式。

语法#guard_msgs 的输出筛选器
A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
guardMsgsFilter ::=
    A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
drop? all
A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
guardMsgsFilter ::= ...
    | A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
drop? info
A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
guardMsgsFilter ::= ...
    | A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
drop? warning
A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
guardMsgsFilter ::= ...
    | A message filter specification for `#guard_msgs`.
- `info`, `warning`, `error`: capture (non-trace) messages with the given severity level.
- `trace`: captures trace messages
- `all`: capture all messages.

The filters can be prefixed with
- `check` (the default): capture and check the message
- `drop`: drop the message
- `pass`: let the message pass through

If no filter is specified, `check all` is assumed.  Otherwise, these filters are processed in
left-to-right order, with an implicit `pass all` at the end.
drop? error

#guard_msgs 的消息过滤器规范。

  • infowarningerror:捕获具有相应严重程度的非跟踪消息;

  • trace:捕获跟踪消息;

  • all:捕获所有消息。

过滤器可带有下列前缀:

  • check(默认):捕获并检查消息;

  • drop:丢弃消息;

  • pass:让消息继续传递。

若未指定过滤器,则假定为 check all。否则从左至右处理这些过滤器,并在末尾隐式 添加 pass all

语法#guard_msgs 的空白字符比较
guardMsgsWhitespaceArg ::=
    exact
guardMsgsWhitespaceArg ::= ...
    | lax
guardMsgsWhitespaceArg ::= ...
    | normalized

比较消息时,始终忽略开头和结尾的空白字符。在此基础上,还可使用以下设置:

  • whitespace := exact 要求空白字符完全匹配。

  • whitespace := normalized 在匹配前将所有换行符转换为空格(默认设置)。这样便可将长行断行。

  • whitespace := lax 在匹配前将连续空白字符折叠为一个空格。

选项 guard_msgs.diff 控制当预期消息与实际产生的消息不匹配时,Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 所产生错误消息的内容。 默认情况下,Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 会逐行显示差异:行首的 + 表示来自实际产生的消息的行,行首的 - 表示来自预期消息的行。 当消息很大而差异很小时,这有助于发现差异所在。 将 guard_msgs.diff 设为 false 后,Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 将只显示实际产生的消息,可将它与源文件中的预期消息进行比较。 如果消息之间的差异令人困惑或信息过载,这样做会比较方便。

🔗选项
guard_msgs.diff

默认值:true

启用后(默认),如果预期消息与实际消息不匹配,#guard_msgs 会显示二者的差异; 禁用后则显示实际消息。

显示差异

Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 命令可用于测试玫瑰树 Tree 的定义,以及创建这种树的函数 Tree.big

inductive Tree (α : Type u) : Type u where | val : α Tree α | branches : List (Tree α) Tree α def Tree.big (n : Nat) : Tree Nat := if n < 5 then .branches [.val n, .val (n - 1), .val n, .val (n - 2)] else .branches [.big (n / 2), .big (n / 3)]

然而,当输出很大时,可能很难看出测试失败源自何处:

set_option guard_msgs.diff false /-- info: Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0], Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]] -/ ❌️ Docstring on `#guard_msgs` does not match generated message: info: Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]], Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]#guard_msgs in Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]], Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]#eval Tree.big 20

求值产生:

Tree.branches
  [Tree.branches
     [Tree.branches
        [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0],
         Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]],
      Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]],
   Tree.branches
     [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1],
      Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]

禁用 guard_msgs.diff 时,Lean.guardMsgsCmd : command`/-- ... -/ #guard_msgs in cmd` 捕获命令 `cmd` 生成的消息,并检查它们是否与文档 注释的内容匹配。 基本示例: ```lean /-- error: Unknown identifier `x` -/ #guard_msgs in example : α := x ``` 这会检查确有此错误,然后消费该消息。 默认情况下,该命令捕获所有消息,但可调整过滤条件。例如,只选择警告: ```lean /-- warning: declaration uses 'sorry' -/ #guard_msgs(warning) in example : α := sorry ``` 或只选择错误: ```lean #guard_msgs(error) in example : α := sorry ``` 在上一个示例中,因为警告未被捕获,`sorry` 上仍会产生警告。可用下述写法彻底丢弃警告: ```lean #guard_msgs(error, drop warning) in example : α := sorry ``` 一般而言,`#guard_msgs` 接受一组置于圆括号内、以逗号分隔的配置子句: ``` #guard_msgs (configElt,*) in cmd ``` 默认配置列表为 `(check all, whitespace := normalized, ordering := exact, positions := false, substring := false)`。 消息过滤器按严重程度选择消息: - `info`、`warning`、`error`:具有相应严重程度的非跟踪消息; - `trace`:跟踪消息; - `all`:所有消息。 过滤器可带有指定操作的前缀: - `check`(默认):捕获并检查消息; - `drop`:丢弃消息; - `pass`:让消息继续传递。 若未指定过滤器,则假定为 `check all`。否则从左至右处理这些过滤器,并在末尾隐式 添加 `pass all`。 空白处理(先去除开头和末尾的空白): - `whitespace := exact` 要求空白完全匹配; - `whitespace := normalized` 在匹配前把所有换行符转换为空格(默认),从而允许拆分长行; - `whitespace := lax` 在匹配前把连续空白压缩为一个空格。 消息排序: - `ordering := exact` 使用消息的原始顺序(默认); - `ordering := sorted` 按字典序排列消息,便于测试消息顺序不确定的命令。 位置信息: - `positions := true` 报告所有消息相对于 `#guard_msgs` 所在行的范围; - `positions := false` 不报告位置信息。 子串匹配: - `substring := true` 检查文档注释是否为输出的子串(在空白归一化之后),适用于只关心 消息一部分的情况; - `substring := false`(默认)要求精确匹配(允许空白归一化造成的差异)。 稳定输出: 消息含有自动生成的名称(例如元变量 `?m.47`)时,输出可能随运行或 Lean 版本而变化。 使用 `set_option pp.mvars.anonymous false` 可把匿名元变量替换为 `?_`,同时保留 `?a` 等用户命名的元变量。也可使用 `set_option pp.mvars false` 把所有元变量替换为 `?_`。类似地,`set_option pp.fvars.anonymous false` 会把 `_fvar.22` 之类的 松散自由变量名替换为 `_fvar._`。 例如,`#guard_msgs (error, drop all) in cmd` 表示检查错误并丢弃其他一切消息。 命令精译器对 `#guard_msgs` 的代码检查有特殊支持。`#guard_msgs` 本身希望捕获代码 检查器的警告,因此会把所附命令当作顶层命令精译。然而,命令精译器会对所有顶层命令 运行代码检查器,其中也包括 `#guard_msgs` 自身,这会导致重复警告或警告未被捕获。 因此,仅当顶层命令中不存在 `#guard_msgs` 时,顶层命令精译器才运行代码检查器。#guard_msgs 命令会报告以下错误:

❌️ Docstring on `#guard_msgs` does not match generated message:

info: Tree.branches
  [Tree.branches
     [Tree.branches
        [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0],
         Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]],
      Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]],
   Tree.branches
     [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1],
      Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]

启用 guard_msgs.diff 后,差异会被突出显示,使错误更加明显:

set_option guard_msgs.diff true in /-- info: Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0, Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]] -/ ❌️ Docstring on `#guard_msgs` does not match generated message: info: Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], - Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0, + Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]], Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]] #guard_msgs in Tree.branches [Tree.branches [Tree.branches [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0], Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]], Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]], Tree.branches [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1], Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]#eval Tree.big 20
❌️ Docstring on `#guard_msgs` does not match generated message:

  info: Tree.branches
    [Tree.branches
       [Tree.branches
          [Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0],
-          Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0,
+          Tree.branches [Tree.val 1, Tree.val 0, Tree.val 1, Tree.val 0]],
        Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1]],
     Tree.branches
       [Tree.branches [Tree.val 3, Tree.val 2, Tree.val 3, Tree.val 1],
        Tree.branches [Tree.val 2, Tree.val 1, Tree.val 2, Tree.val 0]]]

3.7. 格式化输出🔗

Repr 类型类用于为数据提供一种标准表示;该表示可以被解析和求值,从而得到一个等价的值。 这并不是严格的正确性准则:对于某些类型,尤其是嵌入了命题的类型,这一点无法做到。 不过,Repr 实例产生的输出应尽可能接近可被解析和求值的内容。

除了可供机器读取之外,这种表示还应便于人类理解——特别是,行不应过长,嵌套值应缩进。 这是通过两步过程实现的:

  1. Repr 实例生成一个类型为 Std.Format 的中间文档,它紧凑地表示一字符串,这些字符串的区别在于换行和缩进的位置。

  2. 渲染过程根据期望的最大行长等准则,从该集合中选择“最佳”代表。

尤其是,Std.Format 可以组合式地构建,因此 Repr 实例无需考虑周围的缩进上下文。

3.7.1. 格式🔗

Format这里介绍的 API 改编自 Wadler 的工作(Philip Wadler, 2003. “A Prettier Printer”. In The Fun of Programming, A symposium in honour of Professor Richard Bird's 60th birthday.)。它经过修改,以便在严格求值语言中高效运行,并支持元数据标签等额外功能。是字符串集合的一种紧凑表示。 最重要的 Format 操作如下:

字符串

使用 text 构造器可以将 String 转换为 Format。 此构造器已注册为从 StringFormat强制转换,因此通常无需显式调用。 text str 表示仅包含 str 的单元素集合。 如果字符串包含换行字符('\n'),无论分组如何,它们都会无条件地作为换行插入最终输出。 不过,它们会按照当前缩进级别进行缩进。

追加

可以使用 Append Format 实例提供的 ++ 运算符追加两个 Format

分组与换行

构造器 line 表示同时包含 "\n" ++ indent" " 的集合,其中 indent 是一个包含足够空格、可使该行正确缩进的字符串。 从命令式角度看,可以把它视为一个换行:如果当前行有足够空间,它就会被“展平”为空格。 换行出现在分组中:最近一层包围它的 group 运算符应用决定该换行属于哪个分组。 默认情况下,一个分组内的所有 line 要么都表示 "\n",要么都表示 " ";也可以将分组配置为填充行,此时分组中数量最少的一部分 line 表示 "\n"。 不属于任何分组的 line 始终表示 "\n"

缩进

插入换行时,输出也会缩进。 nest n 将文档的缩进增加 n 个空格。 这不足以表示所有 Lean 语法,因为有时要求各列精确对齐。 align 是一种确保输出字符串位于当前缩进级别的文档:如果可能就只插入空格,否则插入一个换行,后接若干空格。

标记

Lean 的交互功能需要能够把输出与其所表示的底层值关联起来。 例如,这使 Lean 开发环境能够在悬停于项、证明状态或错误消息上时呈现精译后的项。 可以使用 tag nNatn 给文档加标签;这些 Nat 应在一张旁表中映射到底层值。

宽度与换行
open Std Format

辅助函数 parenSeq 创建一个带圆括号的序列,并通过分组和缩进使其适应不同的输出宽度。

def parenSeq (xs : List Format) : Format := group <| nest 2 (text "(" ++ line ++ joinSep xs line) ++ line ++ ")"

此文档表示一个带圆括号的数字序列:

def lst : Format := parenSeq nums where nums := [1, 2, 3, 4, 5].map (text s!"{·}")

以默认的 120 字符行宽渲染它时,整个序列会位于一行:

( 1 2 3 4 5 ) #eval IO.println lst.pretty
( 1 2 3 4 5 )

因为所有 line 都属于同一个 group,它们要么全部渲染为空格,要么全部渲染为换行。 如果只有 9 个字符的可用宽度,lst 中的所有 line 都会变成换行:

( 1 2 3 4 5 ) #eval IO.println (lst.pretty (width := 9))
(
  1
  2
  3
  4
  5
)

此文档在另一个带圆括号的序列中包含三份 lst

def lsts := parenSeq [lst, lst, lst]

在默认宽度下,它仍位于一行:

( ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ) #eval IO.println lsts.pretty
( ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) )

如果只有 20 个字符的可用宽度,每次出现的 lst 都会单独占一行。 这是因为,将外层 group 转换为换行已经足以使字符串保持在 20 列以内:

( ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ) #eval IO.println (lsts.pretty (width := 20))
(
  ( 1 2 3 4 5 )
  ( 1 2 3 4 5 )
  ( 1 2 3 4 5 )
)

如果只有 10 个字符的可用宽度,每个数字都必须单独占一行:

( ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ( 1 2 3 4 5 ) ) #eval IO.println (lsts.pretty (width := 10))
(
  (
    1
    2
    3
    4
    5
  )
  (
    1
    2
    3
    4
    5
  )
  (
    1
    2
    3
    4
    5
  )
)
分组与填充
open Std Format

辅助函数 parenSeq 创建一个带圆括号的序列,其中每个元素都另起一行并缩进:

def parenSeq (xs : List Format) : Format := nest 2 (text "(" ++ line ++ joinSep xs line) ++ line ++ ")"

nums 包含从一到二十的数字,是一个格式列表:

def nums : List Format := Nat.fold 20 (init := []) fun i _ ys => text s!"{20 - i}" :: ys [Std.Format.text "1", Std.Format.text "2", Std.Format.text "3", Std.Format.text "4", Std.Format.text "5", Std.Format.text "6", Std.Format.text "7", Std.Format.text "8", Std.Format.text "9", Std.Format.text "10", Std.Format.text "11", Std.Format.text "12", Std.Format.text "13", Std.Format.text "14", Std.Format.text "15", Std.Format.text "16", Std.Format.text "17", Std.Format.text "18", Std.Format.text "19", Std.Format.text "20"]#eval nums

由于 parenSeq 没有引入任何分组,所得文档不会被渲染为一行:

( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println (pretty (parenSeq nums))

可以通过对它们进行分组来修复此问题。 grouped 使用 group 进行分组,而 filled 使用 fill

def grouped := group (parenSeq nums) def filled := fill (parenSeq nums)

两个分组运算符都会使 line 被渲染为空格。 如果空间充足,两者都会渲染为一行:

( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println (pretty grouped)
( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )
( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println (pretty filled)
( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 )

然而,当一行空间不足时,两者的差异便会显现。 除非 group 中的所有换行都能变为空格,否则没有任何一个能变为空格:

( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println (pretty (width := 30) grouped)
(
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
)

另一方面,使用 fill 时,只会按避免宽度过宽所需插入换行:

( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println (pretty (width := 30) filled)
( 1 2 3 4 5 6 7 8 9 10 11 12
  13 14 15 16 17 18 19 20 )

在更长的序列中可以清楚看到 fill 的行为:

( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) #eval IO.println <| pretty (width := 30) (fill (parenSeq (nums ++ nums ++ nums ++ nums)))
( 1 2 3 4 5 6 7 8 9 10 11 12
  13 14 15 16 17 18 19 20 1 2
  3 4 5 6 7 8 9 10 11 12 13 14
  15 16 17 18 19 20 1 2 3 4 5
  6 7 8 9 10 11 12 13 14 15 16
  17 18 19 20 1 2 3 4 5 6 7 8
  9 10 11 12 13 14 15 16 17 18
  19 20 )
字符串中的换行字符

字符串中包含换行字符时,渲染过程会无条件地插入换行。 不过,这些换行仍会遵循当前缩进级别。

文档 str 由一个内嵌了两个换行的字符串组成:

open Std Format def str : Format := text "abc\nxyz\n123"

无论是否分组,打印字符串时都会使用这些换行:

abc xyz 123 #eval IO.println str.pretty
abc
xyz
123
abc xyz 123 #eval IO.println (group str).pretty
abc
xyz
123

由于该字符串并不以换行结尾,第一个字符串的最后一行会与第二个字符串的第一行位于同一行:

abc xyz 123abc xyz 123 #eval IO.println (str ++ str).pretty
abc
xyz
123abc
xyz
123

不过,提高缩进级别会使字符串的三行都从同一列开始:

It is: abc xyz 123 #eval IO.println (text "It is:" ++ indentD str).pretty
It is:
  abc
  xyz
  123
It is: abc xyz 123 #eval IO.println (nest 8 <| text "It is:" ++ align true ++ str).pretty
It is:  abc
        xyz
        123

3.7.1.1. 文档🔗

🔗归纳类型
Std.Format : Type
Std.Format : Type

表示一组字符串;这些字符串的换行位置和缩进各不相同。

给定以列数表示的具体行宽后,可以从中选出占用行数最少的字符串。

美化打印算法基于 Wadler 的论文 A Prettier Printer

Std.Format.nil : Std.Format

空格式。

Std.Format.line : Std.Format

当当前组无法容纳于给定列宽时,可以在此处插入换行。

Std.Format.align (force : Bool) : Std.Format

align 指示格式化器用空格填充至当前缩进层级;若当前位置已达到或越过缩进, 则改为换行。

forcetrue,即使位于已扁平化的组中,也会填充到缩进位置。

示例:

open Std Format in
#eval IO.println (nest 2 <| "." ++ align ++ "a" ++ line ++ "b")
. a b
Std.Format.text : String  Std.Format

包含普通字符串的节点。若字符串含换行,格式化器会发出换行,并缩进到当前层级。

Std.Format.nest (indent : Int) (f : Std.Format) : Std.Format

渲染 f 时,nest indent f 将当前缩进层级增加 indent

示例:

open Std Format in def fmtList (l : List Format) : Format := let f := joinSep l ("," ++ Format.line) group (nest 1 <| "[" ++ f ++ "]")

通常它会写在一行;但如果文本过长,格式化器会在逗号后换行,并把后续行缩进 1 列。

Std.Format.group :
  Std.Format 
    (behavior :
        optParam Std.Format.FlattenBehavior
          Std.Format.FlattenBehavior.allOrNone) 
      Std.Format

为给定的内部 Format 创建新的扁平化组。

Std.Format.tag : Nat  Std.Format  Std.Format

用于把辅助信息(例如 Expr)关联到 Format 对象。

🔗归纳类型

确定当文本超出剩余空间时,应如何在组内插入换行。

  • allOrNone 会把组内每个 Format.line 都变成换行,或者一个也不换行:

    [1, 2, 3]
  • fill 只会把尽可能少的 Format.line 变成换行:

    [1, 2, 3]
Std.Format.FlattenBehavior.allOrNone :
  Std.Format.FlattenBehavior

组内的 Format.line 要么全部变成换行,要么全部变成空格。

Std.Format.FlattenBehavior.fill : Std.Format.FlattenBehavior

组内只有尽可能少的 Format.line 会变成换行。

🔗定义

创建一个组,其中只有尽可能少的 Format.line 被渲染为换行。这等价于将 FlattenBehavior 设为 fill 后调用 Format.group

3.7.1.2. 空文档🔗

空字符串在 Std.Format 中没有唯一的单一表示。 以下各项都表示空字符串:

  • .nil

  • .text ""

  • .text "" ++ .nil

  • .nil ++ .text ""

使用 Std.Format.isEmpty 检查文档是否包含零个字符;若要专门检查它是否为构造器 Std.Format.nil,则使用 Std.Format.isNil

🔗定义

检查给定格式是否按其结构判为空。此检查会把 .align 节点视为空,即使对齐在渲染时可能输出空格或换行;因此返回 true 并不保证最终渲染的字符串为空。

🔗定义

检查 Format 是否恰为构造子 Format.nil。与 Format.isEmpty 不同,此函数不递归检查组合结构。

3.7.1.3. 序列🔗

当存在某种重复内容(例如列表元素)时,本节中的运算符很有用。 通常的做法是在分隔符参数中包含 line,并使用括起运算符

🔗定义

使用 ++ 连接 Format 列表。

🔗定义

以给定格式 sep 分隔并连接列表。列表元素使用 ToFormat.format 格式化。

🔗定义

在每个元素前加上 pre 后连接给定列表。列表元素使用 ToFormat.format 格式化。

🔗定义

在每个元素后加上给定后缀再连接列表。列表元素使用 ToFormat.format 格式化。

3.7.1.4. 缩进🔗

这些运算符使得在 Std.Format.nest 之上实现一致的缩进风格更加容易。

🔗定义

将缩进层级增加默认的缩进量。

🔗定义

默认缩进层级,即两个空格。

🔗定义

插入换行,随后放置 f,并将整体按默认缩进量嵌套。

3.7.1.5. 方括号与圆括号🔗

这些运算符使实现一致的括号风格更加容易。

🔗定义

创建格式 l ++ f ++ r,为它建立扁平化组,并按 l 的长度缩进内容。 该组的 FlattenBehaviorallOrNone;若需 fill,请使用 Std.Format.bracketFill

🔗定义

创建格式 "[" ++ f ++ "]",为它建立扁平化组,并缩进一个空格。 sbracket 是 “square bracket”(方括号)的缩写。

🔗定义

创建格式 "(" ++ f ++ ")",为它建立扁平化组,并缩进一个空格。

🔗定义

创建格式 l ++ f ++ r,为它建立扁平化组,并按 l 的长度缩进内容。 该组的 FlattenBehaviorfill;若需 allOrNone,请使用 Std.Format.bracket

3.7.1.6. 渲染🔗

ToString Std.Format 实例使用默认参数调用 Std.Format.pretty

渲染文档有两种方式:

  • 使用 pretty 构造 String。 必须先完整构造整个字符串,之后才能将其中任何内容发送给用户。

  • 使用 prettyM,利用某个 Monad 中的效果,增量地发出 String。 每一行一经渲染就会被发出。 这适用于流式输出。

🔗定义
Std.Format.pretty (f : Std.Format) (width : Nat := Std.Format.defWidth) (indent column : Nat := 0) : String
Std.Format.pretty (f : Std.Format) (width : Nat := Std.Format.defWidth) (indent column : Nat := 0) : String

Format 渲染为字符串。

  • width:总宽度;

  • indent:换行后的初始缩进(后续换行可能进一步增加缩进);

  • column:让第一行比通常情况提前 column 个字符换行(当输出字符串将从第 column 列开始打印时很有用)。

🔗定义

目标输出的默认宽度,即 120 列。

🔗定义
Std.Format.prettyM {m : Type Type} (f : Std.Format) (w : Nat) (indent : Nat := 0) [Monad m] [Std.Format.MonadPrettyFormat m] : m Unit
Std.Format.prettyM {m : Type Type} (f : Std.Format) (w : Nat) (indent : Nat := 0) [Monad m] [Std.Format.MonadPrettyFormat m] : m Unit

使用单子 m 中的效应和 MonadPrettyFormat 的方法渲染 Format。 每一行一经渲染就会被发出,而不等待整个文档渲染完毕。

  • w:总宽度;

  • indent:换行后的初始缩进(后续换行可能进一步增加缩进)。

🔗类型类
Std.Format.MonadPrettyFormat (m : Type Type) : Type
Std.Format.MonadPrettyFormat (m : Type Type) : Type

可用于增量渲染 Format 对象的单子。

pushOutput : String  m Unit

发出字符串 s

pushNewline : Nat  m Unit

发出一个换行,随后发出 indent 列缩进。

currColumn : m Nat

获取下一个字符串将从哪一列开始发出。

startTag : Nat  m Unit

开始一个以 tag 标记的区域。

endTags : Nat  m Unit

退出 count 个已打开标签的作用域。

3.7.1.7. ToFormat🔗

Std.ToFormat 类用于提供一种格式化值的标准方式,并不要求这种格式是有效的 Lean 语法。 错误消息和某些序列连接运算符会使用这些实例。

🔗类型类
Std.ToFormat.{u} (α : Type u) : Type u
Std.ToFormat.{u} (α : Type u) : Type u

指定一种面向用户的方式,把类型 α 的值转换成 Format 对象;所得字符串不要求是 有效代码。Repr 类与之相似,但其实例应生成有效的 Lean 代码。

Std.ToFormat.mk.{u}
format : α  Std.Format

将值转换成 Format 对象,不要求所得字符串是有效代码。

3.7.2. Repr🔗

Repr 实例描述如何将值表示为 Std.Format。 因为它们应当发出有效的 Lean 语法,所以这些实例需要考虑优先级。 插入最大数量的圆括号确实可行,但会使人类更难阅读所得输出。

🔗类型类
Repr.{u} (α : Type u) : Type u
Repr.{u} (α : Type u) : Type u

把某种类型的值转换为 Format 的标准方式。渲染所得 Format 后,结果应尽可能接近 可以解析回输入值的文本。

Repr.mk.{u}
reprPrec : α  Nat  Std.Format

在给定优先级下把类型 α 的值转换为 Format。可利用优先级值避免不必要的圆括号。

🔗定义
repr.{u_1} {α : Type u_1} [Repr α] (a : α) : Std.Format
repr.{u_1} {α : Type u_1} [Repr α] (a : α) : Std.Format

使用 aRepr 实例将其转换为 Format,初始优先级为 0。

🔗定义
reprStr.{u_1} {α : Type u_1} [Repr α] (a : α) : String
reprStr.{u_1} {α : Type u_1} [Repr α] (a : α) : String

使用 aRepr 实例将其转换为 String,并以默认的 120 列宽度渲染 Format。 初始优先级为 0。

最多的圆括号

类型 NatOrInt 可以包含一个 Nat 或一个 Int

inductive NatOrInt where | nat : Nat NatOrInt | int : Int NatOrInt

这个 Repr NatOrInt 实例通过插入许多圆括号来确保输出是有效的 Lean 语法:

instance : Repr NatOrInt where reprPrec x _ := .nestD <| .group <| match x with | .nat n => .text "(" ++ "NatOrInt.nat" ++ .line ++ "(" ++ repr n ++ "))" | .int i => .text "(" ++ "NatOrInt.int" ++ .line ++ "(" ++ repr i ++ "))"

无论它包含 Nat、非负的 Int,还是负的 Int,结果都可以被解析:

open NatOrInt in (NatOrInt.nat (3)) (NatOrInt.int (5)) (NatOrInt.int (-5)) #eval do IO.println <| repr <| nat 3 IO.println <| repr <| int 5 IO.println <| repr <| int (-5)
(NatOrInt.nat (3))
(NatOrInt.int (5))
(NatOrInt.int (-5))

不过,(NatOrInt.nat (3)) 并不是特别惯用的 Lean 写法,而且冗余的圆括号会使大型表达式难以阅读。

方法 Repr.reprPrec 具有如下签名:

Repr.reprPrec.{u} {α : Type u} [Repr α] : α Nat Std.Format

第一个显式参数是要表示的值,第二个则是该值所在上下文的优先级。 可以利用此优先级决定是否插入圆括号:如果实例所生成语法的优先级不高于其上下文的优先级,就需要圆括号。

3.7.2.1. 如何编写 Repr 实例🔗

Lean 可以使用实例派生为大多数类型自动生成合适的 Repr 实例。 不过,在某些情况下需要手动编写实例:

编写自定义 Repr 实例时,请遵循以下约定:

优先级

检查优先级,按需添加圆括号,并将正确的优先级传给内嵌数据的 reprPrec 实例。 每个实例都负责在需要时为自身加上圆括号;实例通常不应为对 reprPrec 的递归调用加圆括号。

函数应用具有最高优先级 max_prec。 辅助函数 Repr.addAppParenreprArg 分别在需要时为应用加上圆括号,以及把适当的优先级传给函数参数。

完全限定名称

Repr 实例无法访问给定位置处已打开的命名空间集合。 环境中所有常量的名称都应完全限定,以消除歧义。

默认嵌套

嵌套数据应使用 nestD 缩进,以确保各实例之间的缩进一致。

分组与换行

每个包含换行的 Repr 实例,其输出都应包围在 group 中。 此外,如果所得代码包含概念上相互嵌套的表达式,则应在每个嵌套层级外围插入一个 group。 通常应在以下位置插入换行:

  • 构造器与它的每个参数之间

  • := 之后

  • , 之后

  • 结构实例记法的左右花括号与其内容之间

  • 中缀运算符之后,而不是之前

圆括号与方括号

应使用 Std.Format.bracket 或其特化形式插入圆括号和方括号:圆括号使用 Std.Format.paren,方括号使用 Std.Format.sbracket。 这些运算符对带圆括号或方括号表达式的内容进行对齐,其方式与 Lean 相同。 结尾的圆括号和方括号不应单独占一行,而应与其内容保持在一起。

🔗定义

若上下文优先级 prec 至少为函数应用的优先级,则给 f 加上圆括号。 它与 reprArg 配合使用,可正确地为函数应用语法添加圆括号。

🔗定义
reprArg.{u_1} {α : Type u_1} [Repr α] (a : α) : Std.Format
reprArg.{u_1} {α : Type u_1} [Repr α] (a : α) : Std.Format

使用 aRepr 实例将其转换为 Format,并把优先级设为函数应用的优先级。 它与 Repr.addAppParen 配合使用,可正确地为函数应用语法添加圆括号。

带构造器的归纳类型

归纳类型 N.NatOrInt 可以包含一个 Nat 或一个 Int

namespace N inductive NatOrInt where | nat : Nat NatOrInt | int : Int NatOrInt

Repr NatOrInt 实例遵循上述约定:

  • 右侧是一个函数应用,因此它使用 Repr.addAppParen 在必要时添加圆括号。

  • 圆括号包围整个主体,且不额外加入 line

  • 整个函数应用被分组,并按默认量嵌套。

  • 函数与其参数之间用一个 line 分隔;这个换行通常会成为空格,因为 Repr NatRepr Int 实例不太可能产生很长的输出。

  • reprPrec 的递归调用传入 max_prec,因为它们位于函数参数位置,而函数应用具有最高优先级。

instance : Repr NatOrInt where reprPrec | .nat n => Repr.addAppParen <| .group <| .nestD <| "N.NatOrInt.nat" ++ .line ++ reprPrec n max_prec | .int i => Repr.addAppParen <| .group <| .nestD <| "N.NatOrInt.int" ++ .line ++ reprPrec i max_prec N.NatOrInt.nat 5 #eval IO.println (repr (NatOrInt.nat 5))
N.NatOrInt.nat 5
N.NatOrInt.int 5 #eval IO.println (repr (NatOrInt.int 5))
N.NatOrInt.int 5
N.NatOrInt.int (-5) #eval IO.println (repr (NatOrInt.int (-5)))
N.NatOrInt.int (-5)
some (N.NatOrInt.int (-5)) #eval IO.println (repr (some (NatOrInt.int (-5))))
some (N.NatOrInt.int (-5))
[N.NatOrInt.nat 0, N.NatOrInt.nat 1, N.NatOrInt.nat 2, N.NatOrInt.nat 3, N.NatOrInt.nat 4, N.NatOrInt.nat 5, N.NatOrInt.nat 6, N.NatOrInt.nat 7, N.NatOrInt.nat 8, N.NatOrInt.nat 9] #eval IO.println (repr <| (List.range 10).map (NatOrInt.nat))
[N.NatOrInt.nat 0,
 N.NatOrInt.nat 1,
 N.NatOrInt.nat 2,
 N.NatOrInt.nat 3,
 N.NatOrInt.nat 4,
 N.NatOrInt.nat 5,
 N.NatOrInt.nat 6,
 N.NatOrInt.nat 7,
 N.NatOrInt.nat 8,
 N.NatOrInt.nat 9]
[N.NatOrInt.nat 0, N.NatOrInt.nat 1, N.NatOrInt.nat 2, N.NatOrInt.nat 3, N.NatOrInt.nat 4, N.NatOrInt.nat 5, N.NatOrInt.nat 6, N.NatOrInt.nat 7, N.NatOrInt.nat 8, N.NatOrInt.nat 9] #eval IO.println <| Std.Format.pretty (width := 3) <| repr <| (List.range 10).map NatOrInt.nat
[N.NatOrInt.nat
   0,
 N.NatOrInt.nat
   1,
 N.NatOrInt.nat
   2,
 N.NatOrInt.nat
   3,
 N.NatOrInt.nat
   4,
 N.NatOrInt.nat
   5,
 N.NatOrInt.nat
   6,
 N.NatOrInt.nat
   7,
 N.NatOrInt.nat
   8,
 N.NatOrInt.nat
   9]
中缀语法

此示例演示如何使用优先级编码左结合的美化打印器。 类型 AddExpr 表示包含常量和加法的表达式:

inductive AddExpr where | nat : Nat AddExpr | add : AddExpr AddExpr AddExpr

OfNatAdd 实例为 AddExpr 提供了更方便的语法:

instance : OfNat AddExpr n where ofNat := .nat n instance : Add AddExpr where add := .add

Repr AddExpr 实例应只插入必要的圆括号。 Lean 的加法运算符是左结合的,优先级为 65,因此左侧的递归调用使用优先级 64;如果当前上下文的优先级大于或等于 65,则为运算符自身加圆括号:

protected def AddExpr.reprPrec : AddExpr Nat Std.Format | .nat n, p => Repr.reprPrec n p | .add e1 e2, p => let out : Std.Format := .nestD <| .group <| AddExpr.reprPrec e1 64 ++ " " ++ "+" ++ .line ++ AddExpr.reprPrec e2 65 if p 65 then out.paren else out instance : Repr AddExpr := AddExpr.reprPrec

无论输入如何加括号,此实例都只插入必要的圆括号:

2 + 3 + 4 #eval IO.println (repr (((2 + 3) + 4) : AddExpr))
2 + 3 + 4
2 + 3 + 4 #eval IO.println (repr ((2 + 3 + 4) : AddExpr))
2 + 3 + 4
2 + (3 + 4) #eval IO.println (repr ((2 + (3 + 4)) : AddExpr))
2 + (3 + 4)
[2 + (3 + 4), 2 + 3 + 4] #eval IO.println (repr ([2 + (3 + 4), (2 + 3) + 4] : List AddExpr))
[2 + (3 + 4), 2 + 3 + 4]

实现中使用的 groupnestDline 会在狭窄上下文中产生预期的换行与缩进:

[2 + (3 + 4), 2 + 3 + 4] #eval ([2 + (3 + 4), (2 + 3) + 4] : List AddExpr) |> repr |>.pretty (width := 0) |> IO.println
[2 +
   (3 +
      4),
 2 +
     3 +
   4]

3.7.2.2. 原子类型🔗

当列表元素足够小时,每个元素单独占一行既难以阅读又浪费空间。 为提高可读性,List 有两个 Repr 实例:一个对其内容使用 Std.Format.bracket,另一个使用 Std.Format.bracketFill。 后者定义在前者之后,因此会在可能时被选中;不过,它要求有空类型类 ReprAtom 的实例。

如果某类型的 Repr 实例从不生成空格或换行,那么该类型应有一个 ReprAtom 实例。 Lean 为 StringUInt8NatCharBool 等类型提供了 ReprAtom 实例。

🔗类型类
ReprAtom.{u} (α : Type u) : Type
ReprAtom.{u} (α : Type u) : Type

辅助类,用于标记应被 Repr 方法视为原子的类型。Repr (List α) 用它判断是否应 使用 bracketFill

ReprAtom.mk.{u}
原子类型与 Repr

归纳类型 ABC 的所有构造器都没有参数:

inductive ABC where | a | b | c deriving Repr

派生的 Repr ABC 实例用于显示列表:

def abc : List ABC := [.a, .b, .c] def abcs : List ABC := abc ++ abc ++ abc [ABC.a, ABC.b, ABC.c, ABC.a, ABC.b, ABC.c, ABC.a, ABC.b, ABC.c] #eval IO.println ((repr abcs).pretty (width := 14))

由于宽度很窄,因此会插入换行:

[ABC.a,
 ABC.b,
 ABC.c,
 ABC.a,
 ABC.b,
 ABC.c,
 ABC.a,
 ABC.b,
 ABC.c]

不过,将列表转换为 List Nat 会得到格式不同的结果。

def ABC.toNat : ABC Nat | .a => 0 | .b => 1 | .c => 2 [0, 1, 2, 0, 1, 2, 0, 1, 2]#eval IO.print ((repr (abcs.map ABC.toNat)).pretty (width := 14))

此时换行少得多:

[0, 1, 2, 0,
 1, 2, 0, 1,
 2]

这是因为存在 ReprAtom Nat 实例。 为 ABC 添加一个这样的实例会产生类似的行为:

instance : ReprAtom ABC := [ABC.a, ABC.b, ABC.c, ABC.a, ABC.b, ABC.c, ABC.a, ABC.b, ABC.c] #eval IO.println ((repr abcs).pretty (width := 14))
[ABC.a, ABC.b,
 ABC.c, ABC.a,
 ABC.b, ABC.c,
 ABC.a, ABC.b,
 ABC.c]