Compare commits

..

303 Commits

Author SHA1 Message Date
Leonardo de Moura
762eca7832 chore: fix tests 2025-06-04 09:32:50 -07:00
Leonardo de Moura
51f34c8425 feat: source at failure diag 2025-06-04 09:31:14 -07:00
Leonardo de Moura
960ed43dae feat: track case-split source 2025-06-04 09:25:22 -07:00
Kim Morrison
7c76dbf6be feat: typeclasses for grind to extensibly embed types into Int (#8543)
This PR adds typeclasses for `grind` to embed types into `Int`, for
cutsat. This allows, for example, treating `Fin n`, or Mathlib's `ℕ+` in
a uniform and extensible way.

There is a primary typeclass that carries the `toInt` function, and a
description of the interval the type embeds in. There are then
individual typeclasses describing how arithmetic/order operations
interact with the embedding.
2025-06-04 13:04:19 +00:00
Lean stage0 autoupdater
6b102c91e3 chore: update stage0 2025-06-04 13:21:17 +00:00
Joachim Breitner
b9243e19be feat: make equational theorems of non-exposed defs private (#8519)
This PR makes the equational theorems of non-exposed defs private. If
the author of a module chose not to expose the body of their function,
then they likely don't want that implementation to leak through
equational theorems. Helps with #8419.

There is some amount of incidential complexity due to how `private`
works in lean, by mangling the name: lots of code paths that need now do
the right thing™ about private and non-private names, including the
whole reserved name machinery.

So this includes a number of refactorings:

* The logic for calculating an equational theorem name (or similar) is
now done by a single function, `mkEqLikeNameFor`, rather than all over
the place.

* Since the name of the equational theorem now depends on the current
context (in particular whether it’s a proper module, or a non-module
file), the forward map from declaration to equational theorem doesn’t
quite work anymore. This map is deleted; the list of equational theorems
are now always found by looking for declaration of the expected names
(`alreadyGenerated). If users define such theorems themselves (and make
it past the “do not allow reserved names to be declared”) they get to
keep both pieces.

* Because this map was deleted, mathlib’s `eqns` command can no longer
easily warn if equational lemmas have already been generated too early
(adaption branch exists). But in general I think lean could provide a
more principled way of supporting custom unfold lemmas, and ideally the
whole equational theorem machinery is just using that.

* The ReservedNamePredicate is used by `resolveExact`, so we need to
make sure that it returns the right name, including privateness. It is
not ok to just reserve both the private and non-private name but then
later in the ReservedNameAction produce just one of the two.
 
* We create `foo.def_eq` eagerly for well-founded recursion. This is
needed because we need feed in the proof of the rewriting done by
`wf_preprocess`. But if `foo.def_eq` is private in a module, then a
non-module importing it will still expect a non-private `foo.def_eq` to
exist. To patch that, we install a `copyPrivateUnfoldTheorem :
GetUnfoldEqnFn` that declares a theorem aliasing the private one. Seems
to work.
2025-06-04 11:52:08 +00:00
Kim Morrison
d6478e15c7 chore: remove slow and unnecessary @[grind] annotations (#8630) 2025-06-04 10:57:25 +00:00
Leonardo de Moura
1629440cb8 feat: improve grind diagnostics for successful case (#8625)
This PR improves the diagnostic information produced by `grind` when it
succeeds. We now include the list of case-splits performed, and the
number of application per function symbol. Example:


![image](https://github.com/user-attachments/assets/109f3f80-85a1-4368-8958-fdf56707ea7d)
2025-06-04 09:34:48 +00:00
Kim Morrison
4500a7f02b fix: remove global NatCast (Fin n) instance (#8620)
This PR removes the `NatCast (Fin n)` global instance (both the direct
instance, and the indirect one via `Lean.Grind.Semiring`), as that
instance causes causes `x < n` (for `x : Fin k`, `n : Nat`) to be
elaborated as `x < ↑n` rather than `↑x < n`, which is undesirable. Note
however that in Mathlib this happens anyway!
2025-06-04 06:58:39 +00:00
Leonardo de Moura
c12159b519 refactor: move read-only data to Grind.Context (#8624) 2025-06-04 02:50:43 +00:00
Kim Morrison
1260059a59 feat: add grind use case example IndexMap (#8622)
This PR adds a test case / use case example for `grind`, setting up the
very basics of `IndexMap`, modelled on Rust's
[`indexmap`](https://docs.rs/indexmap/latest/indexmap/). It is not
intended as a complete implementation: just enough to exercise `grind`.

(Thanks to @arthurpaulino for suggesting this as a test case.)
2025-06-04 01:33:56 +00:00
Leonardo de Moura
8165ecc1db fix: bug in the equality resolution procedure in grind (#8621)
This PR fixes a bug in the equality-resolution procedure used by
`grind`.
The procedure now performs a topological sort so that every simplified
theorem declaration is emitted **before** any place where it is
referenced.
Previously, applying equality resolution to
```lean
h : ∀ x, p x a → ∀ y, p y b → x ≠ y
```
in the example
```lean
example
  (p : Nat → Nat → Prop)
  (a b c : Nat)
  (h  : ∀ x, p x a → ∀ y, p y b → x ≠ y)
  (h₁ : p c a)
  (h₂ : p c b) :
  False := by
  grind
```
caused `grind` to produce the incorrect term
```lean
p ?y a → ∀ y, p y b → False
```
The patch eliminates this error, and the following correct simplified
theorem is generated
```lean
∀ y, p y a → p y b → False
```
2025-06-04 00:34:47 +00:00
Leonardo de Moura
344b52f999 fix: term internalization issue in grind (#8619)
This PR fixes an internalization (aka preprocessing) issue in `grind`
when applying injectivity theorems.
2025-06-04 00:13:51 +00:00
Kyle Miller
5e952598dc fix: let private names be unresolved in the pretty printer, fix shadowing bug when pp.universes is true (#8617)
This PR fixes (1) an issue where private names are not unresolved when
they are pretty printed, (2) an issue where in `pp.universes` mode names
were allowed to shadow local names, (3) an issue where in `match`
patterns constants shadowing locals wouldn't use `_root_`, and (4) an
issue where tactics might have an incorrect "try this" when
`pp.fullNames` is set. Adds more delaboration tests for name
unresolution.

It also cleans up the `delabConst` delaborator so that it uses
`unresolveNameGlobalAvoidingLocals`, rather than doing any local context
analysis itself. The `inPattern` logic has been removed; it was a
heuristic added back in #575, but it now leads to incorrect results (and
in `match` patterns, local names shadow constants in name resolution).
2025-06-03 23:37:35 +00:00
Cameron Zwarich
b9aefb4a50 feat: LCNF constant folding for Nat.nextPowerOfTwo (#8618)
This PR implements LCNF constant folding for `Nat.nextPowerOfTwo`.
2025-06-03 21:13:58 +00:00
Cameron Zwarich
9afe5ccae3 feat: LCNF constant folding for Nat.pow (#8616)
This PR adds constant folding for `Nat.pow` to the new compiler,
following the same limits as the old compiler.
2025-06-03 19:10:38 +00:00
Marc Huisinga
cb0284f98e feat: signature help (#8511)
This PR implements signature help support. When typing a function
application, editors with support for signature help will now display a
popup that designates the current (remaining) function type. This
removes the need to remember the function signature while typing the
function application, or having to constantly cycle between hovering
over the function identifier and typing the application. In VS Code, the
signature help can be triggered manually using `Ctrl+Shift+Space`.


![Demo](https://github.com/user-attachments/assets/d1f6ed79-bb16-4593-8d28-68b1cce5d5dc)

### Other changes

- In order to support signature help for the partial syntax `f a <|` or
`f a $`, these notations now elaborate as `f a`, not `f a .missing`.
- The logic in `delabConstWithSignature` that delaborates parameters is
factored out into a function `delabForallParamsWithSignature` so that it
can be used for arbitrary `forall`s, not just constants.
- The `InfoTree` formatter is adjusted to produce output where it is
easier to identify the kind of `Info` in the `InfoTree`.
- A bug in `InfoTree.smallestInfo?` is fixed so that it doesn't panic
anymore when its predicate `p` does not ensure that both `pos?` and
`tailPos?` of the `Info` are present.
2025-06-03 17:26:33 +00:00
Cameron Zwarich
35e83066e6 feat: implement LCNF constant folding for toNat (#8614)
This PR implements constant folding for `toNat` in the new compiler,
which improves parity with the old compiler.
2025-06-03 17:12:15 +00:00
Sebastian Ullrich
ba847d41f1 chore: revise environment constant addition details (#8610)
* Move constant registration with elab env from `Lean.addDecl` to
`Lean.Environment.addDeclCore` for compatibility
* Make module system behavior independent of `Elab.async` value
2025-06-03 15:16:45 +00:00
Cameron Zwarich
f5e72d0962 feat: make guard_msgs.diff=true the default (#8596)
This PR makes `guard_msgs.diff=true` the default. The main usage of
`#guard_msgs` is for writing tests, and this makes staring at altered
test outputs considerably less tiring.
2025-06-03 15:13:15 +00:00
Sebastian Ullrich
536c87d73c chore: make test more robust 2025-06-03 16:11:09 +02:00
Sebastian Ullrich
c95e058e3c chore: fix tests after rebootstrap 2025-06-03 16:11:09 +02:00
Sebastian Ullrich
4746e38414 chore: update stage0 2025-06-03 16:11:09 +02:00
Sebastian Ullrich
f718f26200 feat: create private aux decls in private contexts 2025-06-03 15:53:05 +02:00
Marc Huisinga
184dbae130 feat: reusable rpc refs (#8105)
This PR adds support for server-sided `RpcRef` reuse and fixes a bug
where trace nodes in the InfoView would close while the file was still
being processed.

The core of the trace node issue is that the server always serves new
RPC references in every single response to the client, which means that
the client is forced to reset its UI state.

In a previous attempt at fixing this (#8056), the server would memorize
the RPC-encoded JSON of interactive diagnostics (which includes RPC
references) and serve it for as long as it could reuse the snapshot
containing the diagnostics, so that RPC references are reused. The
problem with this was that the client then had multiple finalizers
registered for the same RPC reference (one for every reused RPC
reference that was served), and once the first reference was
garbage-collected, all other reused references would point into the
void.

This PR takes a different approach to resolve the issue: The meaning of
`$/lean/rpc/release` is relaxed from "Free the object pointed to by this
RPC reference" to "Decrement the RPC reference count of the object
pointed to by this RPC reference", and the server now maintains a
reference count to track how often a given `RpcRef` was served. Only
when every single served instance of the `RpcRef` has been released, the
object is freed. Additionally, the reuse mechanism is generalized from
being only supported for interactive diagnostics, to being supported for
any object using `WithRpcRef`. In order to make use of reusable RPC
references, downstream users still need to memorize the `WithRpcRef`
instances accordingly.

Closes #8053.

### Breaking changes

Since `WithRpcRef` is now capable of tracking its identity to decide
which `WithRpcRef` usage constitutes a reuse, the constructor of
`WithRpcRef` has been made `private` to discourage downstream users from
creating `WithRpcRef` instances with manually-set `id`s. Instead,
`WithRpcRef.mk` (which lives in `BaseIO`) is now the preferred way to
create `WithRpcRef` instances.
2025-06-03 12:35:12 +00:00
Kim Morrison
bc47aa180b feat: use grind to shorten some proofs in the LRAT checker (#8609)
This PR uses `grind` to shorten some proofs in the LRAT checker. The
intention is not particularly to improve the quality or maintainability
of these proofs (although hopefully this is a side effect), but just to
give `grind` a work out.

There are a number of remaining notes, either about places where `grind`
fails with an internal error (for which #8608 is hopefully
representative, and we can fix after that), or `omega` works but `grind`
doesn't (to be investigated later).

Only in some of the files have I thoroughly used grind. In many files
I've just replaced leaves or branches of proofs with `grind` where it
worked easily, without setting up the internal annotations in the LRAT
library required to optimize the use of `grind`. It's diminishing
returns to do this in a proof library that is not high priority, so I've
simply drawn a line.
2025-06-03 08:38:57 +00:00
Kim Morrison
f7b6e155d4 chore: add failing grind test (#8608) 2025-06-03 07:45:38 +00:00
Kim Morrison
f4e86e310c chore: add failing grind test (unknown metavariable) (#8607) 2025-06-03 07:00:56 +00:00
Kim Morrison
5f0bdfcada chore: initial @[grind] annotations for Array/Vector.range (#8606) 2025-06-03 06:44:01 +00:00
Kim Morrison
0f4459b42c chore: add @[grind] annotations to Fin.getElem_fin (#8605) 2025-06-03 06:37:35 +00:00
Paul Reichert
55b89aaf38 feat: introduce drop iterator combinator (#8420)
This PR provides the iterator combinator `drop` that transforms any
iterator into one that drops the first `n` elements.

Additionally, the PR removes the specialized `IteratorLoop` instance on
`Take`. It currently does not have a `LawfulIteratorLoop` instance,
which needs to exist for the loop consumer lemmas to work. Having the
specialized instance is low priority.
2025-06-03 06:37:09 +00:00
Kim Morrison
9fc8713946 chore: grind annotations for getElem?_pos and variants (#8590)
This PR adds `@[grind]` to `getElem?_pos` and variants.

I'd initially thought these would result in too much case splitting, but
it seems to be only minor, and in use cases the payoff is good.
2025-06-03 06:17:05 +00:00
Cameron Zwarich
106411420b fix: support compiler.extract_closed option in the new compiler (#8604)
This PR adds support for the `compiler.extract_closed` option to the new
compiler, since this is used by the definition of `unsafeBaseIO`. We'll
revisit this once we switch to the new compiler and rethink its
relationship with IO.
2025-06-03 05:58:32 +00:00
Kim Morrison
921be93535 chore: add @[grind] to List/Array/Vector.mem_map (#8603) 2025-06-03 05:07:11 +00:00
Cameron Zwarich
63d123f4be fix: support Eq.recOn in the new compiler (#8602)
This PR adds support to the new compiler for `Eq.recOn` (which is
supported by the old compiler but missing a test).
2025-06-03 04:45:20 +00:00
Kim Morrison
7adea80123 chore: missing [@grind] annotations for List/Array.modify` (#8601) 2025-06-03 04:13:01 +00:00
Kim Morrison
310a123901 chore: grind annotations for List/Array/Vector.any/all (#8600) 2025-06-03 03:52:54 +00:00
Kim Morrison
6c17ad8954 chore: add failing grind test (#8599)
`@[grind local]` currently doesn't work as expected on theorems in
namespaces.
2025-06-03 01:49:36 +00:00
Jakob von Raumer
3452a8a2e5 feat: improve BitVec.extractLsb' lemma on appended vectors (#8585)
This PR makes the lemma `BitVec.extractLsb'_append_eq_ite` more usable
by using the "simple case" more often, and uses this simplification to
make `BitVec.extractLsb'_append_eq_of_add_lt` stronger, renaming it to
`BitVec.extractLsb'_append_eq_of_add_le`.
2025-06-02 20:11:59 +00:00
Luisa Cicolini
fcc97fe49f feat: add toInt_smod and auxilliary theorems (#8253)
This PR adds `toInt_smod` and auxilliary lemmas necessary for its proof
(`msb_intMin_umod_neg_of_msb_true`,
`msb_neg_umod_neg_of_msb_true_of_msb_true`, `toInt_dvd_toInt_iff`,
`toInt_dvd_toInt_iff_of_msb_true_msb_false`,
`toInt_dvd_toInt_iff_of_msb_false_msb_true`,
`neg_toInt_neg_umod_eq_of_msb_true_msb_true`, `toNat_pos_of_ne_zero`,
`toInt_umod_neg_add`, `toInt_sub_neg_umod` and
`BitVec.[lt_of_msb_false_of_msb_true, msb_umod_of_msb_false_of_ne_zero`,
`neg_toInt_neg]`)

co-authored with @tobiasgrosser

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
Co-authored-by: Tobias Grosser <github@grosser.es>
Co-authored-by: kuhnsa <151550049+salinhkuhn@users.noreply.github.com>
Co-authored-by: Siddharth <siddu.druid@gmail.com>
2025-06-02 20:09:00 +00:00
Cameron Zwarich
af365238a1 fix: wrap the new compiler in withoutExporting (#8595)
This PR wraps the invocation of the new compiler in `withoutExporting`.
This is not necessary for the old compiler because it uses more direct
access to the kernel environment.
2025-06-02 16:57:10 +00:00
Cameron Zwarich
3ccc9ca7ac fix: remove incorrect strictOr/strictAnd optimizations (#8594)
This PR removes incorrect optimizations for strictOr/strictAnd from the
old compiler, along with deleting an incorrect test. In order to do
these optimizations correctly, nontermination analysis is required.
Arguably, the correct way to express these optimizations is by exposing
the implementation of strictOr/strictAnd to a nontermination-aware phase
of the compiler, and then having them follow from more general
transformations.
2025-06-02 16:14:56 +00:00
Cameron Zwarich
b73a67a635 chore: use HashMap in ToMonoM.State.noncomputableVars (#8592) 2025-06-02 15:08:51 +00:00
Kim Morrison
9a3228ef88 chore: adjustments to grind lemmas for List.Pairwise (#8588) 2025-06-02 13:19:21 +00:00
Kim Morrison
b0963938d4 chore: initial grind annotations for List.erase (#8589) 2025-06-02 12:56:09 +00:00
Kim Morrison
47b353f155 chore: adjust HashMap grind lemmas (#8587)
This PR adjusts the grind annotation on
`Std.HashMap.map_fst_toList_eq_keys` and variants, so `grind` can reason
bidirectionally between `m.keys` and `m.toList`.
2025-06-02 12:50:21 +00:00
Sebastian Ullrich
add3e1ae12 fix: IO.FS.removeDirAll should not follow symlinks (#8573)
This PR avoids the likely unexpected behavior of `removeDirAll` to
delete through symlinks and adds the new function
`IO.FS.symlinkMetadata`.

---------

Co-authored-by: Rob23oba <152706811+Rob23oba@users.noreply.github.com>
2025-06-02 08:44:17 +00:00
Sebastian Ullrich
569e46033b feat: do not export private declarations (#8337)
This PR adjusts the experimental module system to not export any private
declarations from modules.

Fixes #5002
2025-06-02 08:01:08 +00:00
Sebastian Ullrich
5023b40576 chore: CI: fix cache (#8579)
* include .olean variants
* include SHA in key on push as well
2025-06-02 08:00:42 +00:00
Sebastien Gouezel
3516143aed doc: use notMem instead of not_mem in recommended_spelling (#8496)
This PR changes the recommended spelling from `not_mem` to `notMem`, to
reflect the decision that has been made in mathlib.

It does *not* change the name of any core lemma.

See Zulip discussion at [#mathlib4 > Naming: nmem vs not_mem @
💬](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Naming.3A.20nmem.20vs.20not_mem/near/520315224)
2025-06-02 06:46:36 +00:00
Cameron Zwarich
0339cd2836 fix: don't drop state during update in Param.toMono (#8582)
This PR fixes an accidental dropping of state in Param.toMono. When this
code was originally written, there was no other state besides
`typeParams`.
2025-06-02 05:28:27 +00:00
Cameron Zwarich
bae336da87 chore: make ToMonoM.State.typeParams an FVarIdHashSet rather than an FVarIdSet (#8581) 2025-06-02 05:07:57 +00:00
dependabot[bot]
e7b24479ed chore: CI: bump dawidd6/action-download-artifact from 9 to 10 (#8578)
Bumps
[dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact)
from 9 to 10.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dawidd6/action-download-artifact/releases">dawidd6/action-download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v10</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix the download-commit test to actually look for a commit by <a
href="https://github.com/mstorsjo"><code>@​mstorsjo</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/330">dawidd6/action-download-artifact#330</a></li>
<li>Add the option &quot;ref&quot;, specifying either a commit or a
branch by <a
href="https://github.com/mstorsjo"><code>@​mstorsjo</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/329">dawidd6/action-download-artifact#329</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/mstorsjo"><code>@​mstorsjo</code></a>
made their first contribution in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/330">dawidd6/action-download-artifact#330</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v9...v10">https://github.com/dawidd6/action-download-artifact/compare/v9...v10</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="4c1e823582"><code>4c1e823</code></a>
Add the option &quot;ref&quot;, specifying either a commit or a branch
(<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/329">#329</a>)</li>
<li><a
href="a708c3c648"><code>a708c3c</code></a>
Fix the download-commit test to actually look for a commit (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/330">#330</a>)</li>
<li><a
href="19f6be5f04"><code>19f6be5</code></a>
Update README.md</li>
<li>See full diff in <a
href="https://github.com/dawidd6/action-download-artifact/compare/v9...v10">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dawidd6/action-download-artifact&package-manager=github_actions&previous-version=9&new-version=10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-01 21:37:06 +00:00
Leonardo de Moura
193f59aefe feat: grind +ring by default (#8576)
This PR sets `ring := true` by default in `grind`. It also fixes a bug
in the reification procedure, and improves the term internalization in
the ring and cutsat modules.
2025-06-01 17:46:49 +00:00
Cameron Zwarich
c681cccf1d fix: make simpAppApp? actually bail out on trivial aliases as intended (#8575)
This PR makes LCNF's simpAppApp? bail out on trivial aliases as
intended. It seems that there was a typo in the original logic, and this
PR also extends it to include aliases of global constants rather than
just local vars.
2025-06-01 16:03:17 +00:00
user202729
c6cad5fcff doc: fix a typo in ULift's documentation (#8571)
Just a typo. From my understanding (and the specification otherwise) the
resulting level is the maximum of `r` and `s` instead of the minimum.

No issue opened yet (thus the draft).
2025-06-01 06:25:52 +00:00
Leonardo de Moura
bb6d1e000b feat: generalized Option theorems for grind (#8572)
This PR adds some generalized `Option` theorems for `grind` . The avoid
`casts` operations during E-matching.
2025-06-01 06:25:37 +00:00
Lean stage0 autoupdater
abcfa708f2 chore: update stage0 2025-06-01 05:51:10 +00:00
Mac Malone
ed705306ae fix: invalid field notation error for mvar (#8259)
This PR clarifies the invalid field notation error when projected value
type is a metavariable.

Co-authored-by @sgraf812.

---------

Co-authored-by: Sebastian Graf <sg@lean-fro.org>
2025-06-01 03:02:04 +00:00
Mac Malone
e618a0a4f5 fix: invalid dotted identifier notation error for sort (#8260)
This PR clarifies the invalid dotted identifier notation error when the
type is a sort.

Co-authored-by @sgraf812.

---------

Co-authored-by: Joseph Rotella <7482866+jrr6@users.noreply.github.com>
2025-06-01 03:00:46 +00:00
Leonardo de Moura
db353ab964 fix: ematch generalized patterns (#8570)
This PR fixes some issues in the E-matching generalized pattern support
after the update stage0.
2025-06-01 02:38:29 +00:00
Leonardo de Moura
157ca5a4f3 feat: ematch generalized patterns (#8569)
This PR adds support for generalized E-match patterns to arbitrary
theorems.
2025-05-31 19:08:33 -07:00
jrr6
43aec5b254 fix: improve error-message hint rendering and API (#8486)
This PR improves the rendering of hints in error messages by
consistently indenting diffs and splitting large diffs less granularly;
it also improves the ergonomics of `Lean.MessageData.hint`. Note that
the changes to the signature of `Lean.MessageData.hint` are breaking.

This PR depends on #8457.
2025-06-01 01:22:09 +00:00
Leonardo de Moura
f6c83f3dce chore: adjust test (#8567)
It is working now
2025-06-01 00:21:23 +00:00
Kyle Miller
502380e1f0 fix: record fvar alias info for generalized variables in induction/cases (#8002)
This PR fixes an issue where "go to definition" for variables
generalized by the `induction` and `cases` tactic did not work. Closes
#2873.
2025-05-31 22:27:44 +00:00
Cameron Zwarich
936eb3d62e fix: don't convert Nat multiplication by 2^n to a left shift (#8566)
This PR changes the LCNF constant folding pass to not convert Nat
multiplication to a left shift by a power of 2. The fast path test for
this is sufficiently complex that it's simpler to just use the fast path
for multiplication.
2025-05-31 21:36:55 +00:00
Cameron Zwarich
0c43efc2c9 fix: only treat type/instance params as ground vars in specialization (#8565)
This PR makes the LCNF specialization pass only treat type/instance
params as ground vars. The current policy was too liberal and would
result on computations being floated into specialized loops.
2025-05-31 21:18:24 +00:00
Leonardo de Moura
2c8ee4f29c fix: simplify interface between grind core and cutsat (#8564)
This PR simplifies the interface between the `grind` core and the cutsat
procedure. Before this PR, core would try to minimize the number of
numeric literals that have to be internalized in cutsat. This
optimization was buggy (see `grind_cutsat_zero.lean` test), and produced
counterintuitive counterexamples.
2025-05-31 16:28:31 +00:00
Leonardo de Moura
0988db9ab2 refactor: simplify inferface between core and offset module (#8562)
`processNewEqLit` optimization is not worth the extra complexity.
2025-05-31 15:16:29 +00:00
Cameron Zwarich
adc7b1ed87 fix: increase maxHeartbeats in isDefEqProjIssue test for the new compiler (#8561)
This PR increases maxHeartbeats in the isDefEqProjIssue test, because
when running under the new compiler the `run_meta` call includes the
allocations of the compiler itself. With the old compiler, many of the
corresponding allocations were internal to C++ code and would not
increase the heartbeat count.
2025-05-31 04:56:29 +00:00
Leonardo de Moura
837193b5ec fix: block potential adversarial exploit of non-aborting assert! (#8560)
This PR is similar to #8559 but for `Expr.mkData`. This vulnerability
has not been exploited yet, but adversarial users may find a way.
2025-05-31 03:14:01 +00:00
Leonardo de Moura
6940d2c4ff fix: block adversarial exploit of non-aborting assert! (#8559)
This PR fixes an adversarial soundness attack described in #8554. The
attack exploits the fact that `assert!` no longer aborts execution, and
that users can redirect error messages.
Another PR will implement the same fix for `Expr.Data`.
2025-05-31 00:08:30 +00:00
Paul Reichert
ed4252f8c9 feat: array iterators, repeat/unfold, ForM for iterators (#8552)
This PR provides array iterators (`Array.iter(M)`,
`Array.iterFromIdx(M)`), infinite iterators produced by a step function
(`Iter.repeat`), and a `ForM` instance for finite iterators that is
implemented in terms of `ForIn`.
2025-05-30 18:17:53 +00:00
Leonardo de Moura
8883ca0965 chore: move test (#8550)
It is working now.
2025-05-30 17:13:38 +00:00
Leonardo de Moura
999fcd2d95 fix: hash function for congruence closure in grind (#8549)
This PR fixes the hash function used to implement congruence closure in
`grind`. The hash of an `Expr` must not depend on whether the expression
has been internalized or not.
2025-05-30 17:07:26 +00:00
Paul Reichert
a8ab3f230c feat: introduce iterator combinators takeWhile and dropWhile (#8493)
This PR provides the iterator combinators `takeWhile` (forwarding all
emitted values of another iterator until a predicate becomes false)
`dropWhile` (dropping values until some predicate on these values
becomes false, then forwarding all the others).
2025-05-30 16:35:40 +00:00
Paul Reichert
4f77e05225 feat: introduce zip iterator combinator (#8484)
This PR provides the iterator combinator `zip` in a pure and monadic
version.
2025-05-30 15:20:28 +00:00
Paul Reichert
90462e2551 feat: introduce iterator combinators filterMap, filter and map (#8451)
This PR provides the iterator combinator `filterMap` in a pure and
monadic version and specializations `map` and `filter`. This new
combinator allows to apply a function to the emitted values of a stream
while filtering out certain elements.

`map` should have an optimized `IteratorCollect` implementation but it
turns out that this is not possible without a major refactor of
`IteratorCollect`: `toArrayMapped` requires a proof that the iterator is
finite. If `it.mapM f` is `Finite` but `it` is not, then such a proof
does not exist. `IteratorCollect` needs to take a proof that the loop
will terminate for the given monadic function `f` instead. This will not
be done in this PR.
2025-05-30 13:43:41 +00:00
Paul Reichert
a12f89aefa feat: introduce take iterator combinator (#8418)
This PR provides the `take` iterator combinator that transforms any
iterator into an iterator that stops after a given number of steps. The
change contains the implementation and lemmas.

`take` has a special implementation of `IteratorLoop` that relies on a
potentially more efficient `forIn` implementation of the inner iterator.

The mysterious `@[specialize]` on a test has been removed because it is
not necessary anymore according to a manual inspection of the IR. Either
I erroneously concluded from experiments that it was necessary of
something has changed in the meantime that makes it unnecessary.
2025-05-30 10:34:12 +00:00
Paul Reichert
2d5e8ca311 feat: upstream LawfulMonadLift(T) from Batteries (#8435)
This PR upstreams the `LawfulMonadLift(T)` classes, lemmas and instances
from Batteries into Core because the iterator library needs them in
order to prove lemmas about the `mapM` operator, which relies on
`MonadLiftT`.
2025-05-30 09:14:01 +00:00
Paul Reichert
d60cb88e62 feat: ForIn, fold(M), drain lemmas for iterators (#8405)
This PR provides lemmas about the loop constructs `ForIn`, `fold`,
`foldM` and `drain` and their relation to each other in the context of
iterators.
2025-05-30 09:10:31 +00:00
Leonardo de Moura
d2e01bbd09 feat: overapplied ite and dite applications in grind (#8544)
This PR implements support for over-applied `ite` and `dite`
applications in the `grind` tactic. It adds support for propagation and
case-split.
2025-05-30 06:34:04 +00:00
Leonardo de Moura
069fb4351c fix: inappropriate whnfD uses in grind (#8542)
This PR fixes two inappropriate uses of `whnfD` in `grind`. They were
potential performance foot guns, and were producing unexpected errors
since `whnfD` is not consistently used (and it should not be) in all
modules.
2025-05-30 04:35:29 +00:00
Leonardo de Moura
f54a65f72f feat: nested proof propagation in grind (#8541)
This PR ensures that for any nested proof `h : p` in a goal, we
propagate that `p` is true in the `grind` tactic.
2025-05-30 03:25:14 +00:00
Mac Malone
3817dd57bd fix: lake: precompile imports of non-workspace files by library (#8529)
This PR changes `lake lean` and `lake setup-file` to precompile the
imports of non-workspace files using the the import's whole library.
This ensures that additional link objects are linked and available
during elaboration.

Closes #8448.
2025-05-30 02:28:28 +00:00
Mac Malone
e68c6a38fb feat: lake: relative paths for Lean build messages (#8539)
This PR changes Lake to use relative path for the Lean messages produced
by a module build. This makes the message portable across different
machines, which is useful for Mathlib's cache.
2025-05-30 02:02:35 +00:00
Cameron Zwarich
b7ec369863 fix: allow ground variables to depend on fun decls in LCNF specialize pass (#8540)
This PR changes the LCNF specialize pass to allow ground variables to
depend on local fun decls (with no non-ground free variables). This
enables specialization of Monad instances that depend on local lambdas.
2025-05-30 00:45:00 +00:00
Mac Malone
3fdaf24b49 fix: lake: ensure valid use of (sync := true) (#8531)
This PR fixes some places in Lake where `(sync := true)` was incorrectly
used on code that could block, and more generally improves `(sync :;=
true)` usage.
2025-05-30 00:19:25 +00:00
Kim Morrison
77e16407e4 chore: add test case where grind causes a PANIC (#8538)
Minimized from #8518, thanks @wkrozowski!
2025-05-30 00:12:37 +00:00
Kim Morrison
efd8d149ea chore: add missing lemma for List.range 1 (#8537) 2025-05-30 00:09:51 +00:00
Leonardo de Moura
4316629119 fix: BEq support in grind (#8536)
This PR fixes the support for `LawfulBEq` and `BEq` in `grind`.
2025-05-29 23:47:40 +00:00
jrr6
020da5bffb fix: behavior of hard line breaks in Format strings (#8457)
This PR fixes an issue when including a hard line break in a `Format`
that caused subsequent (ordinary) line breaks to be erroneously
flattened to spaces.

This issue is especially important for displaying notes and hints in
error messages, as these components could appear garbled due to improper
line-break rendering.
2025-05-29 22:10:27 +00:00
Sebastian Ullrich
bc8189b61d perf: avoid Environment.find? block in addDecl (#8533) 2025-05-29 21:32:37 +00:00
Cameron Zwarich
e30303e33c fix: extract more Nats in extractClosed (#8535)
This PR extracts more Nats (and their downstream users) in extractClosed
by fixing a silly oversight in the logic.
2025-05-29 21:11:21 +00:00
Sebastian Ullrich
1879a2bafc fix: SnapshotTree.waitAll (#8532) 2025-05-29 20:12:23 +00:00
Mac Malone
3b72c7d193 fix: lake: better library plugin heuristic (#8528)
This PR fixes the heuristic Lake uses to determine whether a `lean_lib`
can be loaded via `lean --plugin` rather than `lean --load-dynlib`.
Previously, a mismatch between the single root's name and the library's
name would not be caught and cause loading to fail.
2025-05-29 17:48:05 +00:00
Kim Morrison
22d4c1d803 chore: failing grind tests (subset of #8518) (#8526)
This is a subset of tests from #8518 that are fully minimized. I'll
merge this first.

---------

Co-authored-by: Wojciech Rozowski <wojciech@lean-fro.org>
2025-05-29 11:48:19 +00:00
Kim Morrison
0fe23b7fd6 feat: initial @[grind] annotations for List.count (#8527)
This PR adds `grind` annotations for theorems about `List.countP` and
`List.count`.
2025-05-29 11:46:44 +00:00
Kim Morrison
72141b05fd chore: add failing grind test (#8524) 2025-05-29 05:59:58 +00:00
Leonardo de Moura
1fd7206f00 feat: match-expressions with congruence equation theorems (#8506)
This PR implements `match`-expressions in `grind` using `match`
congruence equations. The goal is to minimize the number of `cast`
operations that need to be inserted, and avoid `cast` over functions.
The new approach support `match`-expressions of the form `match h : ...
with ...`.
2025-05-29 02:23:26 +00:00
Cameron Zwarich
a6e76b424c fix: move the new compiler's noncomputable check into toMono (#8523)
This PR moves the new compiler's noncomputable check into toMono,
matching the recent change in the old compiler. This is mildly more
complicated because we can't throw an error at the mere use of a
constant, we need to check for a later relevant use. This is still a bit
more conservative than it could theoretically be around join points and
local functions, but it's hard to imagine that mattering in practice
(and we can easily enable it if it does).
2025-05-29 00:40:25 +00:00
Kyle Miller
4dd8648a25 feat: different syntax for new clear_value tactic (#8516)
This PR is a followup to #8449 to refine the syntax of `clear_value`.
The syntax for adding equality hypotheses before clearing values is now
`clear_value (h : x = _)`. Any expression definitionally equal to `x`
can be used in place of the underscore.

This syntax was developed in a [Zulip
discussion](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60clear_value.60.20syntax.20request.20for.20comments/near/520704290).
2025-05-28 22:33:35 +00:00
Cameron Zwarich
5814c1e757 fix: recursively process jmp args in LCNF.toMono (#8521)
This PR makes LCNF.toMono recursively process jmp args.
2025-05-28 20:56:03 +00:00
Kyle Miller
c3a010a938 feat: use dot notation for class parent projections (#8504)
This PR modifies the pretty printer so that dot notation is used for
class parent projections. Previously, dot notation was never used for
classes.

We still need to modify dot notation to take the method resolution order
into account when collapsing parent projections.
2025-05-28 20:34:40 +00:00
Kim Morrison
bd14e7079b fix: make Array.size not reducible (#8513)
This PR removes the `@[reducible]` annotation on `Array.size`. This is
probably best gone anyway in order to keep separation between the `List`
and `Array` APIs, but it also helps avoid uselessly instantiating
`Array` theorems when `grind` is working on `List` problems.
2025-05-28 12:37:24 +00:00
Sebastian Ullrich
f214708636 chore: update stage0 2025-05-28 14:27:31 +02:00
Sebastian Ullrich
5d7e09ddad feat: [no_expose] attribute 2025-05-28 14:26:22 +02:00
Kim Morrison
c6194e05b8 chore: remove prime from Fin.ofNat' (#8515)
This PR removes the prime from `Fin.ofNat'`: the old `Fin.ofNat` has
completed its 6 month deprecation cycle and is being removed.
2025-05-28 11:51:00 +00:00
Kim Morrison
1087ec9225 chore: remove >6 month old deprecations (#8514) 2025-05-28 11:28:03 +00:00
Kyle Miller
c5bea23a54 feat: value_of% elaborator (#8512)
This PR adds a `value_of% ident` term that elaborates to the value of
the local or global constant `ident`. This is useful for creating
definition hypotheses:
```lean
let x := ... complicated expression ...
have hx : x = value_of% x := rfl
```
2025-05-28 11:12:11 +00:00
Kim Morrison
ede085ae74 chore: add failing grind test (#8509) 2025-05-28 08:56:23 +00:00
Sebastian Ullrich
067fa83b1a chore: update stage0 2025-05-28 10:18:04 +02:00
Sebastian Ullrich
af1d8dd070 feat: := private instance syntax 2025-05-28 10:18:04 +02:00
Joachim Breitner
803dc3e687 refactor: Init: expose lots of functions (#8501)
This PR adds the `@[expose]` attribute to many functions (and changes
some theorems to be by `:= (rfl)`) in preparation for the `@[defeq]`
attribute change in #8419.
2025-05-28 07:37:54 +00:00
Kyle Miller
921ce7682e feat: use omission dots for hidden let values in Infoview (#8041)
This PR changes the behavior of `pp.showLetValues` to use a hoverable
`⋯` to hide let values. This is now false by default, and there is a new
option `pp.showLetValues.threshold` for allowing small expressions to be
shown anyway. For tactic metavariables, there is an additional option
`pp.showLetValues.tactic.threshold`, which by default is set to the
maximal value, since in tactic states local values are usually
significant.
2025-05-27 23:09:11 +00:00
Leonardo de Moura
5187cb37a9 chore: notation for HEq (#8503) 2025-05-27 19:22:57 +00:00
Cameron Zwarich
632d078a70 fix: use kernel environment to find definitions in the new compiler (#8502)
This PR changes the new compiler to use the kernel environment to find
definitions, which causes compilation to be skipped when the decl had a
kernel error (e.g. due to an unresolved metavariable). This matches the
behavior of the old compiler.

This will need to be revisited in the future when we want to make
compilation more asynchronous.
2025-05-27 16:56:00 +00:00
Luisa Cicolini
5fda4c1023 feat: BitVec.[toNat|toInt] non-overflow simp lemmas (#8492)
This PR adds `simp` lemmas for `toInt_*` and `toNat_*` with arithmetic
operation given the hypothesis of no-overflow
(`toNat_add_of_not_uaddOverflow`, `toInt_add_of_not_saddOverflow`,
`toNat_sub_of_not_usubOverflow`, `toInt_sub_of_not_ssubOverflow`,
`toInt_neg_of_not_negOverflow`, `toNat_mul_of_not_umulOverflow`,
`toInt_mul_of_not_smulOverflow`). In particular, these are `simp` since
(1) the `rhs` is strictly simpler than the `lhs` and (2) this version is
also simpler than the standard operation when the hypothesis is
available.
 
co-authored by @tobiasgrosser

---------

Co-authored-by: Henrik Böving <hargonix@gmail.com>
2025-05-27 15:13:43 +00:00
Kim Morrison
a4fb2eef47 feat: make Array.ofFn.go use fuel (#8499)
This PR changes the definition of `Array.ofFn.go` to use recursion on
`Nat` (rather than well-founded recursion). This resolves a problem
reported on [zulip]([#lean4 > Memory issues with &#96;Vector.ofFn&#96;.
@
💬](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Memory.20issues.20with.20.60Vector.2EofFn.60.2E/near/520622564)).
2025-05-27 13:44:28 +00:00
Kim Morrison
87152a3fae feat: grind annotations for List.Sublist/IsInfix/IsPrefix/IsSuffix (#8497)
This PR adds preliminary grind annotations for
`List.Sublist`/`IsInfix`/`IsPrefix`/`IsSuffix`, along with test cases.
2025-05-27 12:56:43 +00:00
Tobias Grosser
ed6c78048e chore: skip OS X aarch64 CI only in merge groups (#8334)
This PR enables the build of all artifacts for custom releases, e.g.,
releases outside the main lean4 repository.

This resolves https://github.com/leanprover/lean4/issues/8333.
2025-05-27 11:51:59 +00:00
Kim Morrison
3ab60c59fe chore: missing @[grind] annotations for Array (#8495) 2025-05-27 09:56:10 +00:00
Kim Morrison
eaa1bc14ed chore: more simp lemmas for LawfulGetElem (#8470)
This PR adds `@[simp]` to `getElem_pos/neg` (similarly for `getElem!`).
These are often already simp lemmas for concrete types.
2025-05-27 09:41:22 +00:00
Rob23oba
a912652b7d fix: simp_all? and simp_all?! (#8491)
This PR fixes the behavior of `simp_all?` and `simp_all?!`, aligning
them with `simp_all` and `simp_all!` respectively.

Closes #8490
2025-05-27 07:07:12 +00:00
Kyle Miller
3af9ab64ed feat: subst tactic can substitute let values (#8450)
This PR adds a feature to the `subst` tactic so that when `x : X := v`
is a local definition, `subst x` substitutes `v` for `x` in the goal and
removes `x`. Previously the tactic would throw an error.
2025-05-27 06:06:35 +00:00
Kyle Miller
a6dd6a4656 feat: clear_value tactic (#8449)
This PR upstreams and extends the Mathlib `clear_value` tactic. Given a
local definition `x : T := v`, the tactic `clear_value x` replaces it
with a hypothesis `x : T`, or throws an error if the goal does not
depend on the value `v`. The syntax `clear_value x with h` creates a
hypothesis `h : x = v` before clearing the value of `x`. Furthermore,
`clear_value *` clears all values that can be cleared, or throws an
error if none can be cleared.
2025-05-27 01:52:08 +00:00
Kim Morrison
1e752b0a01 chore: cleanup simp lemmas, following the simpNF linter (#8481) 2025-05-26 04:13:17 +00:00
Leonardo de Moura
11f7d6da39 feat: reuse simp cache in grind (#8483)
This PR ensures `grind` reuses the `simp` cache between different calls.
Recall that `grind` uses `simp` to normalize terms during
internalization.
2025-05-26 04:10:58 +00:00
Kim Morrison
e2fc9ba92e feat: grind annotations for List.Pairwise/Nodup (#8482)
This PR adds preliminary `@[grind]` annotations for `List.Pairwise` and
`List.Nodup`.
2025-05-26 03:13:18 +00:00
Kim Morrison
c1866a7b7e chore: fix awaiting-mathlib.yml (#8480)
This PR hopefully fixes a problem from #8471, which even the most
cursory testing (by me!) should have detected.
2025-05-26 02:13:00 +00:00
Leonardo de Moura
03e905d994 feat: hash consing with alpha equivalence in grind (#8479)
This PR implements hash-consing for `grind` that takes alpha equivalence
into account.
2025-05-26 00:51:18 +00:00
Kim Morrison
383f68f806 chore: add grind_trig test case (#8476) 2025-05-26 00:03:53 +00:00
Kim Morrison
41c2ae12f3 chore: update syntax in grind_ite example (#8475) 2025-05-25 23:21:11 +00:00
Sebastian Ullrich
9982bab93e perf: Environment.find? should not block on privacy mismatch (#8472)
This PR avoids name resolution blocking on the elaboration of a
theorem's proof when looking up the theorem name.
2025-05-25 16:18:57 +00:00
Cameron Zwarich
be513656b0 fix: use a custom environment extension for LCNF decls (#8468)
This PR switches the LCNF baseExt/monoExt environment extensions to use
a custom environment extension that uses a PersistentHashMap. The
optimizer relies upon the ability to update a decl multiple times, which
does not work with SimplePersistentEnvExtension.
2025-05-25 15:11:54 +00:00
Kim Morrison
bdbb659765 chore: while awaiting-mathlib, show yellow status not red (#8471)
This PR changes the CI check when the `awaiting-mathlib` label is
present. If `breaks-mathlib` is present, it shows a red cross, but if
neither `breaks-mathlib` nor `builds-mathlib` is present it shows a
yellow circle.
2025-05-25 12:38:56 +00:00
Leonardo de Moura
2a1354b3cc chore: add seal to workaround performance issue (#8469)
This PR adds `seal` commands at `grind_ite.lean` to workaround expensive
definitionally equality tests in the canonicalizer. The new module
system will automatically hide definitions such as `HashMap.insert` and
`TreeMap.insert` which are being unfolded by the canonicalizer in this
test.
This PR also adds a `profileItM` for tracking the time spent in the
`grind` canonicalizer.
2025-05-25 00:54:30 +00:00
Leonardo de Moura
a54872f5f6 fix: preprocessLight at ensureInternalized (#8466)
This PR fixes another instance of the `grind` issue "unexpected kernel
projection term during internalization".
2025-05-24 17:13:20 +00:00
Kim Morrison
2b0b1e013f feat: further generic GetElem lemmas (#8465)
This PR adds further lemmas about `LawfulGetElem`, including marking
some with `@[grind]`.
2025-05-24 12:58:29 +00:00
Mario Carneiro
1f000feb80 chore: remove unnecessary partial in Lean.Expr (#8464)
The termination prover has gotten stronger since these definitions were
written, and now they can be proved terminating automatically. (One
definition had to be changed slightly because it wasn't actually
terminating before.)
2025-05-24 07:00:37 +00:00
Cameron Zwarich
d5060e9e66 feat: add extractClosed pass to LCNF pass list (#8462)
This PR enables the LCNF extractClosed pass by default.
2025-05-24 05:20:10 +00:00
Kim Morrison
38ca310fb7 feat: @[grind] annotations for TreeMap (#8446)
This PR adds basic `@[grind]` annotations for `TreeMap` and its
variants. Likely more annotations will be added after we've explored
some examples.
2025-05-24 04:49:54 +00:00
Kim Morrison
3dd12f85f0 feat: further @[grind] annotations for Option (#8460)
This PR adds further `@[grind]` annotations for `Option`, as follow-up
to the recent additions to the `Option` API in #8379 and #8298.

**However**, I am concurrently investigating adding `attribute [grind
cases] Option`, which will result in many (most?) of the annotations for
`Option` being removed again. In any case, I'm going to merge this
first, as if that is viable I would like to test that most/all the
lemmas now marked with `@[grind]` are still provable by `grind`.
2025-05-24 04:25:00 +00:00
Kim Morrison
0f8618f842 chore: remove @[grind] from Array.size_eq_zero_iff` (#8461) 2025-05-24 04:20:52 +00:00
Kim Morrison
acdef6e04b feat: verification of qsort via grind (#7995)
This PR adds a verification of `Array.qsort` properties, trying to use
`grind` and `fun_induction` where possible.
Currently this is in the `tests/` folder, but once `grind` is ready for
production use we will move it out into the library.

Note that the current `qsort` algorithm has quadratic behaviour on
constant lists, and needs to be adjusted. We'll only move the
verification out into the library once this has been fixed (and the
proofs adapted). These verification theorems may be commented out in the
meantime if it's urgent to fix `qsort`.

---------

Co-authored-by: Kyle Miller <kmill31415@gmail.com>
Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2025-05-24 04:01:55 +00:00
Cameron Zwarich
7b80cd24a9 feat: closed term extraction in the new compiler (#8458)
This PR adds closed term extraction to the new compiler, closely
following the approach in the old compiler. In the future, we will
explore some ideas to improve upon this approach.
2025-05-24 02:40:37 +00:00
Leonardo de Moura
21846ebdf8 feat: non-chronological backtracking for grind (WIP) (#8440)
This PR implements non-chronological backtracking for the `grind`
tactic. This feature ensures that `grind` does not need to process
irrelevant branches after performing a case-split that is not relevant.
It is not just about performance, but also the size of the final proof
term. The new test demonstrates this feature in practice.
```lean
-- In the following test, the first 8 case-splits are irrelevant,
-- and non-choronological backtracking is used to avoid searching
-- (2^8 - 1) irrelevant branches
/--
trace: 
[grind.split] p8 ∨ q8, generation: 0
[grind.split] p7 ∨ q7, generation: 0
[grind.split] p6 ∨ q6, generation: 0
[grind.split] p5 ∨ q5, generation: 0
[grind.split] p4 ∨ q4, generation: 0
[grind.split] p3 ∨ q3, generation: 0
[grind.split] p2 ∨ q2, generation: 0
[grind.split] p1 ∨ q1, generation: 0
[grind.split] ¬p ∨ ¬q, generation: 0
-/
#guard_msgs (trace) in
set_option trace.grind.split true in
theorem ex
    : p ∨ q →
      ¬ p ∨ q →
      p ∨ ¬ q →
      ¬ p ∨ ¬ q →
      p1 ∨ q1 →
      p2 ∨ q2 →
      p3 ∨ q3 →
      p4 ∨ q4 →
      p5 ∨ q5 →
      p6 ∨ q6 →
      p7 ∨ q7 →
      p8 ∨ q8 →
      False := by
  grind (splits := 10)
```
2025-05-23 19:33:54 +00:00
Cameron Zwarich
9ea4946560 feat: add support for USize literals in LCNF (#8456)
This PR adds support for primitive USize literals in LCNF.
2025-05-23 17:22:31 +00:00
Cameron Zwarich
3b205505ef chore: clean up structProjCases pass (#8455) 2025-05-23 15:46:21 +00:00
Lean stage0 autoupdater
6afa8208ec chore: update stage0 2025-05-23 15:21:08 +00:00
Rob23oba
65a5d0cb9d feat: improve Ord proof api (#8378)
This PR improves and extends the api around `Ord` and `Ordering`. These
changes are split off from #8210.
2025-05-23 14:00:20 +00:00
Joachim Breitner
fc3c82b1c7 chore: denixify stage0-updater workflow (#8452)
This PR lets the stage0 autoupdater build lean using the `cmake`
infrastructure, not the deprecated nix infrastructure.
2025-05-23 13:12:50 +00:00
Sebastian Graf
8fc94c5c90 fix: Make split work with metavariables in the target (#8437)
This PR fixes `split` in the presence of metavariables in the target.

The fix consists of replacing an internal use of `apply` for
instantiating match splitters by a new, simpler variant `applyN`. This
new `applyN` is not prone to #8436, which is the ultimate cause for
`split` failing on targets containing metavariables.

---------

Co-authored-by: Sebastian Graf <sg@lean-fro.org>
Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2025-05-23 12:46:27 +00:00
Paul Reichert
96b81f3cc1 feat: lemmas about list iterators (#8384)
This PR provides lemmas about the behavior of `step`, `toArray`,
`toList` and `toListRev` on list iterators created with `List.iter` and
`List.iterM`.
2025-05-23 09:29:59 +00:00
Kim Morrison
44ff70020d feat: add simp lemma writing Vector.tail in terms of Vector.extract (#8445)
This PR adds a `@[simp]` lemma, and comments explaining that there is
intentionally no verification API for `Vector.take`, `Vector.drop`, or
`Vector.tail`, which should all be rewritten in terms of
`Vector.extract`.
2025-05-22 23:22:54 +00:00
Eric Wieser
ae1ab94992 fix: replace bad simp lemmas for Id (#7352)
This PR reworks the `simp` set around the `Id` monad, to not elide or
unfold `pure` and `Id.run`

In particular, it stops encoding the "defeq abuse" of `Id X = X` in the
statements of theorems, instead using `Id.run` and `pure` to pass back
and forth between these two spellings. Often when writing these with
`pure`, they generalize to other lawful monads; though such changes were
split off to other PRs.

This fixes the problem with the current simp set where `Id.run (pure x)`
is simplified to `Id.run x`, instead of the desirable `x`.
This is particularly bad because the` x` is sometimes inferred with type
`Id X` instead of `X`, which prevents other `simp` lemmas about `X` from
firing.

Making `Id` reducible instead is not an option, as then the `Monad`
instances would have nothing to key on.

---------

Co-authored-by: Sebastian Graf <sg@lean-fro.org>
Co-authored-by: Kim Morrison <kim@tqft.net>
Co-authored-by: Paul Reichert <6992158+datokrat@users.noreply.github.com>
2025-05-22 22:45:35 +00:00
Joachim Breitner
5e40f4af52 feat: linear-size noConfusionType construction (#8037)
This PR introduces a `noConfusionType` construction that’s sub-quadratic
in size, and reduces faster.

The previous `noConfusion` construction with two nested `match`
statements is quadratic in size and reduction behavior. Using some
helper definitions, a linear size construction is possible.

With this, processing the RISC-V-AST definition from
https://github.com/opencompl/sail-riscv-lean takes 6s instead of 60s.

The previous construction is still used when processing the early
prelude, and can be enabled elsewhere using `set_option
backwards.linearNoConfusionType false`.
2025-05-22 14:54:05 +00:00
Rob23oba
2594a8edad fix: namespace completion to only use the short name (#8350)
This PR changes namespace completion to use the same algorithm as
declaration identifier completion, which makes it use the short name
(last name component) for completions instead of the full name, avoiding
namespace duplications.

Closes #5654
2025-05-22 11:58:47 +00:00
Kim Morrison
b24e232a7a feat: lemmas about ordered rings and fields for grind (#8443)
This PR adds the lemmas about ordered rings and ordered fields which
will be needed by the new algebraic normalization components of `grind`.
2025-05-22 11:41:51 +00:00
Jakob von Raumer
9ad3974314 feat: add List.drop_cons (#8434)
This PR adds the equivalent of `List.take_cons` about `List.drop`.
2025-05-22 11:29:42 +00:00
Lean stage0 autoupdater
b31bf4e645 chore: update stage0 2025-05-22 11:24:54 +00:00
Marc Huisinga
c8d245a08f fix: unknown identifier ranges (#8362)
This PR fixes a bug where the unknown identifier code actions wouldn't
work correctly for some unknown identifier error spans and adjusts
several unknown identifier spans to actually end on the identifier in
question.

The following additional adjustments are made:
- The fallback mechanism of the unknown identifier code actions is
removed, since it could produce severely incorrect suggestions for
unknown identifier errors on fields.
- A performance bug when using the code action to import all unknown
identifiers is fixed.
- A bug that occurs when the elaborator produces multiple overlapping
completion infos is fixed.
- A bug in the snapshot selection that could cause it to wait for
snapshots in snapshots with non-canonical syntax is fixed.
- Some invariants of the snapshot tree are documented.
- The snapshot tree formatting is adjusted to display the final info
tree again.
2025-05-22 10:05:31 +00:00
Leonardo de Moura
4eccb5b479 fix: grind diagnostics at maxHeartbeats (#8438)
This PR ensures that `grind` diagnostics are obtained even when
`maxHeartbeats` is reached.
This PR also removes some dead code.
2025-05-21 22:14:59 +00:00
Paul Reichert
0a43c138ac feat: lemmas about iterator collectors (#8380)
This PR provides simple lemmas about `toArray`, `toList` and `toListRev`
for the iterator library.

It also changes the definition of `Iter` and `IterM` so that they aren't
equal anymore and in particular not definitionally equal. While it was
very convenient to have them be definitionally equal when working with
dependent code, it was also confusing and annoying that one would
sometimes end up with something like `it.toList = IterM.toList it`,
where `it : Iter β`.
2025-05-21 21:11:26 +00:00
Arthur Adjedj
1138062a70 fix: normalize imax 1 u to u (#7631)
This PR fixes `Lean.Level.mkIMaxAux` (`mk_imax` in the kernel) such that
`imax 1 u` reduces to `u`.

Closes #7096
2025-05-21 20:27:53 +00:00
grunweg
ebf455a137 doc: clarify that .now returns a date(time) in the local time zone (#8331)
This PR improves the docstring for `PlainDateTime.now` and its variants.

---------

Co-authored-by: Markus Himmel <markus@lean-fro.org>
2025-05-21 08:04:36 +00:00
Kim Morrison
87cc330489 feat: ordered ring typeclass for grind (#8429)
This PR adds `Lean.Grind.Ring.IsOrdered`, and cleans up the ring/module
grind API. These typeclasses are at present unused, but will support
future algorithmic improvements in `grind`.
2025-05-21 07:05:01 +00:00
Kim Morrison
47a1355fc4 chore: cleanup grind palindrome test (#8428) 2025-05-21 03:31:56 +00:00
Kim Morrison
79254d039c chore: restore @[simp] to List.ofFn_succ (#8427) 2025-05-21 03:12:26 +00:00
Leonardo de Moura
c28b052576 feat: [grind?] attribute (#8426)
This PR adds the attribute `[grind?]`. It is like `[grind]` but displays
inferred E-matching patterns. It is a more convinient than writing.
Thanks @kim-em for suggesting this feature.
```lean
set_option trace.grind.ematch.pattern true
```
This PR also improves some tests, and adds helper function
`ENode.isRoot`.
2025-05-21 00:32:49 +00:00
Kim Morrison
a541b8e75e chore: fix name of new Fin.foldlM_eq_finRange_foldlM lemmas (#8425) 2025-05-21 00:30:33 +00:00
Li Xuanji
a9a069a0ef doc: Fix doc bug in Resolve.lean (#8411)
This PR fixes a doc bug in the Resolve.lean; in reverse order, B comes
before A
2025-05-20 17:16:18 +00:00
Leonardo de Moura
8753239226 chore: remove Grind.Config.failures options (#8423)
Option is not very useful.
2025-05-20 15:40:51 +00:00
Paul Reichert
f4ee72b18c feat: minimal iterator library (#8358)
This PR introduces a very minimal version of the new iterator library.
It comes with list iterators and various consumers, namely `toArray`,
`toList`, `toListRev`, `ForIn`, `fold`, `foldM` and `drain`. All
consumers also come in a partial variant that can be used without any
proofs. This limited version of the iterator library generates decent
code, even with the old code generator.
2025-05-20 14:53:57 +00:00
Leonardo de Moura
8535a2268b fix: simplify isCasesAttrCandidate? in grind (#8415)
The behavior was counterintuitive.
2025-05-20 14:29:07 +00:00
Kim Morrison
d8e7ca2355 feat: draft typeclasses/tests for grind handling fields (#8417)
This PR introduces `Lean.Grind.Field`, proves that a `IsCharP 0` field
satisfies `NoNatZeroDivisors`, and sets up some basic (currently
failing) tests for `grind`.
2025-05-20 13:44:11 +00:00
Henrik Böving
8e0870beec feat: LT for Timestamp and Duration (#8422)
This PR adds `LT` and `Decidable` `LT` instances for
`Std.Time.Timestamp` and `Std.Time.Duration`.
2025-05-20 11:33:49 +00:00
Kim Morrison
3790f8c78e chore: deduplicate Grind.RatModule and Grind.NoNatZeroDivisors (#8416)
Also adds instances from e.g. `Semiring` to `NatModule` and `Ring` to
`IntModule`.
2025-05-20 07:49:42 +00:00
Kim Morrison
3bf95e9b58 feat: add List/Array/Vector.ofFnM (#8389)
This PR adds the `List/Array/Vector.ofFnM`, the monadic analogues of
`ofFn`, along with basic theory.

At the same time we pave some potholes in nearby API.

---------

Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
2025-05-20 05:28:29 +00:00
Kim Morrison
bc21b57396 chore: use HMul in Lean.Grind.Module (#8414) 2025-05-20 04:22:06 +00:00
Kim Morrison
6395d69140 feat: add HashMap.get*_filter* lemmas specialized for LawfulBEq (#8399)
This PR adds variants of `HashMap.getElem?_filter` that assume
`LawfulBEq` and have a simpler right-hand-side. `simp` can already
achieve these, via rewriting with `getKey_eq` under the lambda. However
`grind` can not, and these lemmas help `grind` work with `HashMap`
goals. There are variants for all variants of `HashMap`,
`getElem?/getElem/getElem!/getD`, and for `filter` and `filterMap`.
2025-05-20 03:04:32 +00:00
Leonardo de Moura
4ba72aeef7 feat: missing normalization rules in grind (#8413)
This PR implements normalization rules that pull universal quantifiers
across disjunctions. This is a common normalization step performed by
first-order theorem provers.
2025-05-20 02:38:29 +00:00
Leonardo de Moura
e984473886 fix: markNestedProofs preprocessor in grind (#8412)
This PR fixes the `markNestedProofs` preprocessor used in `grind`. There
was a missing case (e.g., `Expr.mdata`)
2025-05-20 01:46:23 +00:00
Leonardo de Moura
88f6439955 fix: case-splitting in grind (#8410)
This PR fixes a case-splitting heuristic in `grind` and simplifies the
proof for test `grind_palindrome2.lean`.
2025-05-20 00:51:47 +00:00
Cameron Zwarich
fc8f290347 feat: support native literals of size unsigned integer types (#8409)
This PR adds support to LCNF for native UInt8/UInt16/UInt32/UInt64
literals.
2025-05-20 00:38:38 +00:00
Cameron Zwarich
423b31755d chore: remove dependency of pretty-printing LCNF.LitValue on toExpr (#8408) 2025-05-19 22:55:21 +00:00
jrr6
d1ec806834 feat: improve error messages in invalid match alternatives (#8368)
This PR improves the error messages produced by invalid pattern-match
alternatives and improves parity in error placement between
pattern-matching tactics and elaborators.

Closes #7170
2025-05-19 17:40:41 +00:00
jrr6
b93231f97e feat: improve inductive type parameter error messages (#8338)
This PR improves the error messages displayed in `inductive`
declarations when type parameters are invalid or absent.

Closes #2195 by improving the relevant error message.
2025-05-19 17:03:49 +00:00
Kim Morrison
f40d72ea47 feat: typeclasses for grind to work with ordered modules (#8347)
This PR adds draft typeclasses for `grind` to process facts about
ordered modules. These interfaces will evolve as the implementation
develops.
2025-05-19 13:55:38 +00:00
Kim Morrison
10fdfc54cb chore: upstream HSMul notation typeclass (#8401)
Upstreaming the `HSMul` notation typeclass, to enable `grind` to process
goals using it.
2025-05-19 12:37:08 +00:00
David Thrane Christiansen
943a9c6a43 chore: revert mistaken deletion (#8404)
This PR reverts the deletion of files that should not have been removed
with the old documentation site.
2025-05-19 12:14:09 +00:00
Wojciech Rozowski
a8a6f71abb fix: add monotonicity lemmas for universal quantifiers (#8403)
This PR adds missing monotonicity lemmas for universal quantifiers, that
are used in defining (co)inductive predicates.
2025-05-19 11:27:46 +00:00
Markus Himmel
9ad4414642 feat: Option lemmas (#8379)
This PR adds missing `Option` lemmas.

Also:

- generalize `bindM` from `Monad` to `Pure`
- change the `simp` normal form of both `<|>` and `Option.orElse` to
`Option.or`
2025-05-19 08:59:31 +00:00
Kim Morrison
efe2ab4c04 chore: remove duplicate instances (#8397)
This PR cleans up many duplicate instances (or, in some cases,
needlessly duplicated `def X := ...; instance Y := X`).
2025-05-19 04:36:06 +00:00
Cameron Zwarich
831026bcf4 chore: remove redundant ToFormat/ToString debug printing instances (#8400) 2025-05-19 03:31:22 +00:00
Cameron Zwarich
fbac0d2ddb chore: use LitValue.toExpr instead of duplicating its definition (#8398) 2025-05-19 01:33:47 +00:00
Eric Wieser
e7b8df0c0e fix: change Array. lemma to be about Array (#8392)
This PR corrects some `Array` lemmas to be about `Array` not `List`.

Discovered [on
Zulip](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/duplicate.20declarations/near/518942094)
2025-05-19 00:29:35 +00:00
Kim Morrison
601ea24e31 chore: add failing grind tests for noncommutative/non-negation rings (#8396) 2025-05-19 00:26:16 +00:00
Cameron Zwarich
ca037ded0d chore: rename LitValue.natVal/strVal to .nat/str (#8394) 2025-05-18 22:10:58 +00:00
Cameron Zwarich
006d2925ba chore: rename LetValue.value to .lit (#8393) 2025-05-18 21:12:35 +00:00
Mac Malone
c8290bd942 fix: lake: import Lake w/ precompiled modules on MacOS (#8383)
This PR fixes the use of `import Lake` with precompiled modules, which
was previously broken on MacOS.

Closes #7388.
2025-05-16 21:24:13 +00:00
Henrik Böving
b7b95896aa fix: tests that suffer from renaming (#8386) 2025-05-16 17:18:52 +00:00
Lean stage0 autoupdater
e46daa8ee6 chore: update stage0 2025-05-16 16:17:48 +00:00
Kyle Miller
3854ba87b6 feat: pretty print letFun using have syntax (#8372)
This PR modifies the pretty printer to use `have` syntax instead of
`let_fun` syntax.
2025-05-16 15:10:01 +00:00
Sebastian Ullrich
4d58a3d124 feat: revamp aux decl name generation (#8363)
This PR unifies various ways of naming auxiliary declarations in a
conflict-free way and ensures the method is compatible with diverging
branches of elaboration such as parallelism or Aesop-like
backtracking+replaying search.
2025-05-16 14:57:18 +00:00
Joachim Breitner
6b7a803bf4 fix: mapError to store message data context (#8375)
This PR ensures that using `mapError` to expand an error message uses
`addMessageContext` to include the current context, so that expressions
are rendered correctly. Also adds a `preprendError` variant with a more
convenient argument order for the common cases of
prepending-and-indenting.
2025-05-16 14:46:23 +00:00
Joachim Breitner
0e96318c72 chore: update DTreeMap proofs with more unfolding induction (#8382)
This is a post-stage0 update following #8359.
2025-05-16 14:41:37 +00:00
Sebastian Ullrich
7994e55d80 chore: try refining some benchmark settings (#8377) 2025-05-16 11:24:11 +00:00
Lean stage0 autoupdater
d24aa91232 chore: update stage0 2025-05-16 10:08:06 +00:00
Joachim Breitner
e7b61232c9 feat: more parameters in .fun_cases theorem (#8359)
This PR improves the functional cases principles, by making a more
educated guess which function parameters should be targets and which
should remain parameters (or be dropped). This simplifies the
principles, and increases the chance that `fun_cases` can unfold the
function call.

Fixes #8296 (at least for the common cases, I hope.)
2025-05-16 09:06:21 +00:00
Sebastian Ullrich
af7eb01f29 chore: build leanc with Lake under USE_LAKE (#8336)
Removes the last use of stdlib.make.in in this configuration outside
stage 0.
2025-05-16 08:07:34 +00:00
Markus Himmel
ca9b3eb75f chore: variants of dite_eq_left_iff (#8357)
This PR adds variants of `dite_eq_left_iff` that will be useful in a
future PR.
2025-05-16 05:42:12 +00:00
Cameron Zwarich
a817067295 chore: adopt Option.getD (#8374) 2025-05-16 05:07:49 +00:00
Cameron Zwarich
fcb6bcee67 fix: revert #8023 now that it is redundant (#8371)
This PR reverts #8023 now that it has been made redundant by the more
general fix in #8367.
2025-05-16 00:53:30 +00:00
Kim Morrison
73509d03f3 chore: cleanup previously failing grind test (#8370)
This test is superseded by the `qsort_grind` branch.
2025-05-16 00:24:33 +00:00
Leonardo de Moura
6448547f41 fix: instantiateTheorem in grind (#8369)
This PR fixes a type error at `instantiateTheorem` function used in
`grind`. It was failing to instantiate theorems such as
```lean
theorem getElem_reverse {xs : Array α} {i : Nat} (hi : i < xs.reverse.size)
    : (xs.reverse)[i] = xs[xs.size - 1 - i]'(by simp at hi; omega)
```
in examples such as
```lean
example (xs : Array Nat) (w : xs.reverse = xs) (j : Nat) (hj : 0 ≤ j) (hj' : j < xs.size / 2)
    : xs[j] = xs[xs.size - 1 - j]
```
generating the issue
```lean
  [issue] type error constructing proof for Array.getElem_reverse
      when assigning metavariable ?hi with
        ‹j < xs.toList.length›
      has type
        j < xs.toList.length : Prop
      but is expected to have type
        j < xs.reverse.size : Prop
```
2025-05-15 23:06:32 +00:00
Cameron Zwarich
632b688cb7 feat: add an LCNF pass to convert structure projections to cases expressions (#8367)
This PR adds a new `structProjCases` pass to the new compiler, analogous
to the `struct_cases_on` pass in the old compiler, which converts all
projections from structs into `cases` expressions. When lowered to IR,
this causes all of the projections from a single structure to be grouped
together, which is an invariant relied upon by the IR RC passes (at
least for linearity, if not general correctness).
2025-05-15 21:54:25 +00:00
Cameron Zwarich
c5335b6f9a fix: give Ordering.then the expose attribute (#8366)
This PR adds the `expose` attribute to `Ordering.then`. This is required
for building with the new compiler, but works fine with the old compiler
because it silently ignores the missing definition.
2025-05-15 21:25:40 +00:00
Leonardo de Moura
a594f655da fix: use withReducibleAndIntances to match ground patterns (#8365)
This PR fixes the transparency mode for ground patterns. This is
important for implicit instances. Here is a mwe for an issue detected
while testing `grind` in Mathlib.
```lean
example (a : Nat) : max a a = a := by
  grind

instance : Max Nat where
  max := Nat.max

example (a : Nat) : max a a = a := by
  grind -- Should work
```
2025-05-15 19:50:46 +00:00
Leonardo de Moura
7a6bca5276 feat: basic support for eta reduction in grind (#7977)
This PR adds basic support for eta-reduction to `grind`.

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
2025-05-15 18:34:56 +00:00
Joachim Breitner
e5393cf6bc fix: cases tactic to handle non-atomic eliminator well (#8361)
This PR fixes a bug in the `cases` tacic introduced in #3188 that arises
when cases (not induction) is used with a non-atomic expression in using
and the argument indexing gets confused.

This fixes #8360.
2025-05-15 16:59:11 +00:00
Joachim Breitner
3481f43130 fix: FunInd: strip MData when creating the unfolding theorem (#8354)
This PR makes sure that when generating the unfolding functional
induction theorem, `mdata` does not get in the way.
2025-05-15 16:09:20 +00:00
Joachim Breitner
528fe0b0ed fix: FunInd: clean up packed arguments more throughly (#8356)
This PR tries harder to clean internals of the argument packing of n-ary
functions from the functional induction theorem, in particular the
unfolding variant
2025-05-15 12:58:52 +00:00
Sebastian Ullrich
01dbbeed99 feat: do not export def bodies by default (#8221)
This PR adjusts the experimental module system to not export the bodies
of `def`s unless opted out by the new attribute `@[expose]` on the `def`
or on a surrounding `section`.

---------

Co-authored-by: Markus Himmel <markus@lean-fro.org>
2025-05-15 12:16:54 +00:00
Sebastian Ullrich
9486421fcc chore: tame some slow benchmarks (#8352)
No single-topic benchmark should take half as long as stdlib.

Bench run time reduced from 27min to 21min.
2025-05-15 11:53:10 +00:00
JovanGerb
d69a8eff3f fix: deduplicate elaboration of constant argument to rw (#8232)
This PR fixes elaboration of constants in the `rewrite` tactic.
previously, `rw [eq_self]` would elaborate `eq_self` twice, and add it
to the infotree twice. This would lead to the "Expected type" being
delaborated with an unknown universe metavariable.

I added a test to show this error during delaboration of the "Expected
type".

This was reported on Zulip as a panic message during delaboration:
[#mathlib4 > Crash in &#96;sup&#96;/&#96;inf&#96; /
&#96;max&#96;/&#96;min&#96;
delaborators](https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Crash.20in.20.60sup.60.2F.60inf.60.20.2F.20.60max.60.2F.60min.60.20delaborators/with/515946714)
2025-05-15 11:33:10 +00:00
Kim Morrison
8154aaa1b3 feat: preparation for semirings and noncommutative rings in grind (#8343)
This PR splits `Lean.Grind.CommRing` into 4 typeclasses, for semirings
and noncommutative rings. This does not yet change the behaviour of
`grind`, which expects to find all 4 typeclasses. Later we will make
some generalizations.
2025-05-15 11:25:57 +00:00
Kim Morrison
abc85c2f3c chore: fix Inv.inv notation (#8351) 2025-05-15 11:22:48 +00:00
Jakob von Raumer
436221986a fix: fix typo in inhabited instance for ExtDHashMap (#8349)
This PR fixes the signature of the intended `Inhabited` instance for
`ExtDHashMap`.
2025-05-15 08:40:23 +00:00
Sebastian Ullrich
49369f9c7c chore: change chatty test to interactive test (#8348) 2025-05-15 07:56:26 +00:00
Kim Morrison
305fba625d feat: missing lemmas about Int order/multiplication (#8346)
This PR adds some missing lemmas about consequences of
positivity/non-negativity of `a * b : Int`.
2025-05-15 06:17:46 +00:00
Kim Morrison
83001213e3 chore: upstream Inv notation typeclass (#8345) 2025-05-15 03:56:23 +00:00
Leonardo de Moura
06ef738aec fix: etaStruct and preprocessing issues in grind (#8344)
This PR fixes term normalization issues in `grind`, and the new option
`grind +etaStruct`.
2025-05-15 03:32:10 +00:00
Kim Morrison
37529a5518 chore: initial work on grind attributes for TreeMap (#8342) 2025-05-15 02:24:51 +00:00
Leonardo de Moura
fad3e0ef5e fix: propagateCtor (#8341)
This PR fixes the `propagateCtor` constraint propagator used in `grind`.
2025-05-15 00:32:25 +00:00
Anne Baanen
e982bf9472 feat: implement "linter sets" that can be turned on as a group (#8106)
This PR adds a `register_linter_set` command for declaring linter sets.
The `getLinterValue` function now checks if the present linter is
contained in a set that has been enabled (using the `set_option` command
or on the command line).

The implementation stores linter set membership in an environment
extension. As a consequence, we need to pass more data to
`getLinterValue`: the argument of ype `Options` has been replaced with a
`LinterOptions`, which you can access by writing `getLinterOptions`
instead of `getOptions`. (The alternative I considered is to modify the
`Options` structure. The current approach seems a bit higher-level and
lower-impact.)

The logic for checking whether a linter should be enabled now goes in
four steps:
1. If the linter has been explicitly en/disabled, return that.
2. If `linter.all` has been explicitly set, return that.
3. If the linter is in any set that has been enabled, return true.
4. Return the default setting for the linter.

Reasoning:
* The linter's explicit setting should take precedence.
* We want to be able to disable all but the explicitly enabled linters
with `linter.all`, so it should take precedence over linter sets.
* We want to progressively enable more linters as they become available,
so the check over sets should be *any*.
* Falling back to the default value last, ensures compatibility with the
current way we define linters.

The public-facing API currently does not allow modifying sets: all
linters have to be added when the set is declared. This way, there is
one place where all the contents of the set are listed.

Linter sets can be declared to contain linters that have not been
declared (yet): this allows declaring linter sets low down in the import
hierarchy when not all the requested linters are defined yet.

---------

Co-authored-by: grunweg <rothgami@math.hu-berlin.de>
2025-05-14 23:30:42 +00:00
Lean stage0 autoupdater
4efef5760c chore: update stage0 2025-05-14 23:49:50 +00:00
JovanGerb
0a32ba371a perf: store dsimp cache in a simp call (#7428)
This PR adds a `dsimp` cache to `simp`. Previously each `dsimp` call
from `simp` started with a fresh cache.

For example, when simplifying `a * b` for `a b : A`, the type `A` is now
only visited once by `dsimp`, instead of at least 3 times.
[
Mathlib
bench](https://github.com/leanprover-community/mathlib4/pull/22812#issuecomment-2712043349):
```
Metric                 Change
=============================
instructions            -8.1%
task-clock              -7.4%
simp                   -45.6%
instantiate metavars   -11.7%
share common exprs      -8.2%
```

[#lean4 > Enormous speedup from &#96;dsimp&#96; caching in
&#96;simp&#96;](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Enormous.20speedup.20from.20.60dsimp.60.20caching.20in.20.60simp.60)

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2025-05-14 22:21:06 +00:00
JovanGerb
f699e18212 perf: dsimp shouldn't visit proofs (#6973)
This PR stops `dsimp` from visiting proof terms, which should make
`simp` and `dsimp` more efficient.
In this attempt I have `dsimp` leave the proofs in place as-is, instead
of simplifying the proof type.

Closes #6960
2025-05-14 22:09:25 +00:00
Rob23oba
b8c941d39a chore: use mutual inductives for data structures in the new compiler (#8332)
This PR changes the types `AltCore`, `FunDeclCore` and `CasesCore` used
in the IRs of the new compiler into the mutual inductives `Alt`,
`FunDecl` and `Cases`.
2025-05-14 18:04:49 +00:00
jrr6
995fa4766b fix: reduce ambiguity of "final" in application type mismatch message (#8322)
This PR refines the new wording of the "application type mismatch" error
message to avoid ambiguity in references to the "final" argument in a
subexpression that may be followed by additional arguments.

It does so by replacing "final" with "last," rephrasing the message so
that this adjective modifies the argument itself rather than the word
"argument," and only displaying this wording when two arguments could be
confused (determined by expression equality).

These changes were motivated by a report that in cases where a function
application `f a b c` fails to elaborate because `b` is incorrectly
typed, the existing error message's reference to `b` being the "final"
argument in the application `f a b` may create confusion because it is
not the final argument in the full application expression.
2025-05-14 16:12:10 +00:00
Sebastian Ullrich
e635eeacd3 chore: CI: replace Nix CI with Linux release (#8335)
Nix cannot handle the awesomeness of our stdlib anymore.
2025-05-14 15:31:40 +00:00
Lean stage0 autoupdater
7240ae2fa2 chore: update stage0 2025-05-14 16:03:07 +00:00
David Thrane Christiansen
12ff2d8c49 chore: remove old documentation site (#7974)
This PR removes the old documentation overview site, as its content has
moved to the main Lean website infrastructure.

This should be merged when the new website section is deployed, after
installing appropriate redirects.

Developer documentation is remaining in Markdown form, but it will no
longer be part of the documentation hosted on the Lean website. Example
code stays here for CI, but it is now rendered via a Verso plugin.
2025-05-14 14:31:33 +00:00
euprunin
88078930a9 chore: fix spelling mistakes (#8324)
Co-authored-by: euprunin <euprunin@users.noreply.github.com>
2025-05-14 06:52:16 +00:00
Leonardo de Moura
6ca31baa55 feat: structure extensionality in grind (#8330)
This PR improves support for structure extensionality in `grind`. It now
uses eta expansion for structures instead of the extensionality theorems
generated by `[ext]`. Examples:

```lean
opaque f (a : Nat) : Nat × Bool

attribute [grind ext] Prod Subtype

example (a b : Nat) : (f a).1 = (f b).1 → (f a).2 = (f b).2 → f a = f b := by
  grind

def g (a : Nat) : { x : Nat // x > 1 } :=
  ⟨a + 2, by grind⟩

example (a b : Nat) : (g a).1 = (g b).1 → g a = g b := by
  grind

@[grind ext] structure S where
  x : Nat
  y : Int

example (x y : S) : x.1 = y.1 → x.2 = y.2 → x = y := by
  grind
```
2025-05-14 02:43:52 +00:00
Kim Morrison
1312d08eda chore: update grind test (#8329) 2025-05-14 00:36:03 +00:00
Kim Morrison
8c80c3ca49 feat: follow-up to lemmas about List.intersperse (#8318)
This PR is follow-up to #8272, combining the conditional lemmas for
`getElem_intersperse` into a single lemma with an `if` on the RHS.
2025-05-14 00:24:13 +00:00
Kim Morrison
7688fbb067 feat: add @[grind] annotations to contains_iff_mem lemmas (#8328)
This PR adds the `@[grind =]` attribute to all `contains_iff_mem`
lemmas.
2025-05-14 00:03:46 +00:00
Kim Morrison
5b2e39e3b5 feat: add @[grind] annotations for generic GetElem lemmas (#8327)
This PR adds `@[grind]` annotations to the generic
`getElem?_eq_none_iff`, `isSome_getElem?`, and `get_getElem?`.
2025-05-14 00:03:38 +00:00
Kim Morrison
b5bf0b1d05 chore: cleanup of grind if-then-else example (#8326) 2025-05-13 23:01:34 +00:00
Kim Morrison
ff85acedb9 chore: move a grind test (#8325)
This PR moves a previously failing `grind` test from `tests/lean/grind/`
to `tests/lean/run/`.
2025-05-13 19:50:12 +00:00
Henrik Böving
337685a38a feat: bv_decide support for BitVec.reverse (#8323)
This PR adds support for bv_decide to understand `BitVec.reverse` in
bitblasting.
2025-05-13 18:32:12 +00:00
Joachim Breitner
127776288b fix: GuessLex: also look for negations of Nat comparisons (#8321)
This PR lets the termination argument inference consider negations of
Nat comparisons. Fixes #8257.
2025-05-13 15:10:19 +00:00
Joachim Breitner
1d90eac631 test: more fundind unfolding test for #8293 (#8320) 2025-05-13 13:19:37 +00:00
Marc Huisinga
92b59ae4f6 test: goals accomplished (#8319)
This PR adds a test for the goals accomplished diagnostics so that we
notice when they break.

Follow-up for #8242.
2025-05-13 13:00:47 +00:00
Eric Wieser
aa3e7848c2 fix: correct whitespace in omit/include (#8169)
This PR makes the whitespace handling in the syntax of `omit` and
`include` consistent with `variable`.

Zulip thread: [#lean4 > Pretty printing instances in &#96;omit&#96; @
💬](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/Pretty.20printing.20instances.20in.20.60omit.60/near/515185216)
2025-05-13 12:50:11 +00:00
Leonardo de Moura
e0a266780b feat: add instance Grind.CommRing (Fin n) (#8276)
This PR adds the instances `Grind.CommRing (Fin n)` and `Grind.IsCharP
(Fin n) n`. New tests:
```lean
example (x y z : Fin 13) :
    (x + y + z) ^ 2 = x ^ 2 + y ^ 2 + z ^ 2 + 2 * (x * y + y * z + z * x) := by
  grind +ring

example (x y : Fin 17) : (x + y) ^ 3 = x ^ 3 + y ^ 3 + 3 * x * y * (x + y) := by
  grind +ring

example (x y : Fin 19) : (x - y) * (x ^ 2 + x * y + y ^ 2) = x ^ 3 - y ^ 3 := by
  grind +ring
```

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2025-05-13 12:09:02 +00:00
Joachim Breitner
2299c3c9ec chore: post-stage0 adaptions for #8104 (#8317) 2025-05-13 11:38:21 +00:00
Kim Morrison
2cf3ac9461 feat: split Std.Classes.Ord (#8315)
This PR splits `Std.Classes.Ord` into `Std.Classes.Ord.Basic` (with few
imports) and `Std.Classes.Ord.SInt` and `Std.Classes.Ord.Vector`. These
changes avoid importing `Init.Data.BitVec.Lemmas` unnecessarily into
various basic files.
As the new import-only file `Std.Classes.Ord` imports all three of
these, end-users are not affected.
2025-05-13 11:22:19 +00:00
Kim Morrison
384c78ae13 chore: remove >6 month old deprecations (#8312) 2025-05-13 11:11:22 +00:00
Marcus Rossel
a6a2833c68 feat: lemmas about List.intersperse (#8272)
This PR adds lemmas about the length and use of `[]?` on results of
`List.intersperse`.

This was suggested by @TwoFX as discussed in
https://github.com/TwoFX/human-eval-lean/pull/164#discussion_r2074101914.

I am unsure about the correct naming of `intersperse_getElem?_even` and
`intersperse_getElem?_odd`.
2025-05-13 10:58:35 +00:00
Lean stage0 autoupdater
b04ecaefd7 chore: update stage0 2025-05-13 10:47:03 +00:00
Joachim Breitner
e575736cae feat: fun_induction to unfold function application in the goal (#8104)
This PR makes `fun_induction` and `fun_cases` (try to) unfold the
function application of interest in the goal. The old behavior can be
enabled with `set_option tactic.fun_induction.unfolding false`. For
`fun_cases` this does not work yet when the function’s result type
depends on one of the arguments, see issue #8296.
2025-05-13 09:37:39 +00:00
Markus Himmel
8eec1e4cfb feat: Option lemmas and cleanup (#8298)
This PR adds various `Option` lemmas and defines `Option.filterM` for
applicative functors.
2025-05-13 08:42:03 +00:00
Marc Huisinga
9659469998 fix: broken unknown identifier code actions (#8180)
This PR fixes the new unknown identifier code actions so that they work
in non-trivial files.

It's very unfortunate that we didn't notice this sooner.
2025-05-13 08:32:36 +00:00
Marc Huisinga
efcf94298a feat: improve workspace symbol performance (#8091)
This PR improves the performance of the workspace symbol request.

In my testing on my machine, the time to respond to the workspace symbol
request containing just `c` in Mathlib has been reduced to ~1200ms from
~11000ms.

We also serve the nearest-matching 1000 symbols instead of just the
first 100 now and use the length of the symbol as a tie-breaker for when
the fuzzy matching score is equal.

Some further improvements might be gained in the future when #8087 is
fixed and we can switch back to `qsort`.
2025-05-13 08:29:49 +00:00
Kim Morrison
f75e36dcdb chore: Vector doesn't extend Array (#8313)
This PR changes the definition of `Vector` so it no longer extends
`Array`. This prevents `Array` API from "leaking through".
2025-05-13 07:13:23 +00:00
Kim Morrison
aa647f3cd6 chore: cleaning up imports (#8314) 2025-05-13 07:09:21 +00:00
Kim Morrison
77302b6572 chore: add grind test for fastEraseDups (#8310)
This PR adds @TwoFX's `List.fastEraseDups` example, with the proof
golfed further using `grind`, as a test case for `grind`.
2025-05-13 06:55:39 +00:00
Kim Morrison
29cc75531a chore: remove accidental grind trace options (#8311) 2025-05-13 05:58:46 +00:00
Kim Morrison
a08d182359 feat: add @[grind] annotations for HashMap (#8246)
This PR add `@[grind]` annotations for HashMap and variants.
2025-05-13 04:56:41 +00:00
Cameron Zwarich
ef77434a49 fix: make new compiler's specialization closure behavior match old compiler (#8308)
This PR makes the new compiler's specialization pass compute closures
the same way as the old compiler, in particular when it comes to
variables captured by lambdas.
2025-05-12 21:31:03 +00:00
Henrik Böving
aa54390c85 fix: bv_decide preprocessing in dependently typed situations (#8306)
This PR makes it possible for `bv_decide` to tackle situations for its
enum type preprocessing where the enums themselves are use in a
dependently type context (for example inside of a `GetElem` body) and
thus not trivially accessible to `simp` for rewriting. To do this we
drop`GetElem` on `BitVec` as well as `dite` as early as possible in the
pipeline.
2025-05-12 21:03:58 +00:00
Cameron Zwarich
579d0ad15d chore: add @zwarich to the compiler CODEOWNERS (#8305) 2025-05-12 18:42:54 +00:00
Rob23oba
e212890dfc perf: optimize Lean/Compiler/IR/ToIR compilation time and size (#8286)
This PR optimizes the `ToIR.lean` module, reducing the size of the
compiled C code by a bit over a factor of 3. This significantly improves
the compilation time, making `ToIR` relatively quick to compile.

Closes #8269
2025-05-12 18:34:07 +00:00
Leonardo de Moura
1aa16f1e3c fix: missing foldProjs (#8303)
This PR fixes missing occurrences of `foldProjs` in `grind`.
2025-05-12 18:32:57 +00:00
Joachim Breitner
cc80f7943d fix: cases to fail gracefully when motive has complex argument of dependent type (#8302)
This PR lets `cases` fail gracefully when the motive has an complex
argument whose type is dependent type on the targets. While the
`induction` tactic can handle this well, `cases` does not. This change
at least gracefully degrades to not instantiating that motive parameter.
See issue #8296 for more details on this issue.
2025-05-12 16:04:26 +00:00
Joachim Breitner
c55bf5172d feat: unfolding induction theorems to unfold bif (#8301)
This PR unfolds functions in the unfolding induction principle properly
when they use `bif` (a.k.a. `Bool.cond`).
2025-05-12 16:00:30 +00:00
Leonardo de Moura
3f75f08e1d feat: abstract metavars in grind preprocessor (#8299)
This PR implements a missing preprocessing step in `grind`: abstract
metavariables in the goal
2025-05-12 14:53:54 +00:00
Markus Himmel
eda467e066 fix: typo in application type mismatch error message (#8290)
This PR fixes a typo that was introduced recently.
2025-05-12 13:35:29 +00:00
Lean stage0 autoupdater
ab5b8ffed1 chore: update stage0 2025-05-12 13:49:07 +00:00
Kim Morrison
7f6f4c889d feat: use NeZero in Fin lemmas where possible (#8291)
This PR changes the statements of `Fin` lemmas to use `[NeZero n] (i :
Fin n)` rather than `(i : Fin (n+1))` where possible.
2025-05-12 12:40:10 +00:00
Kim Morrison
294360518a chore: adjust @[grind] attributes on List lemmas (#8295) 2025-05-12 12:31:29 +00:00
Sebastian Ullrich
c7acb7e481 chore: reserve [expose] attribute (#8292)
To be used in the module system.
2025-05-12 12:19:30 +00:00
Siddharth
9105c01757 feat: BitVec.neg_ofNat_eq_ofInt_neg (#8206)
This PR shows that negating a bitvector created from a natural number
equals creating a bitvector from the the negative of that number (as an
integer).

```lean
theorem neg_ofNat_eq_ofInt_neg {w : Nat} (x : Nat) :
    - BitVec.ofNat w x = BitVec.ofInt w (- x) := by
  apply BitVec.eq_of_toInt_eq
  simp [BitVec.toInt_neg, BitVec.toInt_ofNat]
```

---------

Co-authored-by: Luisa Cicolini <48860705+luisacicolini@users.noreply.github.com>
2025-05-12 10:00:49 +00:00
Henrik Böving
d0c4d19270 fix: bv_decide can handle universe polymorphic enums (#8270)
This PR makes the enum pass of `bv_decide` handle enum types that are
universe polymorphic.
2025-05-12 08:22:57 +00:00
Kim Morrison
60ea92fdb0 chore: add failing grind tests (#8289) 2025-05-12 06:33:38 +00:00
Kim Morrison
2b4f372317 chore: add failing grind test (#8288) 2025-05-12 06:10:25 +00:00
Kim Morrison
10bda559f9 chore: begin development cycle for v4.21.0 (#8287) 2025-05-12 05:02:41 +00:00
Joachim Breitner
33aaabaed7 fix: FunInd: rewrite matches more reliably in .induct_unfolding (#8277)
This PR improves the generation of `.induct_unfolding` by rewriting
`match` statements more reliably, using the new “congruence equations”
introduced in #8284. Fixes #8195.
2025-05-11 15:26:28 +00:00
Joachim Breitner
dc1a70fa43 feat: congruence equations for matchers (#8284)
This PR adds a new variant of equations for matchers, namely “congruence
equations” that generalize the normal matcher equations. They have
unrestricted left-hand-sides, extra equality assumptions relating the
discriminiants with the patterns and thus prove heterogenous equalities.
In that sense they combine congruence with rewriting. They can be used
to rewrite matcher applications where, due to dependencies, `simp` would
fail to rewrite the discriminants, and will be used when producing the
unfolding induction theorems.
2025-05-11 13:04:59 +00:00
Joachim Breitner
ca73223d4c fix: left-over free variables in splitter (#8285)
This PR fixes “declaration has free variables” errors when generating a
splitter for a match statement with named patterns. Fixes #8274.
2025-05-11 13:04:45 +00:00
Sebastian Ullrich
1f85fd2db8 fix: rfl theorem tracking in the module system (#8215)
We need to track rfl status in both the private and public scope once
defs may become irreducible in the latter.
2025-05-11 07:57:19 +00:00
Leonardo de Moura
e681855428 feat: improve procedure for proving auxiliary type casting equalities in grind (#8281)
This PR improves the module used to prove auxiliary type cast equalities
in `grind`.
2025-05-11 04:15:41 +00:00
Leonardo de Moura
9096eb168d fix: arrow congruence in grind (#8280)
This PR the support for arrows in the congruence closure procedure used
in `grind`.
2025-05-11 03:18:18 +00:00
Cameron Zwarich
575b4786f9 feat: optimize lean_nat_shiftr for scalars (#8268)
This PR optimizes lean_nat_shiftr for scalar operands. The new compiler
converts Nat divisions into right shifts, so this now shows up as hot in
some profiles.
2025-05-11 01:39:59 +00:00
Leonardo de Moura
ddf5512c9a feat: add support for implies_congr in grind (#8275)
This PR ensures the congruence closure in `grind` and find non-dependent
arrow congruences. That is, it can apply the `implies_congr` theorem.
2025-05-10 12:09:45 +00:00
Leonardo de Moura
eabde77d84 fix: improve type-as-hole error message (#8262)
This PR improves the type-as-hole error message. Type-as-hole error for
theorem declarations should not admit the possibility of omitting the
type entirely.

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2025-05-09 22:49:37 +00:00
Rob23oba
5df7770977 feat: consider universes and projections in addPPExplicitToExposeDiff (#8271)
This PR changes `addPPExplicitToExposeDiff` to show universe differences
and to visit into projections, e.g.:
```
error: tactic 'rfl' failed, the left-hand side
  (Test.mk (∀ (x : PUnit.{1}), True)).1
is not definitionally equal to the right-hand side
  (Test.mk (∀ (x : PUnit.{2}), True)).1
```
for
```lean
inductive Test where
  | mk (x : Prop)

example : (Test.mk (∀ _ : PUnit.{1}, True)).1 = (Test.mk (∀ _ : PUnit.{2}, True)).1 := by
  rfl
```
2025-05-09 15:07:50 +00:00
Joachim Breitner
0e49576fe4 feat: guard_msgs to treat trace messages separate (#8267)
This PR makes `#guard_msgs` to treat `trace` messages separate from
`info`, `warning` and `error`. It also introduce the ability to say
`#guard_msgs (pass info`, like `(drop info)` so far, and also adds
`(check info)` as the explicit form of `(info)`, for completeness.

Fixes #8266
2025-05-09 05:44:34 +00:00
Kim Morrison
33afaa061e feat: improve 'apply' unification error message (#8261)
This PR adjusts the error message when `apply` fails to unify. It is
clearer about distinguishing the term being applied and the goal, as
well as distinguishing the "conclusion" of the given term and the term
itself.

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2025-05-08 16:00:42 +00:00
Markus Himmel
1db53b39c4 chore: improve application type mismatch error message (#8264)
This PR rewords the `application type mismatch` error message by more
specifically mentioning that the problem is with the final argument.
This is useful when the same argument is passed to the function multiple
times.

We decided against using a wording which specifically mentions the
"function expression", because users who are not used to currying might
not think of the `f a` in `f a b` as a function.
2025-05-08 15:34:40 +00:00
2146 changed files with 40535 additions and 18852 deletions

View File

@@ -10,11 +10,29 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check awaiting-mathlib label
id: check-awaiting-mathlib-label
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const { labels } = context.payload.pull_request;
if (labels.some(label => label.name == "awaiting-mathlib") && !labels.some(label => label.name == "builds-mathlib")) {
core.setFailed('PR is marked "awaiting-mathlib" but "builds-mathlib" label has not been applied yet by the bot');
const { labels, number: prNumber } = context.payload.pull_request;
const hasAwaiting = labels.some(label => label.name == "awaiting-mathlib");
const hasBreaks = labels.some(label => label.name == "breaks-mathlib");
const hasBuilds = labels.some(label => label.name == "builds-mathlib");
if (hasAwaiting && hasBreaks) {
core.setFailed('PR has both "awaiting-mathlib" and "breaks-mathlib" labels.');
} else if (hasAwaiting && !hasBreaks && !hasBuilds) {
core.info('PR is marked "awaiting-mathlib" but neither "breaks-mathlib" nor "builds-mathlib" labels are present.');
core.setOutput('awaiting', 'true');
}
- name: Wait for mathlib compatibility
if: github.event_name == 'pull_request' && steps.check-awaiting-mathlib-label.outputs.awaiting == 'true'
run: |
echo "::notice title=Awaiting mathlib::PR is marked 'awaiting-mathlib' but neither 'breaks-mathlib' nor 'builds-mathlib' labels are present."
echo "This check will remain in progress until the PR is updated with appropriate mathlib compatibility labels."
# Keep the job running indefinitely to show "in progress" status
while true; do
sleep 3600 # Sleep for 1 hour at a time
done

View File

@@ -105,11 +105,11 @@ jobs:
path: |
.ccache
${{ matrix.name == 'Linux Lake' && 'build/stage1/**/*.trace
build/stage1/**/*.olean
build/stage1/**/*.olean*
build/stage1/**/*.ilean
build/stage1/**/*.c
build/stage1/**/*.c.o*' || '' }}
key: ${{ matrix.name }}-build-v3-${{ github.event.pull_request.head.sha }}
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
${{ matrix.name }}-build-v3
@@ -193,7 +193,7 @@ jobs:
run: |
ulimit -c unlimited # coredumps
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/stage1 -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
if: (matrix.wasm || !matrix.cross) && inputs.check-level >= 1
if: (matrix.wasm || !matrix.cross) && (inputs.check-level >= 1 || matrix.name == 'Linux release')
- name: Test Summary
uses: test-summary/action@v2
with:
@@ -243,7 +243,7 @@ jobs:
path: |
.ccache
${{ matrix.name == 'Linux Lake' && 'build/stage1/**/*.trace
build/stage1/**/*.olean
build/stage1/**/*.olean*
build/stage1/**/*.ilean
build/stage1/**/*.c
build/stage1/**/*.c.o*' || '' }}

View File

@@ -103,6 +103,13 @@ jobs:
echo "Tag ${TAG_NAME} did not match SemVer regex."
fi
- name: Check for custom releases (e.g., not in the main lean repository)
if: startsWith(github.ref, 'refs/tags/') && github.repository != 'leanprover/lean4'
id: set-release-custom
run: |
TAG_NAME="${GITHUB_REF##*/}"
echo "RELEASE_TAG=$TAG_NAME" >> "$GITHUB_OUTPUT"
- name: Set check level
id: set-level
# We do not use github.event.pull_request.labels.*.name here because
@@ -111,7 +118,7 @@ jobs:
run: |
check_level=0
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" ]]; then
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
check_level=2
elif [[ "${{ github.event_name }}" != "pull_request" ]]; then
check_level=1

View File

@@ -1,112 +0,0 @@
name: Nix CI
on:
push:
branches:
- master
tags:
- '*'
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# see ci.yml
configure:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.result }}
steps:
- name: Configure build matrix
id: set-matrix
uses: actions/github-script@v7
with:
script: |
let large = ${{ github.repository == 'leanprover/lean4' }};
let matrix = [
{
"name": "Nix Linux",
"os": large ? "nscloud-ubuntu-22.04-amd64-8x8" : "ubuntu-latest",
}
];
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`);
return matrix;
Build:
needs: [configure]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: nix run .#ciShell -- bash -euxo pipefail {0}
strategy:
matrix:
include: ${{fromJson(needs.configure.outputs.matrix)}}
# complete all jobs
fail-fast: false
name: ${{ matrix.name }}
env:
NIX_BUILD_ARGS: --print-build-logs --fallback
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# the default is to use a virtual merge commit between the PR and master: just use the PR
ref: ${{ github.event.pull_request.head.sha }}
- name: Set Up Nix Cache
uses: actions/cache@v4
with:
path: nix-store-cache
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
${{ matrix.name }}-nix-store-cache
save-always: true
- name: Further Set Up Nix Cache
shell: bash -euxo pipefail {0}
run: |
# Nix seems to mutate the cache, so make a copy
cp -r nix-store-cache nix-store-cache-copy || true
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
extra-sandbox-paths = /nix/var/cache/ccache?
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
- name: Prepare CCache Cache
run: |
sudo mkdir -m0770 -p /nix/var/cache/ccache
sudo chown -R $USER /nix/var/cache/ccache
- name: Setup CCache Cache
uses: actions/cache@v4
with:
path: /nix/var/cache/ccache
key: ${{ matrix.name }}-nix-ccache-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
${{ matrix.name }}-nix-ccache
save-always: true
- name: Further Set Up CCache Cache
run: |
sudo chown -R root:nixbld /nix/var/cache
sudo chmod -R 770 /nix/var/cache
- name: Build
run: |
nix build $NIX_BUILD_ARGS .#cacheRoots -o push-build
- name: Test
run: |
nix build --keep-failed $NIX_BUILD_ARGS .#test -o push-test || (ln -s /tmp/nix-build-*/build/source/src/build ./push-test; false)
- name: Test Summary
uses: test-summary/action@v2
with:
paths: push-test/test-results.xml
if: always()
continue-on-error: true
- name: Rebuild Nix Store Cache
run: |
rm -rf nix-store-cache || true
nix copy ./push-* --to file://$PWD/nix-store-cache?compression=none
- name: Fixup CCache Cache
run: |
sudo chown -R $USER /nix/var/cache

View File

@@ -34,7 +34,7 @@ jobs:
- name: Download artifact from the previous workflow.
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
id: download-artifact
uses: dawidd6/action-download-artifact@v9 # https://github.com/marketplace/actions/download-workflow-artifact
uses: dawidd6/action-download-artifact@v10 # https://github.com/marketplace/actions/download-workflow-artifact
with:
run_id: ${{ github.event.workflow_run.id }}
path: artifacts

View File

@@ -40,34 +40,24 @@ jobs:
run: |
git config --global user.name "Lean stage0 autoupdater"
git config --global user.email "<>"
# Would be nice, but does not work yet:
# https://github.com/DeterminateSystems/magic-nix-cache/issues/39
# This action does not run that often and building runs in a few minutes, so ok for now
#- if: env.should_update_stage0 == 'yes'
# uses: DeterminateSystems/magic-nix-cache-action@v2
- if: env.should_update_stage0 == 'yes'
name: Restore Build Cache
uses: actions/cache/restore@v4
with:
path: nix-store-cache
key: Nix Linux-nix-store-cache-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
Nix Linux-nix-store-cache
- if: env.should_update_stage0 == 'yes'
name: Further Set Up Nix Cache
shell: bash -euxo pipefail {0}
run: |
# Nix seems to mutate the cache, so make a copy
cp -r nix-store-cache nix-store-cache-copy || true
- if: env.should_update_stage0 == 'yes'
name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
extra-conf: |
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
- name: Open Nix shell once
if: env.should_update_stage0 == 'yes'
run: true
shell: 'nix develop -c bash -euxo pipefail {0}'
- name: Set up NPROC
if: env.should_update_stage0 == 'yes'
run: |
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: nix run .#update-stage0-commit
run: cmake --preset release
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: make -j$NPROC -C build/release update-stage0-commit
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: git show --stat
- if: env.should_update_stage0 == 'yes' && github.event_name == 'push'

View File

@@ -7,8 +7,9 @@
/.github/ @kim-em
/RELEASES.md @kim-em
/src/kernel/ @leodemoura
/src/library/compiler/ @zwarich
/src/lake/ @tydeu
/src/Lean/Compiler/ @leodemoura
/src/Lean/Compiler/ @leodemoura @zwarich
/src/Lean/Data/Lsp/ @mhuisi
/src/Lean/Elab/Deriving/ @kim-em
/src/Lean/Elab/Tactic/ @kim-em

View File

@@ -2,20 +2,19 @@ This is the repository for **Lean 4**.
# About
- [Quickstart](https://lean-lang.org/lean4/doc/quickstart.html)
- [Quickstart](https://lean-lang.org/documentation/setup/)
- [Homepage](https://lean-lang.org)
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
- [Documentation Overview](https://lean-lang.org/lean4/doc/)
- [Documentation Overview](https://lean-lang.org/documentation/)
- [Language Reference](https://lean-lang.org/doc/reference/latest/)
- [Release notes](RELEASES.md) starting at v4.0.0-m3
- [Examples](https://lean-lang.org/lean4/doc/examples.html)
- [Examples](https://lean-lang.org/documentation/examples/)
- [External Contribution Guidelines](CONTRIBUTING.md)
- [FAQ](https://lean-lang.org/lean4/doc/faq.html)
# Installation
See [Setting Up Lean](https://lean-lang.org/lean4/doc/setup.html).
See [Setting Up Lean](https://lean-lang.org/documentation/setup/).
# Contributing
@@ -23,4 +22,4 @@ Please read our [Contribution Guidelines](CONTRIBUTING.md) first.
# Building from Source
See [Building Lean](https://lean-lang.org/lean4/doc/make/index.html) (documentation source: [doc/make/index.md](doc/make/index.md)).
See [Building Lean](doc/make/index.md).

10
doc/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Developer Documentation and Examples
This directory contains documentation that describes how to work on
Lean itself, as well as examples that are included in documentation
that's hosted on the Lean website. The `make` directory contains
information on building Lean, and the `dev` directory describes how to
work on Lean.
The [documentation section](https://lean-lang.org/documentation) has
links to documentation that describes how to use Lean itself.

View File

@@ -1,46 +0,0 @@
# Summary
- [What is Lean](./whatIsLean.md)
- [Tour of Lean](./tour.md)
- [Setting Up Lean](./quickstart.md)
- [Extended Setup Notes](./setup.md)
- [Theorem Proving in Lean](./tpil.md)
- [Functional Programming in Lean](fplean.md)
- [Examples](./examples.md)
- [Palindromes](examples/palindromes.lean.md)
- [Binary Search Trees](examples/bintree.lean.md)
- [A Certified Type Checker](examples/tc.lean.md)
- [The Well-Typed Interpreter](examples/interp.lean.md)
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
- [Syntax Examples](./syntax_examples.md)
- [Balanced Parentheses](./syntax_example.md)
- [Arithmetic DSL](./metaprogramming-arith.md)
# Language Manual
- [The Lean Reference Manual](./reference.md)
# Other
- [Frequently Asked Questions](./faq.md)
- [Significant Changes from Lean 3](./lean3changes.md)
- [Syntax Highlighting Lean in LaTeX](./syntax_highlight_in_latex.md)
- [User Widgets](examples/widgets.lean.md)
- [Semantic Highlighting](./semantic_highlighting.md)
# Development
- [Development Guide](./dev/index.md)
- [Building Lean](./make/index.md)
- [Ubuntu Setup](./make/ubuntu.md)
- [macOS Setup](./make/osx-10.9.md)
- [Windows MSYS2 Setup](./make/msys2.md)
- [Windows with WSL](./make/wsl.md)
- [Bootstrapping](./dev/bootstrap.md)
- [Testing](./dev/testing.md)
- [Debugging](./dev/debugging.md)
- [Commit Convention](./dev/commit_convention.md)
- [Release checklist](./dev/release_checklist.md)
- [Building This Manual](./dev/mdbook.md)
- [Foreign Function Interface](./dev/ffi.md)

View File

@@ -1,786 +0,0 @@
@charset "UTF-8";
/*
Copyright © 2019 Clément Pit-Claudel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*******************************/
/* CSS reset for .alectryon-io */
/*******************************/
.content {
/*
Use `initial` instead of `contents` to avoid a browser bug which removes
the element from the accessibility tree.
https://developer.mozilla.org/en-US/docs/Web/CSS/display#display_contents
*/
display: initial;
}
.alectryon-io blockquote {
line-height: inherit;
}
.alectryon-io blockquote:after {
display: none;
}
.alectryon-io label {
display: inline;
font-size: inherit;
margin: 0;
}
.alectryon-io a {
text-decoration: none !important;
font-style: oblique !important;
color: unset;
}
/* Undo <small> and <blockquote>, added to improve RSS rendering. */
.alectryon-io small.alectryon-output,
.alectryon-io small.alectryon-type-info {
font-size: inherit;
}
.alectryon-io blockquote.alectryon-goal,
.alectryon-io blockquote.alectryon-message {
font-weight: normal;
font-size: inherit;
}
/***************/
/* Main styles */
/***************/
.alectryon-coqdoc .doc .code,
.alectryon-coqdoc .doc .comment,
.alectryon-coqdoc .doc .inlinecode,
.alectryon-mref,
.alectryon-block, .alectryon-io,
.alectryon-toggle-label, .alectryon-banner {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
font-size: 0.875em;
font-feature-settings: "COQX" 1 /* Coq ligatures */, "XV00" 1 /* Legacy */, "calt" 1 /* Fallback */;
line-height: initial;
}
.alectryon-io, .alectryon-block, .alectryon-toggle-label, .alectryon-banner {
overflow: visible;
overflow-wrap: break-word;
position: relative;
white-space: pre-wrap;
}
/*
CoqIDE doesn't turn off the unicode bidirectional algorithm (and PG simply
respects the user's `bidi-display-reordering` setting), so don't turn it off
here either. But beware unexpected results like `Definition test_אב := 0.`
.alectryon-io span {
direction: ltr;
unicode-bidi: bidi-override;
}
In any case, make an exception for comments:
.highlight .c {
direction: embed;
unicode-bidi: initial;
}
*/
.alectryon-mref,
.alectryon-mref-marker {
align-self: center;
box-sizing: border-box;
display: inline-block;
font-size: 80%;
font-weight: bold;
line-height: 1;
box-shadow: 0 0 0 1pt black;
padding: 1pt 0.3em;
text-decoration: none;
}
.alectryon-block .alectryon-mref-marker,
.alectryon-io .alectryon-mref-marker {
user-select: none;
margin: -0.25em 0 -0.25em 0.5em;
}
.alectryon-inline .alectryon-mref-marker {
margin: -0.25em 0.15em -0.25em 0.625em; /* 625 = 0.5em / 80% */
}
.alectryon-mref {
color: inherit;
margin: -0.5em 0.25em;
}
.alectryon-goal:target .goal-separator .alectryon-mref-marker,
:target > .alectryon-mref-marker {
animation: blink 0.2s step-start 0s 3 normal none;
background-color: #fcaf3e;
position: relative;
}
@keyframes blink {
50% {
box-shadow: 0 0 0 3pt #fcaf3e, 0 0 0 4pt black;
z-index: 10;
}
}
.alectryon-toggle,
.alectryon-io .alectryon-extra-goal-toggle {
display: none;
}
.alectryon-bubble,
.alectryon-io label,
.alectryon-toggle-label {
cursor: pointer;
}
.alectryon-toggle-label {
display: block;
font-size: 0.8em;
}
.alectryon-io .alectryon-input {
padding: 0.1em 0; /* Enlarge the hitbox slightly to fill interline gaps */
}
.alectryon-io .alectryon-token {
white-space: pre-wrap;
display: inline;
}
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-input {
/* FIXME if keywords were bolder we wouldn't need !important */
font-weight: bold !important; /* Use !important to avoid a * selector */
}
.alectryon-bubble:before,
.alectryon-toggle-label:before,
.alectryon-io label.alectryon-input:after,
.alectryon-io .alectryon-goal > label:before {
border: 1px solid #babdb6;
border-radius: 1em;
box-sizing: border-box;
content: '';
display: inline-block;
font-weight: bold;
height: 0.25em;
margin-bottom: 0.15em;
vertical-align: middle;
width: 0.75em;
}
.alectryon-toggle-label:before,
.alectryon-io .alectryon-goal > label:before {
margin-right: 0.25em;
}
.alectryon-io .alectryon-goal > label:before {
margin-top: 0.125em;
}
.alectryon-io label.alectryon-input {
padding-right: 1em; /* Prevent line wraps before the checkbox bubble */
}
.alectryon-io label.alectryon-input:after {
margin-left: 0.25em;
margin-right: -1em; /* Compensate for the anti-wrapping space */
}
.alectryon-failed {
/* Underlines are broken in Chrome (they reset at each element boundary)… */
/* text-decoration: red wavy underline; */
/* … but it isn't too noticeable with dots */
text-decoration: red dotted underline;
text-decoration-skip-ink: none;
/* Chrome prints background images in low resolution, yielding a blurry underline */
/* background: bottom / 0.3em auto repeat-x url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyLjY0NiAxLjg1MiIgaGVpZ2h0PSI4IiB3aWR0aD0iMTAiPjxwYXRoIGQ9Ik0wIC4yNjVjLjc5NCAwIC41MyAxLjMyMiAxLjMyMyAxLjMyMi43OTQgMCAuNTMtMS4zMjIgMS4zMjMtMS4zMjIiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmVkIiBzdHJva2Utd2lkdGg9Ii41MjkiLz48L3N2Zz4=); */
}
/* Wrapping :hover rules in a media query ensures that tapping a Coq sentence
doesn't trigger its :hover state (otherwise, on mobile, tapping a sentence to
hide its output causes it to remain visible (its :hover state gets triggered.
We only do it for the default style though, since other styles don't put the
output over the main text, so showing too much is not an issue. */
@media (any-hover: hover) {
.alectryon-bubble:hover:before,
.alectryon-toggle-label:hover:before,
.alectryon-io label.alectryon-input:hover:after {
background: #eeeeec;
}
.alectryon-io label.alectryon-input:hover {
text-decoration: underline dotted #babdb6;
text-shadow: 0 0 1px rgb(46, 52, 54, 0.3); /* #2e3436 + opacity */
}
.alectryon-io .alectryon-sentence:hover .alectryon-output,
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper,
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper {
z-index: 2; /* Place hovered goals above .alectryon-sentence.alectryon-target ones */
}
}
.alectryon-toggle:checked + .alectryon-toggle-label:before,
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked + label.alectryon-input:after,
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal > label:before {
background-color: #babdb6;
border-color: #babdb6;
}
/* Disable clicks on sentences when the document-wide toggle is set. */
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input {
cursor: unset;
pointer-events: none;
}
/* Hide individual checkboxes when the document-wide toggle is set. */
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input:after {
display: none;
}
/* .alectryon-output is displayed by toggles, :hover, and .alectryon-target rules */
.alectryon-io .alectryon-output {
box-sizing: border-box;
display: none;
left: 0;
right: 0;
position: absolute;
padding: 0.25em 0;
overflow: visible; /* Let box-shadows overflow */
z-index: 1; /* Default to an index lower than that used by :hover */
}
.alectryon-io .alectryon-type-info-wrapper {
position: absolute;
display: inline-block;
width: 100%;
}
.alectryon-io .alectryon-type-info-wrapper.full-width {
left: 0;
min-width: 100%;
max-width: 100%;
}
.alectryon-io .alectryon-type-info .goal-separator {
height: unset;
margin-top: 0em;
}
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info {
box-sizing: border-box;
bottom: 100%;
position: absolute;
/*padding: 0.25em 0;*/
visibility: hidden;
overflow: visible; /* Let box-shadows overflow */
z-index: 1; /* Default to an index lower than that used by :hover */
white-space: pre-wrap !important;
}
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info .alectryon-goal.alectryon-docstring {
white-space: pre-wrap !important;
}
@media (any-hover: hover) { /* See note above about this @media query */
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) {
display: block;
}
.alectryon-io.output-hidden .alectryon-sentence:hover .alectryon-output:not(:hover) {
display: none !important;
}
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
/*visibility: hidden !important;*/
}
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
visibility: visible;
transition-delay: 0.5s;
}
}
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output {
display: block;
}
/* Indicate active (hovered or targeted) goals with a shadow. */
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals,
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
box-shadow: 2px 2px 2px gray;
}
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-hyps {
display: none;
}
.alectryon-io .alectryon-extra-goals .alectryon-extra-goal-toggle:not(:checked) + .alectryon-goal label.goal-separator hr {
/* Dashes indicate that the hypotheses are hidden */
border-top-style: dashed;
}
/* Show just a small preview of the other goals; this is undone by the
"extra-goal" toggle and by :hover and .alectryon-target in windowed mode. */
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-conclusion {
max-height: 5.2em;
overflow-y: auto;
/* Combining overflow-y: auto with display: inline-block causes extra space
to be added below the box. vertical-align: middle gets rid of it. */
vertical-align: middle;
}
.alectryon-io .alectryon-goals,
.alectryon-io .alectryon-messages {
background: #f6f7f6;
/*border: thin solid #d3d7cf; /* Convenient when pre's background is already #EEE */
display: block;
padding: 0.25em;
}
.alectryon-message::before {
content: '';
float: right;
/* etc/svg/square-bubble-xl.svg */
background: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 3.704 3.704' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill-rule='evenodd' stroke='%23000' stroke-width='.264'%3E%3Cpath d='M.794.934h2.115M.794 1.463h1.455M.794 1.992h1.852'/%3E%3C/g%3E%3Cpath d='M.132.14v2.646h.794v.661l.926-.661h1.72V.14z' fill='none' stroke='%23000' stroke-width='.265'/%3E%3C/svg%3E") top right no-repeat;
height: 14px;
width: 14px;
}
.alectryon-toggle:checked + label + .alectryon-container {
width: unset;
}
/* Show goals when a toggle is set */
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output,
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output {
display: block;
position: static;
width: unset;
background: unset; /* Override the backgrounds set in floating in windowed mode */
padding: 0.25em 0; /* Re-assert so that later :hover rules don't override this padding */
}
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output .goal-hyps,
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output .goal-hyps {
/* Overridden back in windowed style */
flex-flow: row wrap;
justify-content: flex-start;
}
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
display: block;
}
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-hyps {
display: flex;
}
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion {
max-height: unset;
overflow-y: unset;
}
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence > .alectryon-toggle ~ .alectryon-wsp,
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-wsp {
display: none;
}
.alectryon-io .alectryon-messages,
.alectryon-io .alectryon-message,
.alectryon-io .alectryon-goals,
.alectryon-io .alectryon-goal,
.alectryon-io .goal-hyps > span,
.alectryon-io .goal-conclusion {
border-radius: 0.15em;
}
.alectryon-io .alectryon-goal,
.alectryon-io .alectryon-message {
align-items: center;
background: #f6f7f6;
border: 0em;
display: block;
flex-direction: column;
margin: 0.25em;
padding: 0.5em;
position: relative;
}
.alectryon-io .goal-hyps {
align-content: space-around;
align-items: baseline;
display: flex;
flex-flow: column nowrap; /* re-stated in windowed mode */
justify-content: space-around;
/* LATER use a gap property instead of margins once supported */
margin: -0.15em -0.25em; /* -0.15em to cancel the item spacing */
padding-bottom: 0.35em; /* 0.5em-0.15em to cancel the 0.5em of .goal-separator */
}
.alectryon-io .goal-hyps > br {
display: none; /* Only for RSS readers */
}
.alectryon-io .goal-hyps > span,
.alectryon-io .goal-conclusion {
/*background: #eeeeec;*/
display: inline-block;
padding: 0.15em 0.35em;
}
.alectryon-io .goal-hyps > span {
align-items: baseline;
display: inline-flex;
margin: 0.15em 0.25em;
}
.alectryon-block var,
.alectryon-inline var,
.alectryon-io .goal-hyps > span > var {
font-weight: 600;
font-style: unset;
}
.alectryon-io .goal-hyps > span > var {
/* Shrink the list of names, but let it grow as long as space is available. */
flex-basis: min-content;
flex-grow: 1;
}
.alectryon-io .goal-hyps > span b {
font-weight: 600;
margin: 0 0 0 0.5em;
white-space: pre;
}
.alectryon-io .hyp-body,
.alectryon-io .hyp-type {
display: flex;
align-items: baseline;
}
.alectryon-io .goal-separator {
align-items: center;
display: flex;
flex-direction: row;
height: 1em; /* Fixed height to ignore goal name and markers */
margin-top: -0.5em; /* Compensated in .goal-hyps when shown */
}
.alectryon-io .goal-separator hr {
border: none;
border-top: thin solid #555753;
display: block;
flex-grow: 1;
margin: 0;
}
.alectryon-io .goal-separator .goal-name {
font-size: 0.75em;
margin-left: 0.5em;
}
/**********/
/* Banner */
/**********/
.alectryon-banner {
background: #eeeeec;
border: 1px solid #babcbd;
font-size: 0.75em;
padding: 0.25em;
text-align: center;
margin: 1em 0;
}
.alectryon-banner a {
cursor: pointer;
text-decoration: underline;
}
.alectryon-banner kbd {
background: #d3d7cf;
border-radius: 0.15em;
border: 1px solid #babdb6;
box-sizing: border-box;
display: inline-block;
font-family: inherit;
font-size: 0.9em;
height: 1.3em;
line-height: 1.2em;
margin: -0.25em 0;
padding: 0 0.25em;
vertical-align: middle;
}
/**********/
/* Toggle */
/**********/
.alectryon-toggle-label {
margin: 1rem 0;
}
/******************/
/* Floating style */
/******************/
/* If there's space, display goals to the right of the code, not below it. */
@media (min-width: 80rem) {
/* Unlike the windowed case, we don't want to move output blocks to the side
when they are both :checked and -targeted, since it gets confusing as
things jump around; hence the commented-output part of the selector,
which would otherwise increase specificity */
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output,
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
top: 0;
left: 100%;
right: -100%;
padding: 0 0.5em;
position: absolute;
}
.alectryon-floating .alectryon-output {
min-height: 100%;
}
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
background: white; /* Ensure that short goals hide long ones */
}
/* This odd margin-bottom property prevents the sticky div from bumping
against the bottom of its container (.alectryon-output). The alternative
would be enlarging .alectryon-output, but that would cause overflows,
enlarging scrollbars and yielding scrolling towards the bottom of the
page. Doing things this way instead makes it possible to restrict
.alectryon-output to a reasonable size (100%, through top = bottom = 0).
See also https://stackoverflow.com/questions/43909940/. */
/* See note on specificity above */
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output > div,
.alectryon-floating .alectryon-sentence:hover .alectryon-output > div {
margin-bottom: -200%;
position: sticky;
top: 0;
}
.alectryon-floating .alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
.alectryon-floating .alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
margin-bottom: unset; /* Undo the margin */
}
/* Float underneath the current fragment
@media (max-width: 80rem) {
.alectryon-floating .alectryon-output {
top: 100%;
}
} */
}
/********************/
/* Multi-pane style */
/********************/
.alectryon-windowed {
border: 0 solid #2e3436;
box-sizing: border-box;
}
.alectryon-windowed .alectryon-sentence:hover .alectryon-output {
background: white; /* Ensure that short goals hide long ones */
}
.alectryon-windowed .alectryon-output {
position: fixed; /* Overwritten by the :checked rules */
}
/* See note about specificity below */
.alectryon-windowed .alectryon-sentence:hover .alectryon-output,
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
padding: 0.5em;
overflow-y: auto; /* Windowed contents may need to scroll */
}
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals {
box-shadow: none; /* A shadow is unnecessary here and incompatible with overflow-y set to auto */
}
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .goal-hyps {
/* Restated to override the :checked style */
flex-flow: column nowrap;
justify-content: space-around;
}
.alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-extra-goals .alectryon-goal .goal-conclusion
/* Like .alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion */ {
max-height: unset;
overflow-y: unset;
}
.alectryon-windowed .alectryon-output > div {
display: flex; /* Put messages after goals */
flex-direction: column-reverse;
}
/*********************/
/* Standalone styles */
/*********************/
.alectryon-standalone {
font-family: 'IBM Plex Serif', 'PT Serif', 'Merriweather', 'DejaVu Serif', serif;
line-height: 1.5;
}
@media screen and (min-width: 50rem) {
html.alectryon-standalone {
/* Prevent flickering when hovering a block causes scrollbars to appear. */
margin-left: calc(100vw - 100%);
margin-right: 0;
}
}
/* Coqdoc */
.alectryon-coqdoc .doc .code,
.alectryon-coqdoc .doc .inlinecode,
.alectryon-coqdoc .doc .comment {
display: inline;
}
.alectryon-coqdoc .doc .comment {
color: #eeeeec;
}
.alectryon-coqdoc .doc .paragraph {
height: 0.75em;
}
/* Centered, Floating */
.alectryon-standalone .alectryon-centered,
.alectryon-standalone .alectryon-floating {
max-width: 50rem;
margin: auto;
}
@media (min-width: 80rem) {
.alectryon-standalone .alectryon-floating {
max-width: 80rem;
}
.alectryon-standalone .alectryon-floating > * {
width: 50%;
margin-left: 0;
}
}
/* Windowed */
.alectryon-standalone .alectryon-windowed {
display: block;
margin: 0;
overflow-y: auto;
position: absolute;
padding: 0 1em;
}
.alectryon-standalone .alectryon-windowed > * {
/* Override properties of docutils_basic.css */
margin-left: 0;
max-width: unset;
}
.alectryon-standalone .alectryon-windowed .alectryon-io {
box-sizing: border-box;
width: 100%;
}
/* No need to predicate the :hover rules below on :not(:checked), since left,
right, top, and bottom will be inactived by the :checked rules setting
position to static */
/* Specificity: We want the output to stay inline when hovered while unfolded
(:checked), but we want it to move when it's targeted (i.e. when the user
is browsing goals one by one using the keyboard, in which case we want to
goals to appear in consistent locations). The selectors below ensure
that :hover < :checked < -targeted in terms of specificity. */
/* LATER: Reimplement this stuff with CSS variables */
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
position: fixed;
}
@media screen and (min-width: 60rem) {
.alectryon-standalone .alectryon-windowed {
border-right-width: thin;
bottom: 0;
left: 0;
right: 50%;
top: 0;
}
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
bottom: 0;
left: 50%;
right: 0;
top: 0;
}
}
@media screen and (max-width: 60rem) {
.alectryon-standalone .alectryon-windowed {
border-bottom-width: 1px;
bottom: 40%;
left: 0;
right: 0;
top: 0;
}
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
bottom: 0;
left: 0;
right: 0;
top: 60%;
}
}

View File

@@ -1,190 +0,0 @@
var Alectryon;
(function(Alectryon) {
(function (slideshow) {
function anchor(sentence) { return "#" + sentence.id; }
function current_sentence() { return slideshow.sentences[slideshow.pos]; }
function unhighlight() {
var sentence = current_sentence();
if (sentence) sentence.classList.remove("alectryon-target");
slideshow.pos = -1;
}
function highlight(sentence) {
sentence.classList.add("alectryon-target");
}
function scroll(sentence) {
// Put the top of the current fragment close to the top of the
// screen, but scroll it out of view if showing it requires pushing
// the sentence past half of the screen. If sentence is already in
// a reasonable position, don't move.
var parent = sentence.parentElement;
/* We want to scroll the whole document, so start at root… */
while (parent && !parent.classList.contains("alectryon-root"))
parent = parent.parentElement;
/* … and work up from there to find a scrollable element.
parent.scrollHeight can be greater than parent.clientHeight
without showing scrollbars, so we add a 10px buffer. */
while (parent && parent.scrollHeight <= parent.clientHeight + 10)
parent = parent.parentElement;
/* <body> and <html> elements can have their client rect overflow
* the window if their height is unset, so scroll the window
* instead */
if (parent && (parent.nodeName == "BODY" || parent.nodeName == "HTML"))
parent = null;
var rect = function(e) { return e.getBoundingClientRect(); };
var parent_box = parent ? rect(parent) : { y: 0, height: window.innerHeight },
sentence_y = rect(sentence).y - parent_box.y,
fragment_y = rect(sentence.parentElement).y - parent_box.y;
// The assertion below sometimes fails for the first element in a block.
// console.assert(sentence_y >= fragment_y);
if (sentence_y < 0.1 * parent_box.height ||
sentence_y > 0.7 * parent_box.height) {
(parent || window).scrollBy(
0, Math.max(sentence_y - 0.5 * parent_box.height,
fragment_y - 0.1 * parent_box.height));
}
}
function highlighted(pos) {
return slideshow.pos == pos;
}
function navigate(pos, inhibitScroll) {
unhighlight();
slideshow.pos = Math.min(Math.max(pos, 0), slideshow.sentences.length - 1);
var sentence = current_sentence();
highlight(sentence);
if (!inhibitScroll)
scroll(sentence);
}
var keys = {
PAGE_UP: 33,
PAGE_DOWN: 34,
ARROW_UP: 38,
ARROW_DOWN: 40,
h: 72, l: 76, p: 80, n: 78
};
function onkeydown(e) {
e = e || window.event;
if (e.ctrlKey || e.metaKey) {
if (e.keyCode == keys.ARROW_UP)
slideshow.previous();
else if (e.keyCode == keys.ARROW_DOWN)
slideshow.next();
else
return;
} else {
// if (e.keyCode == keys.PAGE_UP || e.keyCode == keys.p || e.keyCode == keys.h)
// slideshow.previous();
// else if (e.keyCode == keys.PAGE_DOWN || e.keyCode == keys.n || e.keyCode == keys.l)
// slideshow.next();
// else
return;
}
e.preventDefault();
}
function start() {
slideshow.navigate(0);
}
function toggleHighlight(idx) {
if (highlighted(idx))
unhighlight();
else
navigate(idx, true);
}
function handleClick(evt) {
if (evt.ctrlKey || evt.metaKey) {
var sentence = evt.currentTarget;
// Ensure that the goal is shown on the side, not inline
var checkbox = sentence.getElementsByClassName("alectryon-toggle")[0];
if (checkbox)
checkbox.checked = false;
toggleHighlight(sentence.alectryon_index);
evt.preventDefault();
}
}
function init() {
document.onkeydown = onkeydown;
slideshow.pos = -1;
slideshow.sentences = Array.from(document.getElementsByClassName("alectryon-sentence"));
slideshow.sentences.forEach(function (s, idx) {
s.addEventListener('click', handleClick, false);
s.alectryon_index = idx;
});
}
slideshow.start = start;
slideshow.end = unhighlight;
slideshow.navigate = navigate;
slideshow.next = function() { navigate(slideshow.pos + 1); };
slideshow.previous = function() { navigate(slideshow.pos + -1); };
window.addEventListener('DOMContentLoaded', init);
})(Alectryon.slideshow || (Alectryon.slideshow = {}));
(function (styles) {
var styleNames = ["centered", "floating", "windowed"];
function className(style) {
return "alectryon-" + style;
}
function setStyle(style) {
var root = document.getElementsByClassName("alectryon-root")[0];
styleNames.forEach(function (s) {
root.classList.remove(className(s)); });
root.classList.add(className(style));
}
function init() {
var banner = document.getElementsByClassName("alectryon-banner")[0];
if (banner) {
banner.append(" Style: ");
styleNames.forEach(function (styleName, idx) {
var s = styleName;
var a = document.createElement("a");
a.onclick = function() { setStyle(s); };
a.append(styleName);
if (idx > 0) banner.append("; ");
banner.appendChild(a);
});
banner.append(".");
}
}
window.addEventListener('DOMContentLoaded', init);
styles.setStyle = setStyle;
})(Alectryon.styles || (Alectryon.styles = {}));
})(Alectryon || (Alectryon = {}));
function setHidden(elements, isVisible, token) {
for (let i = 0; i < elements.length; i++) {
if (isVisible) {
elements[i].classList.remove(token)
} else {
elements[i].classList.add(token)
}
}
}
function toggleShowTypes(checkbox) {
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "type-info-hidden")
}
function toggleShowGoals(checkbox) {
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "output-hidden")
}

View File

@@ -1,14 +0,0 @@
Lean binary distribution
------------------------
The binary distribution package contains:
- Lean executable (located in the sub-directory bin)
- Standard library (located in the sub-directory lib/lean/library)
Assuming you are in the same directory this file is located,
the following command executes a simple set of examples
% bin/lean examples/ex.lean
For more information on Lean and supported editors, please see https://lean-lang.org/documentation/.

View File

@@ -1,21 +0,0 @@
[book]
authors = ["Leonardo de Moura", "Sebastian Ullrich"]
language = "en"
multilingual = false
src = "."
title = "Lean Documentation Overview"
[build]
build-dir = "out"
[output.html]
git-repository-url = "https://github.com/leanprover/lean4"
additional-css = ["alectryon.css", "pygments.css"]
additional-js = ["alectryon.js"]
[output.html.fold]
enable = true
level = 0
[output.html.playground.boring-prefixes]
lean = "# "

View File

@@ -1 +0,0 @@
# Booleans

View File

@@ -1,885 +0,0 @@
# Declarations
-- TODO (fix)
Declaration Names
=================
A declaration name is a hierarchical [identifier](lexical_structure.md#identifiers) that is interpreted relative to the current namespace as well as (during lookup) to the set of open namespaces.
```lean
namespace A
opaque B.c : Nat
#print B.c -- opaque A.B.c : Nat
end A
#print A.B.c -- opaque A.B.c : Nat
open A
#print B.c -- opaque A.B.c : Nat
```
Declaration names starting with an underscore are reserved for internal use. Names starting with the special atomic name ``_root_`` are interpreted as absolute names.
```lean
opaque a : Nat
namespace A
opaque a : Int
#print _root_.a -- opaque a : Nat
#print A.a -- opaque A.a : Int
end A
```
Contexts and Telescopes
=======================
When processing user input, Lean first parses text to a raw expression format. It then uses background information and type constants to disambiguate overloaded symbols and infer implicit arguments, resulting in a fully-formed expression. This process is known as *elaboration*.
As hinted in [Expression Syntax](expressions.md#expression_syntax),
expressions are parsed and elaborated with respect to an *environment*
and a *local context*. Roughly speaking, an environment represents the
state of Lean at the point where an expression is parsed, including
previously declared axioms, constants, definitions, and theorems. In a
given environment, a *local context* consists of a sequence ``(a₁ :
α₁) (a₂ : α₂) ... (aₙ : αₙ)`` where each ``aᵢ`` is a name denoting a
local constant and each ``αᵢ`` is an expression of type ``Sort u`` for
some ``u`` which can involve elements of the environment and the local
constants ``aⱼ`` for ``j < i``.
Intuitively, a local context is a list of variables that are held constant while an expression is being elaborated. Consider the following
```lean
def f (a b : Nat) : Nat → Nat := fun c => a + (b + c)
```
Here the expression ``fun c => a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat)`` and the expression ``a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat) (c : Nat)``. If you replace the expression ``a + (b + c)`` with an underscore, the error message from Lean will include the current *goal*:
```
a b c : Nat
⊢ Nat
```
Here ``a b c : Nat`` indicates the local context, and the second ``Nat`` indicates the expected type of the result.
A *context* is sometimes called a *telescope*, but the latter is used more generally to include a sequence of declarations occurring relative to a given context. For example, relative to the context ``(a₁ : α₁) (a₂ : α₂) ... (aₙ : αₙ)``, the types ``βᵢ`` in a telescope ``(b₁ : β₁) (b₂ : β₂) ... (bₙ : βₙ)`` can refer to ``a₁, ..., aₙ``. Thus a context can be viewed as a telescope relative to the empty context.
Telescopes are often used to describe a list of arguments, or parameters, to a declaration. In such cases, it is often notationally convenient to let ``(a : α)`` stand for a telescope rather than just a single argument. In general, the annotations described in [Implicit Arguments](expressions.md#implicit_arguments) can be used to mark arguments as implicit.
.. _basic_declarations:
Basic Declarations
==================
Lean provides ways of adding new objects to the environment. The following provide straightforward ways of declaring new objects:
* ``axiom c : α`` : declare a constant named ``c`` of type ``α``, it is postulating that `α` is not an empty type.
* ``def c : α := v`` : defines ``c`` to denote ``v``, which should have type ``α``.
* ``theorem c : p := v`` : similar to ``def``, but intended to be used when ``p`` is a proposition.
* ``opaque c : α (:= v)?`` : declares a opaque constant named ``c`` of type ``α``, the optional value `v` is must have type `α`
and can be viewed as a certificate that ``α`` is not an empty type. If the value is not provided, Lean tries to find one
using a procedure based on type class resolution. The value `v` is hidden from the type checker. You can assume that
Lean "forgets" `v` after type checking this kind of declaration.
It is sometimes useful to be able to simulate a definition or theorem without naming it or adding it to the environment.
* ``example : α := t`` : elaborates ``t`` and checks that it has sort ``α`` (often a proposition), without adding it to the environment.
In ``def``, the type (``α`` or ``p``, respectively) can be omitted when it can be inferred by Lean. Constants declared with ``theorem`` are marked as ``irreducible``.
Any of ``def``, ``theorem``, ``axiom``, or ``example`` can take a list of arguments (that is, a context) before the colon. If ``(a : α)`` is a context, the definition ``def foo (a : α) : β := t``
is interpreted as ``def foo : (a : α) → β := fun a : α => t``. Similarly, a theorem ``theorem foo (a : α) : p := t`` is interpreted as ``theorem foo : ∀ a : α, p := fun a : α => t``.
```lean
opaque c : Nat
opaque d : Nat
axiom cd_eq : c = d
def foo : Nat := 5
def bar := 6
def baz (x y : Nat) (s : List Nat) := [x, y] ++ s
theorem foo_eq_five : foo = 5 := rfl
theorem baz_theorem (x y : Nat) : baz x y [] = [x, y] := rfl
example (x y : Nat) : baz x y [] = [x, y] := rfl
```
Inductive Types
===============
Lean's axiomatic foundation allows users to declare arbitrary
inductive families, following the pattern described by [Dybjer]_. To
make the presentation more manageable, we first describe inductive
*types*, and then describe the generalization to inductive *families*
in the next section. The declaration of an inductive type has the
following form:
```
inductive Foo (a : α) where
| constructor₁ : (b : β₁) → Foo a
| constructor₂ : (b : β₂) → Foo a
...
| constructorₙ : (b : βₙ) → Foo a
```
Here ``(a : α)`` is a context and each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``Foo``, subject to the following constraints.
Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``. Each argument in the telescope is either *nonrecursive* or *recursive*.
- An argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if it ``βᵢⱼ`` of the form ``Π (d : δ), foo`` where ``(d : δ)`` is a telescope which does not refer to ``foo`` or any nonrecursive arguments.
The inductive type ``foo`` represents a type that is freely generated by the constructors. Each constructor can take arbitrary data and facts as arguments (the nonrecursive arguments), as well as indexed sequences of elements of ``foo`` that have been previously constructed (the recursive arguments). In set theoretic models, such sets can be represented by well-founded trees labeled by the constructor data, or they can defined using other transfinite or impredicative means.
The declaration of the type ``foo`` as above results in the addition of the following constants to the environment:
- the *type former* ``foo : Π (a : α), Sort u``
- for each ``i``, the *constructor* ``foo.constructorᵢ : Π (a : α) (b : βᵢ), foo a``
- the *eliminator* ``foo.rec``, which takes arguments
+ ``(a : α)`` (the parameters)
+ ``{C : foo a → Type u}`` (the *motive* of the elimination)
+ for each ``i``, the *minor premise* corresponding to ``constructorᵢ``
+ ``(x : foo)`` (the *major premise*)
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
+ ``(b : βᵢ)`` (the arguments to the constructor)
+ an argument of type ``Π (d : δ), C (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), foo`` (the recursive values of the function being defined)
and returns an element of ``C (constructorᵢ a b)``, the intended value of the function at ``constructorᵢ a b``.
The eliminator represents a principle of recursion: to construct an element of ``C x`` where ``x : foo a``, it suffices to consider each of the cases where ``x`` is of the form ``constructorᵢ a b`` and to provide an auxiliary construction in each case. In the case where some of the arguments to ``constructorᵢ`` are recursive, we can assume that we have already constructed values of ``C y`` for each value ``y`` constructed at an earlier stage.
Under the propositions-as-type correspondence, when ``C x`` is an element of ``Prop``, the eliminator represents a principle of induction. In order to show ``∀ x, C x``, it suffices to show that ``C`` holds for each constructor, under the inductive hypothesis that it holds for all recursive inputs to the constructor.
The eliminator and constructors satisfy the following identities, in which all the arguments are shown explicitly. Suppose we set ``F := foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction:
```
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
```
where the ellipses include one entry for each recursive argument.
Below are some common examples of inductive types, many of which are defined in the core library.
```lean
namespace Hide
universe u v
-- BEGIN
inductive Empty : Type
inductive Unit : Type
| unit : Unit
inductive Bool : Type
| false : Bool
| true : Bool
inductive Prod (α : Type u) (β : Type v) : Type (max u v)
| mk : α → β → Prod α β
inductive Sum (α : Type u) (β : Type v)
| inl : α → Sum α β
| inr : β → Sum α β
inductive Sigma (α : Type u) (β : α → Type v)
| mk : (a : α) → β a → Sigma α β
inductive false : Prop
inductive True : Prop
| trivial : True
inductive And (p q : Prop) : Prop
| intro : p → q → And p q
inductive Or (p q : Prop) : Prop
| inl : p → Or p q
| inr : q → Or p q
inductive Exists (α : Type u) (p : α → Prop) : Prop
| intro : ∀ x : α, p x → Exists α p
inductive Subtype (α : Type u) (p : α → Prop) : Type u
| intro : ∀ x : α, p x → Subtype α p
inductive Nat : Type
| zero : Nat
| succ : Nat → Nat
inductive List (α : Type u)
| nil : List α
| cons : α → List α → List α
-- full binary tree with nodes and leaves labeled from α
inductive BinTree (α : Type u)
| leaf : α → BinTree α
| node : BinTree αα → BinTree α → BinTree α
-- every internal node has subtrees indexed by Nat
inductive CBT (α : Type u)
| leaf : α → CBT α
| node : (Nat → CBT α) → CBT α
-- END
end Hide
```
Note that in the syntax of the inductive definition ``Foo``, the context ``(a : α)`` is left implicit. In other words, constructors and recursive arguments are written as though they have return type ``Foo`` rather than ``Foo a``.
Elements of the context ``(a : α)`` can be marked implicit as described in [Implicit Arguments](#implicit.md#implicit_arguments). These annotations bear only on the type former, ``Foo``. Lean uses a heuristic to determine which arguments to the constructors should be marked implicit, namely, an argument is marked implicit if it can be inferred from the type of a subsequent argument. If the annotation ``{}`` appears after the constructor, a argument is marked implicit if it can be inferred from the type of a subsequent argument *or the return type*. For example, it is useful to let ``nil`` denote the empty list of any type, since the type can usually be inferred in the context in which it appears. These heuristics are imperfect, and you may sometimes wish to define your own constructors in terms of the default ones. In that case, use the ``[match_pattern]`` [attribute](TODO: missing link) to ensure that these will be used appropriately by the [Equation Compiler](#the-equation-compiler).
There are restrictions on the universe ``u`` in the return type ``Sort u`` of the type former. There are also restrictions on the universe ``u`` in the return type ``Sort u`` of the motive of the eliminator. These will be discussed in the next section in the more general setting of inductive families.
Lean allows some additional syntactic conveniences. You can omit the return type of the type former, ``Sort u``, in which case Lean will infer the minimal possible nonzero value for ``u``. As with function definitions, you can list arguments to the constructors before the colon. In an enumerated type (that is, one where the constructors have no arguments), you can also leave out the return type of the constructors.
```lean
namespace Hide
universe u
-- BEGIN
inductive Weekday
| sunday | monday | tuesday | wednesday
| thursday | friday | saturday
inductive Nat
| zero
| succ (n : Nat) : Nat
inductive List (α : Type u)
| nil : List α
| cons (a : α) (l : List α) : List α
@[match_pattern]
def List.nil' (α : Type u) : List α := List.nil
def length {α : Type u} : List α → Nat
| (List.nil' _) => 0
| (List.cons a l) => 1 + length l
-- END
end Hide
```
The type former, constructors, and eliminator are all part of Lean's axiomatic foundation, which is to say, they are part of the trusted kernel. In addition to these axiomatically declared constants, Lean automatically defines some additional objects in terms of these, and adds them to the environment. These include the following:
- ``Foo.recOn`` : a variant of the eliminator, in which the major premise comes first
- ``Foo.casesOn`` : a restricted version of the eliminator which omits any recursive calls
- ``Foo.noConfusionType``, ``Foo.noConfusion`` : functions which witness the fact that the inductive type is freely generated, i.e. that the constructors are injective and that distinct constructors produce distinct objects
- ``Foo.below``, ``Foo.ibelow`` : functions used by the equation compiler to implement structural recursion
- ``instance : SizeOf Foo`` : a measure which can be used for well-founded recursion
Note that it is common to put definitions and theorems related to a datatype ``foo`` in a namespace of the same name. This makes it possible to use projection notation described in [Structures](struct.md#structures) and [Namespaces](namespaces.md#namespaces).
```lean
namespace Hide
universe u
-- BEGIN
inductive Nat
| zero
| succ (n : Nat) : Nat
#check Nat
#check @Nat.rec
#check Nat.zero
#check Nat.succ
#check @Nat.recOn
#check @Nat.casesOn
#check @Nat.noConfusionType
#check @Nat.noConfusion
#check @Nat.brecOn
#check Nat.below
#check Nat.ibelow
#check Nat._sizeOf_1
-- END
end Hide
```
.. _inductive_families:
Inductive Families
==================
In fact, Lean implements a slight generalization of the inductive types described in the previous section, namely, inductive *families*. The declaration of an inductive family in Lean has the following form:
```
inductive Foo (a : α) : Π (c : γ), Sort u
| constructor₁ : Π (b : β₁), Foo t₁
| constructor₂ : Π (b : β₂), Foo t₂
...
| constructorₙ : Π (b : βₙ), Foo tₙ
```
Here ``(a : α)`` is a context, ``(c : γ)`` is a telescope in context ``(a : α)``, each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``(Foo : Π (c : γ), Sort u)`` subject to the constraints below, and each ``tᵢ`` is a tuple of terms in the context ``(a : α) (b : βᵢ)`` having the types ``γ``. Instead of defining a single inductive type ``Foo a``, we are now defining a family of types ``Foo a c`` indexed by elements ``c : γ``. Each constructor, ``constructorᵢ``, places its result in the type ``Foo a tᵢ``, the member of the family with index ``tᵢ``.
The modifications to the scheme in the previous section are straightforward. Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``.
- As before, an argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``Foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s`` where ``(d : δ)`` is a telescope which does not refer to ``Foo`` or any nonrecursive arguments and ``s`` is a tuple of terms in context ``(a : α)`` and the previous nonrecursive ``bⱼ``'s with types ``γ``.
The declaration of the type ``Foo`` as above results in the addition of the following constants to the environment:
- the *type former* ``Foo : Π (a : α) (c : γ), Sort u``
- for each ``i``, the *constructor* ``Foo.constructorᵢ : Π (a : α) (b : βᵢ), Foo a tᵢ``
- the *eliminator* ``Foo.rec``, which takes arguments
+ ``(a : α)`` (the parameters)
+ ``{C : Π (c : γ), Foo a c → Type u}`` (the motive of the elimination)
+ for each ``i``, the minor premise corresponding to ``constructorᵢ``
+ ``(x : Foo a)`` (the major premise)
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
+ ``(b : βᵢ)`` (the arguments to the constructor)
+ an argument of type ``Π (d : δ), C s (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s``
and returns an element of ``C tᵢ (constructorᵢ a b)``.
Suppose we set ``F := Foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction, as before:
```
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
```
where the ellipses include one entry for each recursive argument.
The following are examples of inductive families.
```lean
namespace Hide
universe u
-- BEGIN
inductive Vector (α : Type u) : Nat → Type u
| nil : Vector 0
| succ : Π n, Vector n → Vector (n + 1)
-- 'IsProd s n' means n is a product of elements of s
inductive IsProd (s : Set Nat) : Nat → Prop
| base : ∀ n ∈ s, IsProd n
| step : ∀ m n, IsProd m → IsProd n → IsProd (m * n)
inductive Eq {α : Sort u} (a : α) : α → Prop
| refl : Eq a
-- END
end Hide
```
We can now describe the constraints on the return type of the type former, ``Sort u``. We can always take ``u`` to be ``0``, in which case we are defining an inductive family of propositions. If ``u`` is nonzero, however, it must satisfy the following constraint: for each type ``βᵢⱼ : Sort v`` occurring in the constructors, we must have ``u ≥ v``. In the set-theoretic interpretation, this ensures that the universe in which the resulting type resides is large enough to contain the inductively generated family, given the number of distinctly-labeled constructors. The restriction does not hold for inductively defined propositions, since these contain no data.
Putting an inductive family in ``Prop``, however, does impose a restriction on the eliminator. Generally speaking, for an inductive family in ``Prop``, the motive in the eliminator is required to be in ``Prop``. But there is an exception to this rule: you are allowed to eliminate from an inductively defined ``Prop`` to an arbitrary ``Sort`` when there is only one constructor, and each argument to that constructor is either in ``Prop`` or an index. The intuition is that in this case the elimination does not make use of any information that is not already given by the mere fact that the type of argument is inhabited. This special case is known as *singleton elimination*.
.. _mutual_and_nested_inductive_definitions:
Mutual and Nested Inductive Definitions
=======================================
Lean supports two generalizations of the inductive families described above, namely, *mutual* and *nested* inductive definitions. These are *not* implemented natively in the kernel. Rather, the definitions are compiled down to the primitive inductive types and families.
The first generalization allows for multiple inductive types to be defined simultaneously.
```
mutual
inductive Foo (a : α) : Π (c : γ₁), Sort u
| constructor₁₁ : Π (b : β₁₁), Foo a t₁₁
| constructor₁₂ : Π (b : β₁₂), Foo a t₁₂
...
| constructor₁ₙ : Π (b : β₁ₙ), Foo a t₁ₙ
inductive Bar (a : α) : Π (c : γ₂), Sort u
| constructor₂₁ : Π (b : β₂₁), Bar a t₂₁
| constructor₂₂ : Π (b : β₂₂), Bar a t₂₂
...
| constructor₂ₘ : Π (b : β₂ₘ), Bar a t₂ₘ
end
```
Here the syntax is shown for defining two inductive families, ``Foo`` and ``Bar``, but any number is allowed. The restrictions are almost the same as for ordinary inductive families. For example, each ``(b : βᵢⱼ)`` is a telescope relative to the context ``(a : α)``. The difference is that the constructors can now have recursive arguments whose return types are any of the inductive families currently being defined, in this case ``Foo`` and ``Bar``. Note that all of the inductive definitions share the same parameters ``(a : α)``, though they may have different indices.
A mutual inductive definition is compiled down to an ordinary inductive definition using an extra finite-valued index to distinguish the components. The details of the internal construction are meant to be hidden from most users. Lean defines the expected type formers ``Foo`` and ``Bar`` and constructors ``constructorᵢⱼ`` from the internal inductive definition. There is no straightforward elimination principle, however. Instead, Lean defines an appropriate ``sizeOf`` measure, meant for use with well-founded recursion, with the property that the recursive arguments to a constructor are smaller than the constructed value.
The second generalization relaxes the restriction that in the recursive definition of ``Foo``, ``Foo`` can only occur strictly positively in the type of any of its recursive arguments. Specifically, in a nested inductive definition, ``Foo`` can appear as an argument to another inductive type constructor, so long as the corresponding parameter occurs strictly positively in the constructors for *that* inductive type. This process can be iterated, so that additional type constructors can be applied to those, and so on.
A nested inductive definition is compiled down to an ordinary inductive definition using a mutual inductive definition to define copies of all the nested types simultaneously. Lean then constructs isomorphisms between the mutually defined nested types and their independently defined counterparts. Once again, the internal details are not meant to be manipulated by users. Rather, the type former and constructors are made available and work as expected, while an appropriate ``sizeOf`` measure is generated for use with well-founded recursion.
```lean
universe u
-- BEGIN
mutual
inductive Even : Nat → Prop
| even_zero : Even 0
| even_succ : ∀ n, Odd n → Even (n + 1)
inductive Odd : Nat → Prop
| odd_succ : ∀ n, Even n → Odd (n + 1)
end
inductive Tree (α : Type u)
| mk : α → List (Tree α) → Tree α
inductive DoubleTree (α : Type u)
| mk : α → List (DoubleTree α) × List (DoubleTree α) → DoubleTree α
-- END
```
.. _the_equation_compiler:
The Equation Compiler
=====================
The equation compiler takes an equational description of a function or proof and tries to define an object meeting that specification. It expects input with the following syntax:
```
def foo (a : α) : Π (b : β), γ
| [patterns₁] => t₁
...
| [patternsₙ] => tₙ
```
Here ``(a : α)`` is a telescope, ``(b : β)`` is a telescope in the context ``(a : α)``, and ``γ`` is an expression in the context ``(a : α) (b : β)`` denoting a ``Type`` or a ``Prop``.
Each ``patternsᵢ`` is a sequence of patterns of the same length as ``(b : β)``. A pattern is either:
- a variable, denoting an arbitrary value of the relevant type,
- an underscore, denoting a *wildcard* or *anonymous variable*,
- an inaccessible term (see below), or
- a constructor for the inductive type of the corresponding argument, applied to a sequence of patterns.
In the last case, the pattern must be enclosed in parentheses.
Each term ``tᵢ`` is an expression in the context ``(a : α)`` together with the variables introduced on the left-hand side of the token ``=>``. The term ``tᵢ`` can also include recursive calls to ``foo``, as described below. The equation compiler does case splitting on the variables ``(b : β)`` as necessary to match the patterns, and defines ``foo`` so that it has the value ``tᵢ`` in each of the cases. In ideal circumstances (see below), the equations hold definitionally. Whether they hold definitionally or only propositionally, the equation compiler proves the relevant equations and assigns them internal names. They are accessible by the ``rewrite`` and ``simp`` tactics under the name ``foo`` (see [Rewrite](tactics.md#rewrite) and _[TODO: where is simplifier tactic documented?]_. If some of the patterns overlap, the equation compiler interprets the definition so that the first matching pattern applies in each case. Thus, if the last pattern is a variable, it covers all the remaining cases. If the patterns that are presented do not cover all possible cases, the equation compiler raises an error.
When identifiers are marked with the ``[match_pattern]`` attribute, the equation compiler unfolds them in the hopes of exposing a constructor. For example, this makes it possible to write ``n+1`` and ``0`` instead of ``Nat.succ n`` and ``Nat.zero`` in patterns.
For a nonrecursive definition involving case splits, the defining equations will hold definitionally. With inductive types like ``Char``, ``String``, and ``Fin n``, a case split would produce definitions with an inordinate number of cases. To avoid this, the equation compiler uses ``if ... then ... else`` instead of ``casesOn`` when defining the function. In this case, the defining equations hold definitionally as well.
```lean
open Nat
def sub2 : Nat → Nat
| zero => 0
| succ zero => 0
| succ (succ a) => a
def bar : Nat → List Nat → Bool → Nat
| 0, _, false => 0
| 0, b :: _, _ => b
| 0, [], true => 7
| a+1, [], false => a
| a+1, [], true => a + 1
| a+1, b :: _, _ => a + b
def baz : Char → Nat
| 'A' => 1
| 'B' => 2
| _ => 3
```
The case where patterns are matched against an argument whose type is an inductive family is known as *dependent pattern matching*. This is more complicated, because the type of the function being defined can impose constraints on the patterns that are matched. In this case, the equation compiler will detect inconsistent cases and rule them out.
```lean
universe u
inductive Vector (α : Type u) : Nat → Type u
| nil : Vector α 0
| cons : α → Vector α n → Vector α (n+1)
namespace Vector
def head : Vector α (n+1) → α
| cons h t => h
def tail : Vector α (n+1) → Vector α n
| cons h t => t
def map (f : α → β → γ) : Vector α n → Vector β n → Vector γ n
| nil, nil => nil
| cons a va, cons b vb => cons (f a b) (map f va vb)
end Vector
```
.. _recursive_functions:
Recursive functions
===================
Lean must ensure that a recursive function terminates, for which there are two strategies: _structural recursion_, in which all recursive calls are made on smaller parts of the input data, and _well-founded recursion_, in which recursive calls are justified by showing that arguments to recursive calls are smaller according to some other measure.
Structural recursion
--------------------
If the definition of a function contains recursive calls, Lean first tries to interpret the definition as a structural recursion. In order for that to succeed, the recursive arguments must be subterms of the corresponding arguments on the left-hand side.
The function is then defined using a *course of values* recursion, using automatically generated functions ``below`` and ``brec`` in the namespace corresponding to the inductive type of the recursive argument. In this case the defining equations hold definitionally, possibly with additional case splits.
```lean
namespace Hide
-- BEGIN
def fib : Nat → Nat
| 0 => 1
| 1 => 1
| (n+2) => fib (n+1) + fib n
def append {α : Type} : List α → List α → List α
| [], l => l
| h::t, l => h :: append t l
example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] => rfl
-- END
end Hide
```
Well-founded recursion
---------------------
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``SizeOf`` for the type of each argument, and then tries to find a permutation of the arguments such that each recursive call is decreasing under the lexicographic order with respect to ``sizeOf`` measures. Lean uses information in the local context, so you can often provide the relevant proof manually using ``have`` in the body of the definition.
In the case of well-founded recursion, the equation used to declare the function holds only propositionally, but not definitionally, and can be accessed using ``unfold``, ``simp`` and ``rewrite`` with the function name (for example ``unfold foo`` or ``simp [foo]``, where ``foo`` is the function defined with well-founded recursion).
```lean
namespace Hide
open Nat
-- BEGIN
def div : Nat → Nat → Nat
| x, y =>
if h : 0 < y ∧ y ≤ x then
have : x - y < x :=
sub_lt (Nat.lt_of_lt_of_le h.left h.right) h.left
div (x - y) y + 1
else
0
example (x y : Nat) :
div x y = if 0 < y ∧ y ≤ x then div (x - y) y + 1 else 0 :=
by rw [div]; rfl
-- END
end Hide
```
If Lean cannot find a permutation of the arguments for which all recursive calls are decreasing, it will print a table that contains, for every recursive call, which arguments Lean could prove to be decreasing. For example, a function with three recursive calls and four parameters might cause the following message to be printed
```
example.lean:37:0-43:31: error: Could not find a decreasing measure.
The arguments relate at each recursive call as follows:
(<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted)
x1 x2 x3 x4
1) 39:6-27 = = _ =
2) 40:6-25 = ? _ <
3) 41:6-25 < _ _ _
Please use `termination_by` to specify a decreasing measure.
```
This table should be read as follows:
* In the first recursive call, in line 39, arguments 1, 2 and 4 are equal to the function's parameters.
* The second recursive call, in line 40, has an equal first argument, a smaller fourth argument, and nothing could be inferred for the second argument.
* The third recursive call, in line 41, has a decreasing first argument.
* No other proofs were attempted, either because the parameter has a type without a non-trivial ``WellFounded`` instance (parameter 3), or because it is already clear that no decreasing measure can be found.
Lean will print the termination measure it found if ``set_option showInferredTerminationBy true`` is set.
If Lean does not find the termination measure, or if you want to be explicit, you can append a `termination_by` clause to the function definition, after the function's body, but before the `where` clause if present. It is of the form
```
termination_by e
```
where ``e`` is an expression that depends on the parameters of the function and should be decreasing at each recursive call. The type of `e` should be an instance of the class ``WellFoundedRelation``, which determines how to compare two values of that type.
If ``f`` has parameters “after the ``:``” (for example when defining functions via patterns using `|`), then these can be brought into scope using the syntax
```
termination_by a₁ … aₙ => e
```
By default, Lean uses the tactic ``decreasing_tactic`` when proving that an argument is decreasing; see its documentation for how to globally extend it. You can also choose to use a different tactic for a given function definition with the clause
```
decreasing_by <tac>
```
which should come after ``termination_by`, if present.
Note that recursive definitions can in general require nested recursions, that is, recursion on different arguments of ``foo`` in the template above. The equation compiler handles this by abstracting later arguments, and recursively defining higher-order functions to meet the specification.
Mutual recursion
----------------
The equation compiler also allows mutual recursive definitions, with a syntax similar to that of [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions). Mutual definitions are always compiled using well-founded recursion, and so once again the defining equations hold only propositionally.
```lean
mutual
def even : Nat → Bool
| 0 => true
| a+1 => odd a
def odd : Nat → Bool
| 0 => false
| a+1 => even a
end
example (a : Nat) : even (a + 1) = odd a :=
by simp [even]
example (a : Nat) : odd (a + 1) = even a :=
by simp [odd]
```
Well-founded recursion is especially useful with [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions), since it provides the canonical way of defining functions on these types.
```lean
mutual
inductive Even : Nat → Prop
| even_zero : Even 0
| even_succ : ∀ n, Odd n → Even (n + 1)
inductive Odd : Nat → Prop
| odd_succ : ∀ n, Even n → Odd (n + 1)
end
open Even Odd
theorem not_odd_zero : ¬ Odd 0 := fun x => nomatch x
mutual
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
| _, odd_succ n h => h
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
| _, even_succ n h => h
end
inductive Term
| const : String → Term
| app : String → List Term → Term
open Term
mutual
def num_consts : Term → Nat
| .const n => 1
| .app n ts => num_consts_lst ts
def num_consts_lst : List Term → Nat
| [] => 0
| t::ts => num_consts t + num_consts_lst ts
end
```
In a set of mutually recursive function, either all or no functions must have an explicit termination measure (``termination_by``). A change of the default termination tactic (``decreasing_by``) only affects the proofs about the recursive calls of that function, not the other functions in the group.
```
mutual
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
| _, odd_succ n h => h
termination_by n h => h
decreasing_by decreasing_tactic
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
| _, even_succ n h => h
termination_by n h => h
end
```
Another way to express mutual recursion is using local function definitions in ``where`` or ``let rec`` clauses: these can be mutually recursive with each other and their containing function:
```
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
| _, odd_succ n h => h
termination_by n h => h
where
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
| _, even_succ n h => h
termination_by n h => h
```
.. _match_expressions:
Match Expressions
=================
Lean supports a ``match ... with ...`` construct similar to ones found in most functional programming languages. The syntax is as follows:
```
match t₁, ..., tₙ with
| p₁₁, ..., p₁ₙ => s₁
...
| pₘ₁, ..., pₘₙ => sₘ
```
Here ``t₁, ..., tₙ`` are any terms in the context in which the expression appears, the expressions ``pᵢⱼ`` are patterns, and the terms ``sᵢ`` are expressions in the local context together with variables introduced by the patterns on the left-hand side. Each ``sᵢ`` should have the expected type of the entire ``match`` expression.
Any ``match`` expression is interpreted using the equation compiler, which generalizes ``t₁, ..., tₙ``, defines an internal function meeting the specification, and then applies it to ``t₁, ..., tₙ``. In contrast to the definitions in [The Equation Compiler](declarations.md#the-equation-compiler), the terms ``tᵢ`` are arbitrary terms rather than just variables, and the expression can occur anywhere within a Lean expression, not just at the top level of a definition. Note that the syntax here is somewhat different: both the terms ``tᵢ`` and the patterns ``pᵢⱼ`` are separated by commas.
```lean
def foo (n : Nat) (b c : Bool) :=
5 + match n - 5, b && c with
| 0, true => 0
| m+1, true => m + 7
| 0, false => 5
| m+1, false => m + 3
```
When a ``match`` has only one line, Lean provides alternative syntax with a destructuring ``let``, as well as a destructuring lambda abstraction. Thus the following definitions all have the same net effect.
```lean
def bar₁ : Nat × Nat → Nat
| (m, n) => m + n
def bar₂ (p : Nat × Nat) : Nat :=
match p with | (m, n) => m + n
def bar₃ : Nat × Nat → Nat :=
fun ⟨m, n⟩ => m + n
def bar₄ (p : Nat × Nat) : Nat :=
let ⟨m, n⟩ := p; m + n
```
Information about the term being matched can be preserved in each branch using the syntax `match h : t with`. For example, a user may want to match a term `ns ++ ms : List Nat`, while tracking the hypothesis `ns ++ ms = []` or `ns ++ ms= h :: t` in the respective match arm:
```lean
def foo (ns ms : List Nat) (h1 : ns ++ ms ≠ []) (k : Nat -> Char) : Char :=
match h2 : ns ++ ms with
-- in this arm, we have the hypothesis `h2 : ns ++ ms = []`
| [] => absurd h2 h1
-- in this arm, we have the hypothesis `h2 : ns ++ ms = h :: t`
| h :: t => k h
-- '7'
#eval foo [7, 8, 9] [] (by decide) Nat.digitChar
```
.. _structures_and_records:
Structures and Records
======================
The ``structure`` command in Lean is used to define an inductive data type with a single constructor and to define its projections at the same time. The syntax is as follows:
```
structure Foo (a : α) : Sort u extends Bar, Baz :=
constructor :: (field₁ : β₁) ... (fieldₙ : βₙ)
```
Here ``(a : α)`` is a telescope, that is, the parameters to the inductive definition. The name ``constructor`` followed by the double colon is optional; if it is not present, the name ``mk`` is used by default. The keyword ``extends`` followed by a list of previously defined structures is also optional; if it is present, an instance of each of these structures is included among the fields to ``Foo``, and the types ``βᵢ`` can refer to their fields as well. The output type, ``Sort u``, can be omitted, in which case Lean infers to smallest non-``Prop`` sort possible (unless all the fields are ``Prop``, in which case it infers ``Prop``).
Finally, ``(field₁ : β₁) ... (fieldₙ : βₙ)`` is a telescope relative to ``(a : α)`` and the fields in ``bar`` and ``baz``.
The declaration above is syntactic sugar for an inductive type declaration, and so results in the addition of the following constants to the environment:
- the type former : ``Foo : Π (a : α), Sort u``
- the single constructor :
```
Foo.constructor : Π (a : α) (toBar : Bar) (toBaz : Baz)
(field₁ : β₁) ... (fieldₙ : βₙ), Foo a
```
- the eliminator ``Foo.rec`` for the inductive type with that constructor
In addition, Lean defines
- the projections : ``fieldᵢ : Π (a : α) (c : Foo) : βᵢ`` for each ``i``
where any other fields mentioned in ``βᵢ`` are replaced by the relevant projections from ``c``.
Given ``c : Foo``, Lean offers the following convenient syntax for the projection ``Foo.fieldᵢ c``:
- *anonymous projections* : ``c.fieldᵢ``
- *numbered projections* : ``c.i``
These can be used in any situation where Lean can infer that the type of ``c`` is of the form ``Foo a``. The convention for anonymous projections is extended to any function ``f`` defined in the namespace ``Foo``, as described in [Namespaces](namespaces.md).
Similarly, Lean offers the following convenient syntax for constructing elements of ``Foo``. They are equivalent to ``Foo.constructor b₁ b₂ f₁ f₁ ... fₙ``, where ``b₁ : Bar``, ``b₂ : Baz``, and each ``fᵢ : βᵢ`` :
- *anonymous constructor*: ``⟨ b₁, b₂, f₁, ..., fₙ ⟩``
- *record notation*:
```
{ toBar := b₁, toBaz := b₂, field₁ := f₁, ...,
fieldₙ := fₙ : Foo a }
```
The anonymous constructor can be used in any context where Lean can infer that the expression should have a type of the form ``Foo a``. The unicode brackets are entered as ``\<`` and ``\>`` respectively.
When using record notation, you can omit the annotation ``: Foo a`` when Lean can infer that the expression should have a type of the form ``Foo a``. You can replace either ``toBar`` or ``toBaz`` by assignments to *their* fields as well, essentially acting as though the fields of ``Bar`` and ``Baz`` are simply imported into ``Foo``. Finally, record notation also supports
- *record updates*: ``{ t with ... fieldᵢ := fᵢ ...}``
Here ``t`` is a term of type ``Foo a`` for some ``a``. The notation instructs Lean to take values from ``t`` for any field assignment that is omitted from the list.
Lean also allows you to specify a default value for any field in a structure by writing ``(fieldᵢ : βᵢ := t)``. Here ``t`` specifies the value to use when the field ``fieldᵢ`` is left unspecified in an instance of record notation.
```lean
universe u v
structure Vec (α : Type u) (n : Nat) :=
(l : List α) (h : l.length = n)
structure Foo (α : Type u) (β : Nat → Type v) : Type (max u v) :=
(a : α) (n : Nat) (b : β n)
structure Bar :=
(c : Nat := 8) (d : Nat)
structure Baz extends Foo Nat (Vec Nat), Bar :=
(v : Vec Nat n)
#check Foo
#check @Foo.mk
#check @Foo.rec
#check Foo.a
#check Foo.n
#check Foo.b
#check Baz
#check @Baz.mk
#check @Baz.rec
#check Baz.toFoo
#check Baz.toBar
#check Baz.v
def bzz := Vec.mk [1, 2, 3] rfl
#check Vec.l bzz
#check Vec.h bzz
#check bzz.l
#check bzz.h
#check bzz.1
#check bzz.2
example : Vec Nat 3 := Vec.mk [1, 2, 3] rfl
example : Vec Nat 3 := ⟨[1, 2, 3], rfl⟩
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl : Vec Nat 3 }
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl }
example : Foo Nat (Vec Nat) := ⟨1, 3, bzz⟩
example : Baz := ⟨⟨1, 3, bzz⟩, ⟨5, 7⟩, bzz⟩
example : Baz := { a := 1, n := 3, b := bzz, c := 5, d := 7, v := bzz}
def fzz : Foo Nat (Vec Nat) := {a := 1, n := 3, b := bzz}
example : Foo Nat (Vec Nat) := { fzz with a := 7 }
example : Baz := { fzz with c := 5, d := 7, v := bzz }
example : Bar := { c := 8, d := 9 }
example : Bar := { d := 9 } -- uses the default value for c
```
.. _type_classes:
Type Classes
============
(Classes and instances. Anonymous instances. Local instances.)
.. [Dybjer] Dybjer, Peter, *Inductive Families*. Formal Aspects of Computing 6, 1994, pages 440-465.

View File

@@ -1 +0,0 @@
# Definitions

View File

@@ -1,66 +0,0 @@
## What makes dependent type theory dependent?
The short explanation is that what makes dependent type theory dependent is that types can depend on parameters.
You have already seen a nice example of this: the type ``List α`` depends on the argument ``α``, and
this dependence is what distinguishes ``List Nat`` and ``List Bool``.
For another example, consider the type ``Vector α n``, the type of vectors of elements of ``α`` of length ``n``.
This type depends on *two* parameters: the type ``α : Type`` of the elements in the vector and the length ``n : Nat``.
Suppose we wish to write a function ``cons`` which inserts a new element at the head of a list.
What type should ``cons`` have? Such a function is *polymorphic*: we expect the ``cons`` function for ``Nat``, ``Bool``,
or an arbitrary type ``α`` to behave the same way.
So it makes sense to take the type to be the first argument to ``cons``, so that for any type, ``α``, ``cons α``
is the insertion function for lists of type ``α``. In other words, for every ``α``, ``cons α`` is the function that takes an element ``a : α``
and a list ``as : List α``, and returns a new list, so we have ``cons α a as : list α``.
It is clear that ``cons α`` should have type ``α → List α → List α``. But what type should ``cons`` have?
A first guess might be ``Type → α → list α → list α``, but, on reflection, this does not make sense:
the ``α`` in this expression does not refer to anything, whereas it should refer to the argument of type ``Type``.
In other words, *assuming* ``α : Type`` is the first argument to the function, the type of the next two elements are ``α`` and ``List α``.
These types vary depending on the first argument, ``α``.
This is an instance of a *dependent function type*, or *dependent arrow type*. Given ``α : Type`` and ``β : α → Type``,
think of ``β`` as a family of types over ``α``, that is, a type ``β a`` for each ``a : α``.
In that case, the type ``(a : α) → β a`` denotes the type of functions ``f`` with the property that,
for each ``a : α``, ``f a`` is an element of ``β a``. In other words, the type of the value returned by ``f`` depends on its input.
Notice that ``(a : α) → β`` makes sense for any expression ``β : Type``. When the value of ``β`` depends on ``a``
(as does, for example, the expression ``β a`` in the previous paragraph), ``(a : α) → β`` denotes a dependent function type.
When ``β`` doesn't depend on ``a``, ``(a : α) → β`` is no different from the type ``α → β``.
Indeed, in dependent type theory (and in Lean), ``α → β`` is just notation for ``(a : α) → β`` when ``β`` does not depend on ``a``.
Returning to the example of lists, we can use the command `#check` to inspect the type of the following `List` functions
We will explain the ``@`` symbol and the difference between the round and curly braces momentarily.
```lean
#check @List.cons -- {α : Type u_1} → α → List α → List α
#check @List.nil -- {α : Type u_1} → List α
#check @List.length -- {α : Type u_1} → List α → Nat
#check @List.append -- {α : Type u_1} → List α → List α → List α
```
Just as dependent function types ``(a : α) → β a`` generalize the notion of a function type ``α → β`` by allowing ``β`` to depend on ``α``,
dependent Cartesian product types ``(a : α) × β a`` generalize the Cartesian product ``α × β`` in the same way. Dependent products are also
called *sigma* types, and you can also write them as `Σ a : α, β a`. You can use `⟨a, b⟩` or `Sigma.mk a b` to create a dependent pair.
```lean
universe u v
def f (α : Type u) (β : α → Type v) (a : α) (b : β a) : (a : α) × β a :=
⟨a, b⟩
def g (α : Type u) (β : α → Type v) (a : α) (b : β a) : Σ a : α, β a :=
Sigma.mk a b
#reduce f
#reduce g
#reduce f Type (fun α => α) Nat 10
#reduce g Type (fun α => α) Nat 10
#reduce (f Type (fun α => α) Nat 10).1 -- Nat
#reduce (g Type (fun α => α) Nat 10).1 -- Nat
#reduce (f Type (fun α => α) Nat 10).2 -- 10
#reduce (g Type (fun α => α) Nat 10).2 -- 10
```
The function `f` and `g` above denote the same function.

View File

@@ -1,3 +0,0 @@
# Dependent Types
In this section, we introduce simple type theory, types as objects, definitions, and explain what makes dependent type theory *dependent*.

View File

@@ -1,109 +0,0 @@
# Documentation
The Lean `doc` folder contains the [Lean Manual](https://lean-lang.org/lean4/doc/) and is
authored in a combination of markdown (`*.md`) files and literate Lean files. The .lean files are
preprocessed using a tool called [LeanInk](https://github.com/leanprover/leanink) and
[Alectryon](https://github.com/Kha/alectryon) which produces a generated markdown file. We then run
`mdbook` on the result to generate the html pages.
## Settings
We are using the following settings while editing the markdown docs.
```json
{
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"[markdown]": {
"rewrap.wrappingColumn": 70
}
}
```
## Build
### Using Nix
Building the manual using Nix (which is what the CI does) is as easy as
```bash
$ nix build --update-input lean ./doc
```
You can also open a shell with `mdbook` for running the commands mentioned below with
`nix develop ./doc#book`. Otherwise, read on.
### Manually
To build and test the book you have to preprocess the .lean files with Alectryon then use our own
fork of the Rust tool named [mdbook](https://github.com/leanprover/mdbook). We have our own fork of
mdBook with the following additional features:
* Add support for hiding lines in other languages
[#1339](https://github.com/rust-lang/mdBook/pull/1339)
* Make `mdbook test` call the `lean` compiler to test the snippets.
* Ability to test a single chapter at a time which is handy when you
are working on that chapter. See the `--chapter` option.
So you need to setup these tools before you can run `mdBook`.
1. install [Rust](https://www.rust-lang.org/tools/install)
which provides you with the `cargo` tool for building rust packages.
Then run the following:
```bash
cargo install --git https://github.com/leanprover/mdBook mdbook
```
1. Clone https://github.com/leanprover/LeanInk.git and run `lake build` then make the resulting
binary available to Alectryon using e.g.
```bash
# make `leanInk` available in the current shell
export PATH=$PWD/build/bin:$PATH
```
1. Create a Python 3.10 environment.
1. Install Alectryon:
```
python3 -m pip install git+https://github.com/Kha/alectryon.git@typeid
```
1. Now you are ready to process the `*.lean` files using Alectryon as follows:
```
cd lean4/doc
alectryon --frontend lean4+markup examples/palindromes.lean --backend webpage -o palindromes.lean.md
```
Repeat this for the other .lean files you care about or write a script to process them all.
1. Now you can build the book using:
```
cd lean4/doc
mdbook build
```
This will put the HTML in a `out` folder so you can load `out/index.html` in your web browser and
it should look like https://lean-lang.org/lean4/doc/.
1. It is also handy to use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html)
in the `doc/` folder so that it keeps the html up to date while you are editing.
```bash
mdbook watch --open # opens the output in `out/` in your default browser
```
## Testing Lean Snippets
You can run the following in the `doc/` folder to test all the lean code snippets.
```bash
mdbook test
```
and you can use the `--chapter` option to test a specific chapter that you are working on:
```bash
mdbook test --chapter Array
```
Use chapter name `?` to get a list of all the chapter names.

View File

@@ -69,7 +69,7 @@ We'll use `v4.6.0` as the intended release version as a running example.
- `repl`:
There are two copies of `lean-toolchain`/`lakefile.lean`:
in the root, and in `test/Mathlib/`. Edit both, and run `lake update` in both directories.
- An awkward situtation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
- An awkward situation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
to a nightly toolchain that comes *after* the stable toolchain we are
targeting. In this case it is necessary to create a branch `releases/v4.6.0` from the last commit which was on
an earlier toolchain, move that branch to the stable toolchain, and create the toolchain tag from that branch.

View File

@@ -1,9 +0,0 @@
Examples
========
- [Palindromes](examples/palindromes.lean.md)
- [Binary Search Trees](examples/bintree.lean.md)
- [A Certified Type Checker](examples/tc.lean.md)
- [The Well-Typed Interpreter](examples/interp.lean.md)
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)

4
doc/examples/README.md Normal file
View File

@@ -0,0 +1,4 @@
These examples are checked in Lean's CI to ensure that they continue
to work. They are included in the documentation section of the Lean
website via a script that copies the latest version, in order to
ensure that the website tracks Lean releases rather than `master`.

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include bintree.lean}}
```

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include deBruijn.lean}}
```

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include interp.lean}}
```

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include palindromes.lean}}
```

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include phoas.lean}}
```

View File

@@ -1,5 +0,0 @@
(this example is rendered by Alectryon in the CI)
```lean
{{#include tc.lean}}
```

View File

@@ -1,5 +0,0 @@
(this chapter is rendered by Alectryon in the CI)
```lean
{{#include widgets.lean}}
```

View File

@@ -1,550 +0,0 @@
Expressions
===========
Every expression in Lean has a [Type](types.md). Every type is also an
expression of type `Sort u` for some universe level u. See [Type
Universes](types.md#type_universes).
Expression Syntax
=================
The set of expressions in Lean is defined inductively as follows:
* ``Sort u`` : the universe of types at universe level ``u``
* ``c`` : where ``c`` is an identifier denoting a declared constant or a defined object
* ``x`` : where ``x`` is a variable in the local context in which the expression is interpreted
* `m?` : where `m?` is a metavariable in the metavariable context in which the expression is interpreted,
you can view metavariable as a "hole" that still needs to be synthesized
* ``(x : α) → β`` : the type of functions taking an element ``x`` of ``α`` to an element of ``β``,
where ``β`` is an expression whose type is a ``Sort``
* ``s t`` : the result of applying ``s`` to ``t``, where ``s`` and ``t`` are expressions
* ``fun x : α => t`` or `λ x : α => t`: the function mapping any value ``x`` of type ``α`` to ``t``, where ``t`` is an expression
* ``let x := t; s`` : a local definition, denotes the value of ``s`` when ``x`` is replaced by ``t``
* `s.i` : a projection, denotes the value of the `i`-th field of `s`
* `lit` : a natural number or string literal
* `mdata k s` : the expression `s` decorated with metadata `k`, where is a key-value map
Every well formed term in Lean has a *type*, which itself is an expression of type ``Sort u`` for some ``u``. The fact that a term ``t`` has type ``α`` is written ``t : α``.
For an expression to be well formed, its components have to satisfy certain typing constraints. These, in turn, determine the type of the resulting term, as follows:
* ``Sort u : Sort (u + 1)``
* ``c : α``, where ``α`` is the type that ``c`` has been declared or defined to have
* ``x : α``, where ``α`` is the type that ``x`` has been assigned in the local context where it is interpreted
* ``?m : α``, where ``α`` is the type that ``?m`` has been declared in the metavariable context where it is interpreted
* ``(x : α) → β : Sort (imax u v)`` where ``α : Sort u``, and ``β : Sort v`` assuming ``x : α``
* ``s t : β[t/x]`` where ``s`` has type ``(x : α) → β`` and ``t`` has type ``α``
* ``(fun x : α => t) : (x : α) → β`` if ``t`` has type ``β`` whenever ``x`` has type ``α``
* ``(let x := t; s) : β[t/x]`` where ``t`` has type ``α`` and ``s`` has type ``β`` assuming ``x : α``
* `lit : Nat` if `lit` is a numeral
* `lit : String` if `lit` is a string literal
* `mdata k s : α` if `s : α`
* `s.i : α` if `s : β` and `β` is an inductive datatype with only one constructor, and `i`-th field has type `α`
``Prop`` abbreviates ``Sort 0``, ``Type`` abbreviates ``Sort 1``, and
``Type u`` abbreviates ``Sort (u + 1)`` when ``u`` is a universe
variable. We say "``α`` is a type" to express ``α : Type u`` for some
``u``, and we say "``p`` is a proposition" to express
``p : Prop``. Using the *propositions as types* correspondence, given
``p : Prop``, we refer to an expression ``t : p`` as a *proof* of ``p``. In
contrast, given ``α : Type u`` for some ``u`` and ``t : α``, we
sometimes refer to ``t`` as *data*.
When the expression ``β`` in ``(x : α) → β`` does not depend on ``x``,
it can be written ``α → β``. As usual, the variable ``x`` is bound in
``(x : α) → β``, ``fun x : α => t``, and ``let x := t; s``. The
expression ``∀ x : α, β`` is alternative syntax for ``(x : α) → β``,
and is intended to be used when ``β`` is a proposition. An underscore
can be used to generate an internal variable in a binder, as in
``fun _ : α => t``.
*Metavariables*, that is, temporary placeholders, are used in the
process of constructing terms. Terms that are added to the
environment contain neither metavariable nor variables, which is to
say, they are fully elaborated and make sense in the empty context.
Axioms can be declared using the ``axiom`` keyword.
Similarly, objects can be defined in various ways, such as using ``def`` and ``theorem`` keywords.
See [Chapter Declarations](./declarations.md) for more information.
Writing an expression ``(t : α)`` forces Lean to elaborate ``t`` so that it has type ``α`` or report an error if it fails.
Lean supports anonymous constructor notation, anonymous projections,
and various forms of match syntax, including destructuring ``fun`` and
``let``. These, as well as notation for common data types (like pairs,
lists, and so on) are discussed in [Chapter Declarations](./declarations.md)
in connection with inductive types.
```lean
universe u
#check Sort 0
#check Prop
#check Sort 1
#check Type
#check Sort u
#check Sort (u+1)
#check Nat → Bool
#check (α : Type u) → List α
#check (α : Type u) → (β : Type u) → Sum α β
#check fun x : Nat => x
#check fun (α : Type u) (x : α) => x
#check let x := 5; x * 2
#check "hello"
#check (fun x => x) true
```
Implicit Arguments
==================
When declaring arguments to defined objects in Lean (for example, with
``def``, ``theorem``, ``axiom``, ``constant``, ``inductive``, or
``structure``; see [Chapter Declarations](./declarations.md) or when
declaring variables in sections (see [Other Commands](./other_commands.md)),
arguments can be annotated as *explicit* or *implicit*.
This determines how expressions containing the object are interpreted.
* ``(x : α)`` : an explicit argument of type ``α``
* ``{x : α}`` : an implicit argument, eagerly inserted
* ``⦃x : α⦄`` or ``{{x : α}}`` : an implicit argument, weakly inserted
* ``[x : α]`` : an implicit argument that should be inferred by type class resolution
* ``(x : α := v)`` : an optional argument, with default value ``v``
* ``(x : α := by tac)`` : an implicit argument, to be synthesized by tactic ``tac``
The name of the variable can be omitted from a class resolution
argument, in which case an internal name is generated.
When a function has an explicit argument, you can nonetheless ask
Lean's elaborator to infer the argument automatically, by entering it
as an underscore (``_``). Conversely, writing ``@foo`` indicates that
all of the arguments to be ``foo`` are to be given explicitly,
independent of how ``foo`` was declared. You can also provide a value
for an implicit parameter using named arguments. Named arguments
enable you to specify an argument for a parameter by matching the
argument with its name rather than with its position in the parameter
list. If you don't remember the order of the parameters but know
their names, you can send the arguments in any order. You may also
provide the value for an implicit parameter whenLean failed to infer
it. Named arguments also improve the readability of your code by
identifying what each argument represents.
```lean
def add (x y : Nat) : Nat :=
x + y
#check add 2 3 -- Nat
#eval add 2 3 -- 5
def id1 (α : Type u) (x : α) : α := x
#check id1 Nat 3
#check id1 _ 3
def id2 {α : Type u} (x : α) : α := x
#check id2 3
#check @id2 Nat 3
#check id2 (α := Nat) 3
#check id2
#check id2 (α := Nat)
def id3 {{α : Type u}} (x : α) : α := x
#check id3 3
#check @id3 Nat 3
#check (id3 : (α : Type) → αα)
class Cls where
val : Nat
instance Cls_five : Cls where
val := 5
def ex2 [c : Cls] : Nat := c.val
example : ex2 = 5 := rfl
def ex2a [Cls] : Nat := ex2
example : ex2a = 5 := rfl
def ex3 (x : Nat := 5) := x
#check ex3 2
#check ex3
example : ex3 = 5 := rfl
def ex4 (x : Nat) (y : Nat := x) : Nat :=
x * y
example : ex4 x = x * x :=
rfl
```
Basic Data Types and Assertions
===============================
The core library contains a number of basic data types, such as the
natural numbers (`Nat`), the integers (`Int`), the
booleans (``Bool``), and common operations on these, as well as the
usual logical quantifiers and connectives. Some example are given
below. A list of common notations and their precedences can be found
in a [file](https://github.com/leanprover/lean4/blob/master/src/Init/Notation.lean)
in the core library. The core library also contains a number of basic
data type constructors. Definitions can also be found the
[Data](https://github.com/leanprover/lean4/blob/master/src/Init/Data)
directory of the core library. For more information, see also [Chapter libraries](./libraries.md).
```
/- numbers -/
def f1 (a b c : Nat) : Nat :=
a^2 + b^2 + c^2
def p1 (a b c d : Nat) : Prop :=
(a + b)^c ≤ d
def p2 (i j k : Int) : Prop :=
i % (j * k) = 0
/- booleans -/
def f2 (a b c : Bool) : Bool :=
a && (b || c)
/- pairs -/
#eval (1, 2)
def p : Nat × Bool := (1, false)
section
variable (a b c : Nat) (p : Nat × bool)
#check (1, 2)
#check p.1 * 2
#check p.2 && tt
#check ((1, 2, 3) : Nat × Nat × Nat)
end
/- lists -/
section
variable x y z : Nat
variable xs ys zs : list Nat
open list
#check (1 :: xs) ++ (y :: zs) ++ [1,2,3]
#check append (cons 1 xs) (cons y zs)
#check map (λ x, x^2) [1, 2, 3]
end
/- sets -/
section
variable s t u : set Nat
#check ({1, 2, 3} ∩ s) ({x | x < 7} ∩ t)
end
/- strings and characters -/
#check "hello world"
#check 'a'
/- assertions -/
#check ∀ a b c n : Nat,
a ≠ 0 ∧ b ≠ 0 ∧ c ≠ 0 ∧ n > 2 → a^n + b^n ≠ c^n
def unbounded (f : Nat → Nat) : Prop := ∀ M, ∃ n, f n ≥ M
```
.. _constructors_projections_and_matching:
Constructors, Projections, and Matching
=======================================
Lean's foundation, the *Calculus of Inductive Constructions*, supports the declaration of *inductive types*. Such types can have any number of *constructors*, and an associated *eliminator* (or *recursor*). Inductive types with one constructor, known as *structures*, have *projections*. The full syntax of inductive types is described in [Declarations](declarations.md), but here we describe some syntactic elements that facilitate their use in expressions.
When Lean can infer the type of an expression and it is an inductive type with one constructor, then one can write ``⟨a1, a2, ..., an⟩`` to apply the constructor without naming it. For example, ``⟨a, b⟩`` denotes ``prod.mk a b`` in a context where the expression can be inferred to be a pair, and ``⟨h₁, h₂⟩`` denotes ``and.intro h₁ h₂`` in a context when the expression can be inferred to be a conjunction. The notation will nest constructions automatically, so ``⟨a1, a2, a3⟩`` is interpreted as ``prod.mk a1 (prod.mk a2 a3)`` when the expression is expected to have a type of the form ``α1 × α2 × α3``. (The latter is interpreted as ``α1 × (α2 × α3)``, since the product associates to the right.)
Similarly, one can use "dot notation" for projections: one can write ``p.fst`` and ``p.snd`` for ``prod.fst p`` and ``prod.snd p`` when Lean can infer that ``p`` is an element of a product, and ``h.left`` and ``h.right`` for ``and.left h`` and ``and.right h`` when ``h`` is a conjunction.
The anonymous projector notation can used more generally for any objects defined in a *namespace* (see [Other Commands](other_commands.md)). For example, if ``l`` has type ``list α`` then ``l.map f`` abbreviates ``list.map f l``, in which ``l`` has been placed at the first argument position where ``list.map`` expects a ``list``.
Finally, for data types with one constructor, one destruct an element by pattern matching using the ``let`` and ``assume`` constructs, as in the examples below. Internally, these are interpreted using the ``match`` construct, which is in turn compiled down for the eliminator for the inductive type, as described in [Declarations](declarations.md).
.. code-block:: lean
universes u v
variable {α : Type u} {β : Type v}
def p : Nat × := ⟨1, 2⟩
#check p.fst
#check p.snd
def p' : Nat × × bool := ⟨1, 2, tt⟩
#check p'.fst
#check p'.snd.fst
#check p'.snd.snd
def swap_pair (p : α × β) : β × α :=
⟨p.snd, p.fst⟩
theorem swap_conj {a b : Prop} (h : a ∧ b) : b ∧ a :=
⟨h.right, h.left⟩
#check [1, 2, 3].append [2, 3, 4]
#check [1, 2, 3].map (λ x, x^2)
example (p q : Prop) : p ∧ q → q ∧ p :=
λ h, ⟨h.right, h.left⟩
def swap_pair' (p : α × β) : β × α :=
let (x, y) := p in (y, x)
theorem swap_conj' {a b : Prop} (h : a ∧ b) : b ∧ a :=
let ⟨ha, hb⟩ := h in ⟨hb, ha⟩
def swap_pair'' : α × β → β × α :=
λ ⟨x, y⟩, (y, x)
theorem swap_conj'' {a b : Prop} : a ∧ b → b ∧ a :=
assume ⟨ha, hb⟩, ⟨hb, ha⟩
Structured Proofs
=================
Syntactic sugar is provided for writing structured proof terms:
* ``have h : p := s; t`` is sugar for ``(fun h : p => t) s``
* ``suffices h : p from s; t`` is sugar for ``(λ h : p => s) t``
* ``suffices h : p by s; t`` is sugar for ``(suffixes h : p from by s; t)``
* ``show p from t`` is sugar for ``(have this : p := t; this)``
* ``show p by tac`` is sugar for ``(show p from by tac)``
Types can be omitted when they can be inferred by Lean. Lean also
allows ``have : p := t; s``, which gives the assumption the
name ``this`` in the local context. Similarly, Lean recognizes the
variant ``suffices p from s; t``, which use the name ``this`` for the new hypothesis.
The notation ``p`` is notation for ``(by assumption : p)``, and can
therefore be used to apply hypotheses in the local context.
As noted in [Constructors, Projections and Matching](#constructors_projections_and_matching),
anonymous constructors and projections and match syntax can be used in proofs just as in expressions that denote data.
.. code-block:: lean
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume h₁ : p,
assume h₂ : q ∧ r,
have h₃ : q, from and.left h₂,
show p ∧ q, from and.intro h₁ h₃
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume : p,
assume : q ∧ r,
have q, from and.left this,
show p ∧ q, from and.intro p this
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume h₁ : p,
assume h₂ : q ∧ r,
suffices h₃ : q, from and.intro h₁ h₃,
show q, from and.left h₂
Lean also supports a calculational environment, which is introduced with the keyword ``calc``. The syntax is as follows:
.. code-block:: text
calc
<expr>_0 'op_1' <expr>_1 ':' <proof>_1
'...' 'op_2' <expr>_2 ':' <proof>_2
...
'...' 'op_n' <expr>_n ':' <proof>_n
Each ``<proof>_i`` is a proof for ``<expr>_{i-1} op_i <expr>_i``.
Here is an example:
.. code-block:: lean
variable (a b c d e : Nat)
variable h1 : a = b
variable h2 : b = c + 1
variable h3 : c = d
variable h4 : e = 1 + d
theorem T : a = e :=
calc
a = b : h1
... = c + 1 : h2
... = d + 1 : congr_arg _ h3
... = 1 + d : add_comm d (1 : Nat)
... = e : eq.symm h4
The style of writing proofs is most effective when it is used in conjunction with the ``simp`` and ``rewrite`` tactics.
.. _computation:
Computation
===========
Two expressions that differ up to a renaming of their bound variables are said to be *α-equivalent*, and are treated as syntactically equivalent by Lean.
Every expression in Lean has a natural computational interpretation, unless it involves classical elements that block computation, as described in the next section. The system recognizes the following notions of *reduction*:
* *β-reduction* : An expression ``(λ x, t) s`` β-reduces to ``t[s/x]``, that is, the result of replacing ``x`` by ``s`` in ``t``.
* *ζ-reduction* : An expression ``let x := s in t`` ζ-reduces to ``t[s/x]``.
* *δ-reduction* : If ``c`` is a defined constant with definition ``t``, then ``c`` δ-reduces to ``t``.
* *ι-reduction* : When a function defined by recursion on an inductive type is applied to an element given by an explicit constructor, the result ι-reduces to the specified function value, as described in [Inductive Types](inductive.md).
The reduction relation is transitive, which is to say, is ``s`` reduces to ``s'`` and ``t`` reduces to ``t'``, then ``s t`` reduces to ``s' t'``, ``λ x, s`` reduces to ``λ x, s'``, and so on. If ``s`` and ``t`` reduce to a common term, they are said to be *definitionally equal*. Definitional equality is defined to be the smallest equivalence relation that satisfies all these properties and also includes α-equivalence and the following two relations:
* *η-equivalence* : An expression ``(λx, t x)`` is η-equivalent to ``t``, assuming ``x`` does not occur in ``t``.
* *proof irrelevance* : If ``p : Prop``, ``s : p``, and ``t : p``, then ``s`` and ``t`` are considered to be equivalent.
This last fact reflects the intuition that once we have proved a proposition ``p``, we only care that is has been proved; the proof does nothing more than witness the fact that ``p`` is true.
Definitional equality is a strong notion of equality of values. Lean's logical foundations sanction treating definitionally equal terms as being the same when checking that a term is well-typed and/or that it has a given type.
The reduction relation is believed to be strongly normalizing, which is to say, every sequence of reductions applied to a term will eventually terminate. The property guarantees that Lean's type-checking algorithm terminates, at least in principle. The consistency of Lean and its soundness with respect to set-theoretic semantics do not depend on either of these properties.
Lean provides two commands to compute with expressions:
* ``#reduce t`` : use the kernel type-checking procedures to carry out reductions on ``t`` until no more reductions are possible, and show the result
* ``#eval t`` : evaluate ``t`` using a fast bytecode evaluator, and show the result
Every computable definition in Lean is compiled to bytecode at definition time. Bytecode evaluation is more liberal than kernel evaluation: types and all propositional information are erased, and functions are evaluated using a stack-based virtual machine. As a result, ``#eval`` is more efficient than ``#reduce,`` and can be used to execute complex programs. In contrast, ``#reduce`` is designed to be small and reliable, and to produce type-correct terms at each step. Bytecode is never used in type checking, so as far as soundness and consistency are concerned, only kernel reduction is part of the trusted computing base.
.. code-block:: lean
#reduce (fun x => x + 3) 5
#eval (fun x => x + 3) 5
#reduce let x := 5; x + 3
#eval let x := 5; x + 3
def f x := x + 3
#reduce f 5
#eval f 5
#reduce @Nat.rec (λ n => Nat) (0 : Nat)
(λ n recval : Nat => recval + n + 1) (5 : Nat)
def g : Nat → Nat
| 0 => 0
| (n+1) => g n + n + 1
#reduce g 5
#eval g 5
#eval g 5000
example : (fun x => x + 3) 5 = 8 := rfl
example : (fun x => f x) = f := rfl
example (p : Prop) (h₁ h₂ : p) : h₁ = h₂ := rfl
Note: the combination of proof irrelevance and singleton ``Prop`` elimination in ι-reduction renders the ideal version of definitional equality, as described above, undecidable. Lean's procedure for checking definitional equality is only an approximation to the ideal. It is not transitive, as illustrated by the example below. Once again, this does not compromise the consistency or soundness of Lean; it only means that Lean is more conservative in the terms it recognizes as well typed, and this does not cause problems in practice. Singleton elimination will be discussed in greater detail in [Inductive Types](inductive.md).
.. code-block:: lean
def R (x y : unit) := false
def accrec := @acc.rec unit R (λ_, unit) (λ _ a ih, ()) ()
example (h) : accrec h = accrec (acc.intro _ (λ y, acc.inv h)) :=
rfl
example (h) : accrec (acc.intro _ (λ y, acc.inv h)) = () := rfl
example (h) : accrec h = () := sorry -- rfl fails
Axioms
======
Lean's foundational framework consists of:
- type universes and dependent function types, as described above
- inductive definitions, as described in [Inductive Types](inductive.md) and
[Inductive Families](declarations.md#inductive-families).
In addition, the core library defines (and trusts) the following axiomatic extensions:
- propositional extensionality:
.. code-block:: lean
namespace hide
-- BEGIN
axiom propext {a b : Prop} : (a ↔ b) → a = b
-- END
end hide
- quotients:
.. code-block:: lean
namespace hide
-- BEGIN
universes u v
constant quot : Π {α : Sort u}, (αα → Prop) → Sort u
constant quot.mk : Π {α : Sort u} (r : αα → Prop),
α → quot r
axiom quot.ind : ∀ {α : Sort u} {r : αα → Prop}
{β : quot r → Prop},
(∀ a, β (quot.mk r a)) →
∀ (q : quot r), β q
constant quot.lift : Π {α : Sort u} {r : αα → Prop}
{β : Sort u} (f : α → β),
(∀ a b, r a b → f a = f b) → quot r → β
axiom quot.sound : ∀ {α : Type u} {r : αα → Prop}
{a b : α},
r a b → quot.mk r a = quot.mk r b
-- END
end hide
``quot r`` represents the quotient of ``α`` by the smallest equivalence relation containing ``r``. ``quot.mk`` and ``quot.lift`` satisfy the following computation rule:
.. code-block:: text
quot.lift f h (quot.mk r a) = f a
- choice:
.. code-block:: lean
namespace hide
universe u
-- BEGIN
axiom choice {α : Sort u} : nonempty αα
-- END
end hide
Here ``nonempty α`` is defined as follows:
.. code-block:: lean
namespace hide
universe u
-- BEGIN
class inductive nonempty (α : Sort u) : Prop
| intro : α → nonempty
-- END
end hide
It is equivalent to ``∃ x : α, true``.
The quotient construction implies function extensionality. The ``choice`` principle, in conjunction with the others, makes the axiomatic foundation classical; in particular, it implies the law of the excluded middle and propositional decidability. Functions that make use of ``choice`` to produce data are incompatible with a computational interpretation, and do not produce bytecode. They have to be declared ``noncomputable``.
For metaprogramming purposes, Lean also allows the definition of objects which stand outside the object language. These are denoted with the ``meta`` keyword, as described in [Metaprogramming](metaprogramming.md).

View File

@@ -1,55 +0,0 @@
Frequently Asked Questions
==========================
### What is Lean?
Lean is a new open source theorem prover being developed at Microsoft Research.
It is a research project that aims to bridge the gap between interactive and automated theorem proving.
Lean can be also used as a programming language. Actually, some Lean features are implemented in Lean itself.
### Should I use Lean?
Lean is under heavy development, and we are constantly trying new
ideas and tweaking the system. It is a research project and not a product.
Things change rapidly, and we constantly break backward compatibility.
Lean comes "as is", you should not expect we will fix bugs and/or add new features for your project.
We have our own priorities, and will not change them to accommodate your needs.
Even if you implement a new feature or fix a bug, we may not want to merge it because
it may conflict with our plans for Lean, it may not be performant, we may not want to maintain it,
we may be busy, etc. If you really need this new feature or bug fix, we suggest you create your own fork and maintain it yourself.
### Where is the documentation?
This is the Lean 4 manual. It is a work in progress, but it will eventually cover the whole language.
A public and very active chat room dedicated to Lean is open on [Zulip](https://leanprover.zulipchat.com).
It is a good place to interact with other Lean users.
### Should I use Lean to teach a course?
Lean has been used to teach courses on logic, type theory and programming languages at CMU and the University of Washington.
The lecture notes for the CMU course [Logic and Proof](https://lean-lang.org/logic_and_proof) are available online,
but they are for Lean 3.
If you decide to teach a course using Lean, we suggest you prepare all material before the beginning of the course, and
make sure that Lean attends all your needs. You should not expect we will fix bugs and/or add features needed for your course.
### Are there IDEs for Lean?
Yes, see [Setting Up Lean](./setup.md).
### Is Lean sound? How big is the kernel? Should I trust it?
Lean has a relatively small kernel.
Several independent checkers have been implemented for Lean 3. Two of them are
[tc](https://github.com/leanprover/tc) and [trepplein](https://github.com/gebner/trepplein).
We expect similar independent checkers will be built for Lean 4.
### Should I open a new issue?
We use [GitHub](https://github.com/leanprover/lean4/issues) to track bugs and new features.
Bug reports are always welcome, but nitpicking issues are not (e.g., the error message is confusing).
See also our [contribution guidelines](https://github.com/leanprover/lean4/blob/master/CONTRIBUTING.md).
### Is it Lean, LEAN, or L∃∀N?
We always use "Lean" in writing.
When specifying a major version number, we append it together with a single space: Lean 4.

151
doc/flake.lock generated
View File

@@ -1,151 +0,0 @@
{
"nodes": {
"alectryon": {
"flake": false,
"locked": {
"lastModified": 1654613606,
"narHash": "sha256-IGCn1PzTyw8rrwmyWUiw3Jo/dyZVGkMslnHYW7YB8yk=",
"owner": "Kha",
"repo": "alectryon",
"rev": "c3b16f650665745e1da4ddfcc048d3bd639f71d5",
"type": "github"
},
"original": {
"owner": "Kha",
"ref": "typeid",
"repo": "alectryon",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"lean": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"nixpkgs-old": "nixpkgs-old"
},
"locked": {
"lastModified": 0,
"narHash": "sha256-saRAtQ6VautVXKDw1XH35qwP0KEBKTKZbg/TRa4N9Vw=",
"path": "../.",
"type": "path"
},
"original": {
"path": "../.",
"type": "path"
}
},
"leanInk": {
"flake": false,
"locked": {
"lastModified": 1704976501,
"narHash": "sha256-FSBUsbX0HxakSnYRYzRBDN2YKmH9EkA0q9p7TSPEJTI=",
"owner": "leanprover",
"repo": "LeanInk",
"rev": "51821e3c2c032c88e4b2956483899d373ec090c4",
"type": "github"
},
"original": {
"owner": "leanprover",
"ref": "refs/pull/57/merge",
"repo": "LeanInk",
"type": "github"
}
},
"mdBook": {
"flake": false,
"locked": {
"lastModified": 1660074464,
"narHash": "sha256-W30G7AeWBjdJE/CQZJU5vJjaDGZtpmxEKNMEvaYtuF8=",
"owner": "leanprover",
"repo": "mdBook",
"rev": "9321c10c502cd59eea8afc4325a84eab3ddf9391",
"type": "github"
},
"original": {
"owner": "leanprover",
"repo": "mdBook",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1710889954,
"narHash": "sha256-Pr6F5Pmd7JnNEMHHmspZ0qVqIBVxyZ13ik1pJtm2QXk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7872526e9c5332274ea5932a0c3270d6e4724f3b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-old": {
"flake": false,
"locked": {
"lastModified": 1581379743,
"narHash": "sha256-i1XCn9rKuLjvCdu2UeXKzGLF6IuQePQKFt4hEKRU5oc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "34c7eb7545d155cc5b6f499b23a7cb1c96ab4d59",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-19.03",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"alectryon": "alectryon",
"flake-utils": [
"lean",
"flake-utils"
],
"lean": "lean",
"leanInk": "leanInk",
"mdBook": "mdBook"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,93 +0,0 @@
{
description = "Lean documentation";
inputs.lean.url = path:../.;
inputs.flake-utils.follows = "lean/flake-utils";
inputs.mdBook = {
url = "github:leanprover/mdBook";
flake = false;
};
inputs.alectryon = {
url = "github:Kha/alectryon/typeid";
flake = false;
};
inputs.leanInk = {
url = "github:leanprover/LeanInk/refs/pull/57/merge";
flake = false;
};
outputs = inputs@{ self, ... }: inputs.flake-utils.lib.eachDefaultSystem (system:
with inputs.lean.packages.${system}.deprecated; with nixpkgs;
let
doc-src = lib.sourceByRegex ../. ["doc.*" "tests(/lean(/beginEndAsMacro.lean)?)?"];
in {
packages = rec {
lean-mdbook = mdbook.overrideAttrs (drv: rec {
name = "lean-${mdbook.name}";
src = inputs.mdBook;
cargoDeps = drv.cargoDeps.overrideAttrs (_: {
inherit src;
outputHash = "sha256-CO3A9Kpp4sIvkT9X3p+GTidazk7Fn4jf0AP2PINN44A=";
});
doCheck = false;
});
book = stdenv.mkDerivation {
name ="lean-doc";
src = doc-src;
buildInputs = [ lean-mdbook ];
buildCommand = ''
mkdir $out
# necessary for `additional-css`...?
cp -r --no-preserve=mode $src/doc/* .
# overwrite stub .lean.md files
cp -r ${inked}/* .
mdbook build -d $out
'';
};
leanInk = (buildLeanPackage {
name = "Main";
src = inputs.leanInk;
deps = [ (buildLeanPackage {
name = "LeanInk";
src = inputs.leanInk;
}) ];
executableName = "leanInk";
linkFlags = ["-rdynamic"];
}).executable;
alectryon = python3Packages.buildPythonApplication {
name = "alectryon";
src = inputs.alectryon;
propagatedBuildInputs =
[ leanInk lean-all ] ++
# https://github.com/cpitclaudel/alectryon/blob/master/setup.cfg
(with python3Packages; [ pygments dominate beautifulsoup4 docutils ]);
doCheck = false;
};
renderLeanMod = mod: mod.overrideAttrs (final: prev: {
name = "${prev.name}.md";
buildInputs = prev.buildInputs ++ [ alectryon ];
outputs = [ "out" ];
buildCommand = ''
dir=$(dirname $relpath)
mkdir -p $dir out/$dir
if [ -d $src ]; then cp -r $src/. $dir/; else cp $src $leanPath; fi
alectryon --frontend lean4+markup $leanPath --backend webpage -o $out/$leanPath.md
'';
});
renderPackage = pkg: symlinkJoin {
name = "${pkg.name}-mds";
paths = map renderLeanMod (lib.attrValues pkg.mods);
};
literate = buildLeanPackage {
name = "literate";
src = ./.;
roots = [
{ mod = "examples"; glob = "submodules"; }
];
};
inked = renderPackage literate;
doc = book;
};
defaultPackage = self.packages.${system}.doc;
});
}

View File

@@ -1,7 +0,0 @@
Functional Programming in Lean
=======================
The goal of [this book](https://lean-lang.org/functional_programming_in_lean/) is to be an accessible introduction to using Lean 4 as a programming language.
It should be useful both to people who want to use Lean as a general-purpose programming language and to mathematicians who want to develop larger-scale proof automation but do not have a background in functional programming.
It does not assume any background with functional programming, though it's probably not a good first book on programming in general.
New content will be added once per month until it's done.

View File

@@ -1,145 +0,0 @@
## Function Abstraction and Evaluation
We have seen that if we have ``m n : Nat``, then we have ``(m, n) : Nat × Nat``.
This gives us a way of creating pairs of natural numbers.
Conversely, if we have ``p : Nat × Nat``, then
we have ``p.1 : Nat`` and ``p.2 : Nat``.
This gives us a way of "using" a pair, by extracting its two components.
We already know how to "use" a function ``f : α → β``, namely,
we can apply it to an element ``a : α`` to obtain ``f a : β``.
But how do we create a function from another expression?
The companion to application is a process known as "lambda abstraction."
Suppose that giving a variable ``x : α`` we can construct an expression ``t : β``.
Then the expression ``fun (x : α) => t``, or, equivalently, ``λ (x : α) => t``, is an object of type ``α → β``.
Think of this as the function from ``α`` to ``β`` which maps any value ``x`` to the value ``t``,
which may depend on ``x``.
```lean
#check fun (x : Nat) => x + 5
#check λ (x : Nat) => x + 5
#check fun x : Nat => x + 5
#check λ x : Nat => x + 5
```
Here are some more examples:
```lean
constant f : Nat → Nat
constant h : Nat → Bool → Nat
#check fun x : Nat => fun y : Bool => h (f x) y -- Nat → Bool → Nat
#check fun (x : Nat) (y : Bool) => h (f x) y -- Nat → Bool → Nat
#check fun x y => h (f x) y -- Nat → Bool → Nat
```
Lean interprets the final three examples as the same expression; in the last expression,
Lean infers the type of ``x`` and ``y`` from the types of ``f`` and ``h``.
Some mathematically common examples of operations of functions can be described in terms of lambda abstraction:
```lean
constant f : Nat → String
constant g : String → Bool
constant b : Bool
#check fun x : Nat => x -- Nat → Nat
#check fun x : Nat => b -- Nat → Bool
#check fun x : Nat => g (f x) -- Nat → Bool
#check fun x => g (f x) -- Nat → Bool
```
Think about what these expressions mean. The expression ``fun x : Nat => x`` denotes the identity function on ``Nat``,
the expression ``fun x : α => b`` denotes the constant function that always returns ``b``,
and ``fun x : Nat => g (f x)``, denotes the composition of ``f`` and ``g``.
We can, in general, leave off the type annotation on a variable and let Lean infer it for us.
So, for example, we can write ``fun x => g (f x)`` instead of ``fun x : Nat => g (f x)``.
We can abstract over the constants `f` and `g` in the previous definitions:
```lean
#check fun (g : String → Bool) (f : Nat → String) (x : Nat) => g (f x)
-- (String → Bool) → (Nat → String) → Nat → Bool
```
We can also abstract over types:
```lean
#check fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x)
```
The last expression, for example, denotes the function that takes three types, ``α``, ``β``, and ``γ``, and two functions, ``g : β → γ`` and ``f : α → β``, and returns the composition of ``g`` and ``f``. (Making sense of the type of this function requires an understanding of dependent products, which we will explain below.) Within a lambda expression ``fun x : α => t``, the variable ``x`` is a "bound variable": it is really a placeholder, whose "scope" does not extend beyond ``t``.
For example, the variable ``b`` in the expression ``fun (b : β) (x : α) => b`` has nothing to do with the constant ``b`` declared earlier.
In fact, the expression denotes the same function as ``fun (u : β) (z : α), u``. Formally, the expressions that are the same up to a renaming of bound variables are called *alpha equivalent*, and are considered "the same." Lean recognizes this equivalence.
Notice that applying a term ``t : α → β`` to a term ``s : α`` yields an expression ``t s : β``.
Returning to the previous example and renaming bound variables for clarity, notice the types of the following expressions:
```lean
#check (fun x : Nat => x) 1 -- Nat
#check (fun x : Nat => true) 1 -- Bool
constant f : Nat → String
constant g : String → Bool
#check
(fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x)) Nat String Bool g f 0
-- Bool
```
As expected, the expression ``(fun x : Nat => x) 1`` has type ``Nat``.
In fact, more should be true: applying the expression ``(fun x : Nat => x)`` to ``1`` should "return" the value ``1``. And, indeed, it does:
```lean
#reduce (fun x : Nat => x) 1 -- 1
#reduce (fun x : Nat => true) 1 -- true
constant f : Nat → String
constant g : String → Bool
#reduce
(fun (α β γ : Type) (g : β → γ) (f : α → β) (x : α) => g (f x)) Nat String Bool g f 0
-- g (f 0)
```
The command ``#reduce`` tells Lean to evaluate an expression by *reducing* it to its normal form,
which is to say, carrying out all the computational reductions that are sanctioned by its kernel.
The process of simplifying an expression ``(fun x => t) s`` to ``t[s/x]`` -- that is, ``t`` with ``s`` substituted for the variable ``x`` --
is known as *beta reduction*, and two terms that beta reduce to a common term are called *beta equivalent*.
But the ``#reduce`` command carries out other forms of reduction as well:
```lean
constant m : Nat
constant n : Nat
constant b : Bool
#reduce (m, n).1 -- m
#reduce (m, n).2 -- n
#reduce true && false -- false
#reduce false && b -- false
#reduce b && false -- Bool.rec false false b
#reduce n + 0 -- n
#reduce n + 2 -- Nat.succ (Nat.succ n)
#reduce 2 + 3 -- 5
```
We explain later how these terms are evaluated.
For now, we only wish to emphasize that this is an important feature of dependent type theory:
every term has a computational behavior, and supports a notion of reduction, or *normalization*.
In principle, two terms that reduce to the same value are called *definitionally equal*.
They are considered "the same" by Lean's type checker, and Lean does its best to recognize and support these identifications.
The `#reduce` command is mainly useful to understand why two terms are considered the same.
Lean is also a programming language. It has a compiler to native code and an interpreter.
You can use the command `#eval` to execute expressions, and it is the preferred way of testing your functions.
Note that `#eval` and `#reduce` are *not* equivalent. The command `#eval` first compiles Lean expressions
into an intermediate representation (IR) and then uses an interpreter to execute the generated IR.
Some builtin types (e.g., `Nat`, `String`, `Array`) have a more efficient representation in the IR.
The IR has support for using foreign functions that are opaque to Lean.
In contrast, the ``#reduce`` command relies on a reduction engine similar to the one used in Lean's trusted kernel,
the part of Lean that is responsible for checking and verifying the correctness of expressions and proofs.
It is less efficient than ``#eval``, and treats all foreign functions as opaque constants.
We later discuss other differences between the two commands.

File diff suppressed because one or more lines are too long

View File

@@ -1,66 +0,0 @@
## Introducing Definitions
The ``def`` command provides one important way of defining new objects.
```lean
def foo : (Nat → Nat) → Nat :=
fun f => f 0
#check foo -- (Nat → Nat) → Nat
#print foo
```
We can omit the type when Lean has enough information to infer it:
```lean
def foo :=
fun (f : Nat → Nat) => f 0
```
The general form of a definition is ``def foo : α := bar``. Lean can usually infer the type ``α``, but it is often a good idea to write it explicitly.
This clarifies your intention, and Lean will flag an error if the right-hand side of the definition does not have the right type.
Lean also allows us to use an alternative format that puts the abstracted variables before the colon and omits the lambda:
```lean
def double (x : Nat) : Nat :=
x + x
#print double
#check double 3
#reduce double 3 -- 6
#eval double 3 -- 6
def square (x : Nat) :=
x * x
#print square
#check square 3
#reduce square 3 -- 9
#eval square 3 -- 9
def doTwice (f : Nat → Nat) (x : Nat) : Nat :=
f (f x)
#eval doTwice double 2 -- 8
```
These definitions are equivalent to the following:
```lean
def double : Nat → Nat :=
fun x => x + x
def square : Nat → Nat :=
fun x => x * x
def doTwice : (Nat → Nat) → Nat → Nat :=
fun f x => f (f x)
```
We can even use this approach to specify arguments that are types:
```lean
def compose (α β γ : Type) (g : β → γ) (f : α → β) (x : α) : γ :=
g (f x)
```

View File

@@ -1,369 +0,0 @@
# Significant changes from Lean 3
Lean 4 is not backward compatible with Lean 3.
We have rewritten most of the system, and took the opportunity to cleanup the syntax,
metaprogramming framework, and elaborator. In this section, we go over the most significant
changes.
## Lambda expressions
We do not use `,` anymore to separate the binders from the lambda expression body.
The Lean 3 syntax for lambda expressions was unconventional, and `,` has been overused in Lean 3.
For example, we believe a list of lambda expressions is quite confusing in Lean 3, since `,` is used
to separate the elements of a list, and in the lambda expression itself. We now use `=>` as the separator,
as an example, `fun x => x` is the identity function. One may still use the symbol `λ` as a shorthand for `fun`.
The lambda expression notation has many new features that are not supported in Lean 3.
## Pattern matching
In Lean 4, one can easily create new notation that abbreviates commonly used idioms. One of them is a
`fun` followed by a `match`. In the following examples, we define a few functions using `fun`+`match` notation.
```lean
# namespace ex1
def Prod.str : Nat × Nat String :=
fun (a, b) => "(" ++ toString a ++ ", " ++ toString b ++ ")"
structure Point where
x : Nat
y : Nat
z : Nat
def Point.addX : Point Point Nat :=
fun { x := a, .. } { x := b, .. } => a+b
def Sum.str : Option Nat String :=
fun
| some a => "some " ++ toString a
| none => "none"
# end ex1
```
## Implicit lambdas
In Lean 3 stdlib, we find many [instances](https://github.com/leanprover/lean/blob/master/library/init/category/reader.lean#L39) of the dreadful `@`+`_` idiom.
It is often used when the expected type is a function type with implicit arguments,
and we have a constant (`reader_t.pure` in the example) which also takes implicit arguments. In Lean 4, the elaborator automatically introduces lambdas
for consuming implicit arguments. We are still exploring this feature and analyzing its impact, but the experience so far has been very positive. As an example,
here is the example in the link above using Lean 4 implicit lambdas.
```lean
# variable (ρ : Type) (m : Type Type) [Monad m]
instance : Monad (ReaderT ρ m) where
pure := ReaderT.pure
bind := ReaderT.bind
```
Users can disable the implicit lambda feature by using `@` or writing a lambda expression with `{}` or `[]` binder annotations.
Here are few examples
```lean
# namespace ex2
def id1 : {α : Type} α α :=
fun x => x
def listId : List ({α : Type} α α) :=
(fun x => x) :: []
-- In this example, implicit lambda introduction has been disabled because
-- we use `@` before `fun`
def id2 : {α : Type} α α :=
@fun α (x : α) => id1 x
def id3 : {α : Type} α α :=
@fun α x => id1 x
def id4 : {α : Type} α α :=
fun x => id1 x
-- In this example, implicit lambda introduction has been disabled
-- because we used the binder annotation `{...}`
def id5 : {α : Type} α α :=
fun {α} x => id1 x
# end ex2
```
## Sugar for simple functions
In Lean 3, we can create simple functions from infix operators by using parentheses. For example, `(+1)` is sugar for `fun x, x + 1`. In Lean 4, we generalize this notation using `·` as a placeholder. Here are a few examples:
```lean
# namespace ex3
#check (· + 1)
-- fun a => a + 1
#check (2 - ·)
-- fun a => 2 - a
#eval [1, 2, 3, 4, 5].foldl (·*·) 1
-- 120
def f (x y z : Nat) :=
x + y + z
#check (f · 1 ·)
-- fun a b => f a 1 b
#eval [(1, 2), (3, 4), (5, 6)].map (·.1)
-- [1, 3, 5]
# end ex3
```
As in Lean 3, the notation is activated using parentheses, and the lambda abstraction is created by collecting the nested `·`s.
The collection is interrupted by nested parentheses. In the following example, two different lambda expressions are created.
```lean
#check (Prod.mk · (· + 1))
-- fun a => (a, fun b => b + 1)
```
## Function applications
In Lean 4, we have support for named arguments.
Named arguments enable you to specify an argument for a parameter by matching the argument with
its name rather than with its position in the parameter list.
If you don't remember the order of the parameters but know their names,
you can send the arguments in any order. You may also provide the value for an implicit parameter when
Lean failed to infer it. Named arguments also improve the readability of your code by identifying what
each argument represents.
```lean
def sum (xs : List Nat) :=
xs.foldl (init := 0) (·+·)
#eval sum [1, 2, 3, 4]
-- 10
example {a b : Nat} {p : Nat Nat Nat Prop} (h₁ : p a b b) (h₂ : b = a)
: p a a b :=
Eq.subst (motive := fun x => p a x b) h₂ h₁
```
In the following examples, we illustrate the interaction between named and default arguments.
```lean
def f (x : Nat) (y : Nat := 1) (w : Nat := 2) (z : Nat) :=
x + y + w - z
example (x z : Nat) : f (z := z) x = x + 1 + 2 - z := rfl
example (x z : Nat) : f x (z := z) = x + 1 + 2 - z := rfl
example (x y : Nat) : f x y = fun z => x + y + 2 - z := rfl
example : f = (fun x z => x + 1 + 2 - z) := rfl
example (x : Nat) : f x = fun z => x + 1 + 2 - z := rfl
example (y : Nat) : f (y := 5) = fun x z => x + 5 + 2 - z := rfl
def g {α} [Add α] (a : α) (b? : Option α := none) (c : α) : α :=
match b? with
| none => a + c
| some b => a + b + c
variable {α} [Add α]
example : g = fun (a c : α) => a + c := rfl
example (x : α) : g (c := x) = fun (a : α) => a + x := rfl
example (x : α) : g (b? := some x) = fun (a c : α) => a + x + c := rfl
example (x : α) : g x = fun (c : α) => x + c := rfl
example (x y : α) : g x y = fun (c : α) => x + y + c := rfl
```
In Lean 4, we can use `..` to provide missing explicit arguments as `_`.
This feature combined with named arguments is useful for writing patterns. Here is an example:
```lean
inductive Term where
| var (name : String)
| num (val : Nat)
| add (fn : Term) (arg : Term)
| lambda (name : String) (type : Term) (body : Term)
def getBinderName : Term Option String
| Term.lambda (name := n) .. => some n
| _ => none
def getBinderType : Term Option Term
| Term.lambda (type := t) .. => some t
| _ => none
```
Ellipsis are also useful when explicit argument can be automatically inferred by Lean, and we want
to avoid a sequence of `_`s.
```lean
example (f : Nat Nat) (a b c : Nat) : f (a + b + c) = f (a + (b + c)) :=
congrArg f (Nat.add_assoc ..)
```
In Lean 4, writing `f(x)` in place of `f x` is no longer allowed, you must use whitespace between the function and its arguments (e.g., `f (x)`).
## Dependent function types
Given `α : Type` and `β : α → Type`, `(x : α) → β x` denotes the type of functions `f` with the property that,
for each `a : α`, `f a` is an element of `β a`. In other words, the type of the value returned by `f` depends on its input.
We say `(x : α) → β x` is a dependent function type. In Lean 3, we write the dependent function type `(x : α) → β x` using
one of the following three equivalent notations:
`forall x : α, β x` or `∀ x : α, β x` or `Π x : α, β x`.
The first two were intended to be used for writing propositions, and the latter for writing code.
Although the notation `Π x : α, β x` has historical significance, we have removed it from Lean 4 because
it is awkward to use and often confuses new users. We can still write `forall x : α, β x` and `∀ x : α, β x`.
```lean
#check forall (α : Type), α α
#check (α : Type), α α
#check α : Type, α α
#check α, α α
#check (α : Type) α α
#check {α : Type} (a : Array α) (i : Nat) i < a.size α
#check {α : Type} [ToString α] α String
#check forall {α : Type} (a : Array α) (i : Nat), i < a.size α
#check {α β : Type} α β α × β
```
## The `meta` keyword
In Lean 3, the keyword `meta` is used to mark definitions that can use primitives implemented in C/C++.
These metadefinitions can also call themselves recursively, relaxing the termination
restriction imposed by ordinary type theory. Metadefinitions may also use unsafe primitives such as
`eval_expr (α : Type u) [reflected α] : expr → tactic α`, or primitives that break referential transparency
`tactic.unsafe_run_io`.
The keyword `meta` has been currently removed from Lean 4. However, we may re-introduce it in the future,
but with a much more limited purpose: marking meta code that should not be included in the executables produced by Lean.
The keyword `constant` has been deleted in Lean 4, and `axiom` should be used instead. In Lean 4, the new command `opaque` is used to define an opaque definition. Here are two simple examples:
```lean
# namespace meta1
opaque x : Nat := 1
-- The following example will not type check since `x` is opaque
-- example : x = 1 := rfl
-- We can evaluate `x`
#eval x
-- 1
-- When no value is provided, the elaborator tries to build one automatically for us
-- using the `Inhabited` type class
opaque y : Nat
# end meta1
```
We can instruct Lean to use a foreign function as the implementation for any definition
using the attribute `@[extern "foreign_function"]`. It is the user's responsibility to ensure the
foreign implementation is correct.
However, a user mistake here will only impact the code generated by Lean, and
it will **not** compromise the logical soundness of the system.
That is, you cannot prove `False` using the `@[extern]` attribute.
We use `@[extern]` with definitions when we want to provide a reference implementation in Lean
that can be used for reasoning. When we write a definition such as
```lean
@[extern "lean_nat_add"]
def add : Nat Nat Nat
| a, Nat.zero => a
| a, Nat.succ b => Nat.succ (add a b)
```
Lean assumes that the foreign function `lean_nat_add` implements the reference implementation above.
The `unsafe` keyword allows us to define functions using unsafe features such as general recursion,
and arbitrary type casting. Regular (safe) functions cannot directly use `unsafe` ones since it would
compromise the logical soundness of the system. As in regular programming languages, programs written
using unsafe features may crash at runtime. Here are a few unsafe examples:
```lean
unsafe def unsound : False :=
unsound
#check @unsafeCast
-- {α : Type _} → {β : Type _} → α → β
unsafe def nat2String (x : Nat) : String :=
unsafeCast x
-- The following definition doesn't type check because it is not marked as `unsafe`
-- def nat2StringSafe (x : Nat) : String :=
-- unsafeCast x
```
The `unsafe` keyword is particularly useful when we want to take advantage of an implementation detail of the
Lean execution runtime. For example, we cannot prove in Lean that arrays have a maximum size, but
the runtime used to execute Lean programs guarantees that an array cannot have more than 2^64 (2^32) elements
in a 64-bit (32-bit) machine. We can take advantage of this fact to provide a more efficient implementation for
array functions. However, the efficient version would not be very useful if it can only be used in
unsafe code. Thus, Lean 4 provides the attribute `@[implemented_by functionName]`. The idea is to provide
an unsafe (and potentially more efficient) version of a safe definition or constant. The function `f`
at the attribute `@[implemented_by f]` is very similar to an extern/foreign function,
the key difference is that it is implemented in Lean itself. Again, the logical soundness of the system
cannot be compromised by using the attribute `implemented_by`, but if the implementation is incorrect your
program may crash at runtime. In the following example, we define `withPtrUnsafe a k h` which
executes `k` using the memory address where `a` is stored in memory. The argument `h` is proof
that `k` is a constant function. Then, we "seal" this unsafe implementation at `withPtr`. The proof `h`
ensures the reference implementation `k 0` is correct. For more information, see the article
"Sealing Pointer-Based Optimizations Behind Pure Functions".
```lean
unsafe
def withPtrUnsafe {α β : Type} (a : α) (k : USize β) (h : u, k u = k 0) : β :=
k (ptrAddrUnsafe a)
@[implemented_by withPtrUnsafe]
def withPtr {α β : Type} (a : α) (k : USize β) (h : u, k u = k 0) : β :=
k 0
```
General recursion is very useful in practice, and it would be impossible to implement Lean 4 without it.
The keyword `partial` implements a very simple and efficient approach for supporting general recursion.
Simplicity was key here because of the bootstrapping problem. That is, we had to implement Lean in Lean before
many of its features were implemented (e.g., the tactic framework or support for wellfounded recursion).
Another requirement for us was performance. Functions tagged with `partial` should be as efficient as the ones implemented in mainstream functional programming
languages such as OCaml. When the `partial` keyword is used, Lean generates an auxiliary `unsafe` definition that
uses general recursion, and then defines an opaque constant that is implemented by this auxiliary definition.
This is very simple, efficient, and is sufficient for users that want to use Lean as a regular programming language.
A `partial` definition cannot use unsafe features such as `unsafeCast` and `ptrAddrUnsafe`, and it can only be used to
implement types we already known to be inhabited. Finally, since we "seal" the auxiliary definition using an opaque
constant, we cannot reason about `partial` definitions.
We are aware that proof assistants such as Isabelle provide a framework for defining partial functions that does not
prevent users from proving properties about them. This kind of framework can be implemented in Lean 4. Actually,
it can be implemented by users since Lean 4 is an extensible system. The developers current have no plans to implement
this kind of support for Lean 4. However, we remark that users can implement it using a function that traverses
the auxiliary unsafe definition generated by Lean, and produces a safe one using an approach similar to the one used in Isabelle.
```lean
# namespace partial1
partial def f (x : Nat) : IO Unit := do
IO.println x
if x < 100 then
f (x+1)
#eval f 98
# end partial1
```
## Library changes
These are changes to the library which may trip up Lean 3 users:
- `List` is no longer a monad.
## Style changes
Coding style changes have also been made:
- Term constants and variables are now `lowerCamelCase` rather than `snake_case`
- Type constants are now `UpperCamelCase`, eg `Nat`, `List`. Type variables are still lower case greek letters. Functors are still lower case latin `(m : Type → Type) [Monad m]`.
- When defining typeclasses, prefer not to use "has". Eg `ToString` or `Add` instead of `HasToString` or `HasAdd`.
- Prefer `return` to `pure` in monad expressions.
- Pipes `<|` are preferred to dollars `$` for function application.
- Declaration bodies should always be indented:
```lean
inductive Hello where
| foo
| bar
structure Point where
x : Nat
y : Nat
def Point.addX : Point → Point → Nat :=
fun { x := a, .. } { x := b, .. } => a + b
```
- In structures and typeclass definitions, prefer `where` to `:=` and don't surround fields with parentheses. (Shown in `Point` above)

View File

@@ -1,180 +0,0 @@
Lexical Structure
=================
This section describes the detailed lexical structure of the Lean
language.
A Lean program consists of a stream of UTF-8 tokens where each token
is one of the following:
```
token: symbol | command | ident | string | raw_string | char | numeral |
: decimal | doc_comment | mod_doc_comment | field_notation
```
Tokens can be separated by the whitespace characters space, tab, line
feed, and carriage return, as well as comments. Single-line comments
start with ``--``, whereas multi-line comments are enclosed by ``/-``
and ``-/`` and can be nested.
Symbols and Commands
====================
.. *(TODO: list built-in symbols and command tokens?)*
Symbols are static tokens that are used in term notations and
commands. They can be both keyword-like (e.g. the `have
<structured_proofs>` keyword) or use arbitrary Unicode characters.
Command tokens are static tokens that prefix any top-level declaration
or action. They are usually keyword-like, with transitory commands
like `#print <instructions>` prefixed by the ``#`` character. The set
of built-in commands is listed in [Other Commands](./other_commands.md).
Users can dynamically extend the sets of both symbols (via the
commands listed in [Quoted Symbols](#quoted-symbols) and command
tokens (via the `[user_command] <attributes>` attribute).
.. _identifiers:
Identifiers
===========
An *atomic identifier*, or *atomic name*, is (roughly) an alphanumeric
string that does not begin with a numeral. A (hierarchical)
*identifier*, or *name*, consists of one or more atomic names
separated by periods.
Parts of atomic names can be escaped by enclosing them in pairs of French double quotes ``«»``.
```lean
def Foo.«bar.baz» := 0 -- name parts ["Foo", "bar.baz"]
```
```
ident: atomic_ident | ident "." atomic_ident
atomic_ident: atomic_ident_start atomic_ident_rest*
atomic_ident_start: letterlike | "_" | escaped_ident_part
letterlike: [a-zA-Z] | greek | coptic | letterlike_symbols
greek: <[α-ωΑ-Ωἀ-] except for [λΠΣ]>
coptic: [ϊ-ϻ]
letterlike_symbols: [℀-⅏]
escaped_ident_part: "«" [^«»\r\n\t]* "»"
atomic_ident_rest: atomic_ident_start | [0-9'ⁿ] | subscript
subscript: [₀-₉ₐ-ₜᵢ-ᵪⱼ]
```
String Literals
===============
String literals are enclosed by double quotes (``"``). They may contain line breaks, which are conserved in the string value. Backslash (`\`) is a special escape character which can be used to the following
special characters:
- `\\` represents an escaped backslash, so this escape causes one backslash to be included in the string.
- `\"` puts a double quote in the string.
- `\'` puts an apostrophe in the string.
- `\n` puts a new line character in the string.
- `\t` puts a tab character in the string.
- `\xHH` puts the character represented by the 2 digit hexadecimal into the string. For example
"this \x26 that" which become "this & that". Values above 0x80 will be interpreted according to the
[Unicode table](https://unicode-table.com/en/) so "\xA9 Copyright 2021" is "© Copyright 2021".
- `\uHHHH` puts the character represented by the 4 digit hexadecimal into the string, so the following
string "\u65e5\u672c" will become "日本" which means "Japan".
- `\` followed by a newline and then any amount of whitespace is a "gap" that is equivalent to the empty string,
useful for letting a string literal span across multiple lines. Gaps spanning multiple lines can be confusing,
so the parser raises an error if the trailing whitespace contains any newlines.
So the complete syntax is:
```
string : '"' string_item '"'
string_item : string_char | char_escape | string_gap
string_char : [^"\\]
char_escape : "\" ("\" | '"' | "'" | "n" | "t" | "x" hex_char{2} | "u" hex_char{4})
hex_char : [0-9a-fA-F]
string_gap : "\" newline whitespace*
```
Raw String Literals
===================
Raw string literals are string literals without any escape character processing.
They begin with `r##...#"` (with zero or more `#` characters) and end with `"#...##` (with the same number of `#` characters).
The contents of a raw string literal may contain `"##..#` so long as the number of `#` characters
is less than the number of `#` characters used to begin the raw string literal.
```
raw_string : raw_string_aux(0) | raw_string_aux(1) | raw_string_aux(2) | ...
raw_string_aux(n) : 'r' '#'{n} '"' raw_string_item '"' '#'{n}
raw_string_item(n) : raw_string_char | raw_string_quote(n)
raw_string_char : [^"]
raw_string_quote(n) : '"' '#'{0..n-1}
```
Char Literals
=============
Char literals are enclosed by single quotes (``'``).
```
char : "'" char_item "'"
char_item : char_char | char_escape
char_char : [^'\\]
```
Numeric Literals
================
Numeric literals can be specified in various bases.
```
numeral : numeral10 | numeral2 | numeral8 | numeral16
numeral10 : [0-9]+ ("_"+ [0-9]+)*
numeral2 : "0" [bB] ("_"* [0-1]+)+
numeral8 : "0" [oO] ("_"* [0-7]+)+
numeral16 : "0" [xX] ("_"* hex_char+)+
```
Floating point literals are also possible with optional exponent:
```
float : numeral10 "." numeral10? [eE[+-]numeral10]
```
For example:
```
constant w : Int := 55
constant x : Nat := 26085
constant y : Nat := 0x65E5
constant z : Float := 2.548123e-05
constant b : Bool := 0b_11_01_10_00
```
Note: that negative numbers are created by applying the "-" negation prefix operator to the number, for example:
```
constant w : Int := -55
```
Doc Comments
============
A special form of comments, doc comments are used to document modules
and declarations.
```
doc_comment: "/--" ([^-] | "-" [^/])* "-/"
mod_doc_comment: "/-!" ([^-] | "-" [^/])* "-/"
```
Field Notation
==============
Trailing field notation tokens are used in expressions such as
``(1+1).to_string``. Note that ``a.toString`` is a single
[Identifier](#identifiers), but may be interpreted as a field
notation expression by the parser.
```
field_notation: "." ([0-9]+ | atomic_ident)
```

View File

@@ -1,134 +0,0 @@
# Arithmetic as an embedded domain-specific language
Let's parse another classic grammar, the grammar of arithmetic expressions with
addition, multiplication, integers, and variables. In the process, we'll learn
how to:
- Convert identifiers such as `x` into strings within a macro.
- add the ability to "escape" the macro context from within the macro. This is useful to interpret identifiers with their _original_ meaning (predefined values)
instead of their new meaning within a macro (treat as a symbol).
Let's begin with the simplest thing possible. We'll define an AST, and use operators `+` and `*` to denote
building an arithmetic AST.
Here's the AST that we will be parsing:
```lean,ignore
{{#include metaprogramming-arith.lean:1:5}}
```
We declare a syntax category to describe the grammar that we will be parsing.
See that we control the precedence of `+` and `*` by writing `syntax:50` for addition and `syntax:60` for multiplication,
indicating that multiplication binds tighter than addition (higher the number, tighter the binding).
This allows us to declare _precedence_ when defining new syntax.
```lean,ignore
{{#include metaprogramming-arith.lean:7:13}}
```
Further, if we look at `syntax:60 arith:60 "+" arith:61 : arith`, the
precedence declarations at `arith:60 "+" arith:61` conveys that the left
argument must have precedence at least `60` or greater, and the right argument
must have precedence at least`61` or greater. Note that this forces left
associativity. To understand this, let's compare two hypothetical parses:
```
-- syntax:60 arith:60 "+" arith:61 : arith -- Arith.add
-- a + b + c
(a:60 + b:61):60 + c
a + (b:60 + c:61):60
```
In the parse tree of `a + (b:60 + c:61):60`, we see that the right argument `(b + c)` is given the precedence `60`. However,
the rule for addition expects the right argument to have a precedence of **at least** 61, as witnessed by the `arith:61` at
the right-hand-side of `syntax:60 arith:60 "+" arith:61 : arith`. Thus, the rule `syntax:60 arith:60 "+" arith:61 : arith`
ensures that addition is left associative.
Since addition is declared arguments of precedence `60/61` and multiplication with `70/71`, this causes multiplication to bind
tighter than addition. Once again, let's compare two hypothetical parses:
```
-- syntax:60 arith:60 "+" arith:61 : arith -- Arith.add
-- syntax:70 arith:70 "*" arith:71 : arith -- Arith.mul
-- a * b + c
a * (b:60 + c:61):60
(a:70 * b:71):70 + c
```
While parsing `a * (b + c)`, `(b + c)` is assigned a precedence `60` by the addition rule. However, multiplication expects
the right argument to have precedence **at least** 71. Thus, this parse is invalid. In contrast, `(a * b) + c` assigns
a precedence of `70` to `(a * b)`. This is compatible with addition which expects the left argument to have precedence
**at least `60` ** (`70` is greater than `60`). Thus, the string `a * b + c` is parsed as `(a * b) + c`.
For more details, please look at the [Lean manual on syntax extensions](./notation.md#notations-and-precedence).
To go from strings into `Arith`, we define a macro to
translate the syntax category `arith` into an `Arith` inductive value that
lives in `term`:
```lean,ignore
{{#include metaprogramming-arith.lean:15:16}}
```
Our macro rules perform the "obvious" translation:
```lean,ignore
{{#include metaprogramming-arith.lean:18:23}}
```
And some examples:
```lean,ignore
{{#include metaprogramming-arith.lean:25:41}}
```
Writing variables as strings, such as `"x"` gets old; wouldn't it be so much
prettier if we could write `x * y`, and have the macro translate this into `Arith.mul (Arith.Symbol "x") (Arith.mul "y")`?
We can do this, and this will be our first taste of manipulating macro variables --- we'll use `x.getId` instead of directly evaluating `$x`.
We also write a macro rule for `Arith|` that translates an identifier into
a string, using `$(Lean.quote (toString x.getId))`:
```lean,ignore
{{#include metaprogramming-arith.lean:43:46}}
```
Let's test and see that we can now write expressions such as `x * y` directly instead of having to write `"x" * "y"`:
```lean,ignore
{{#include metaprogramming-arith.lean:48:51}}
```
We now show an unfortunate consequence of the above definitions. Suppose we want to build `(x + y) + z`.
Since we already have defined `xPlusY` as `x + y`, perhaps we should reuse it! Let's try:
```lean,ignore
#check `[Arith| xPlusY + z] -- Arith.add (Arith.symbol "xPlusY") (Arith.symbol "z")
```
Whoops, that didn't work! What happened? Lean treats `xPlusY` _itself_ as an identifier! So we need to add some syntax
to be able to "escape" the `Arith|` context. Let's use the syntax `<[ $e:term ]>` to mean: evaluate `$e` as a real term,
not an identifier. The macro looks like follows:
```lean,ignore
{{#include metaprogramming-arith.lean:53:56}}
```
Let's try our previous example:
```lean,ignore
{{#include metaprogramming-arith.lean:58:58}}
```
Perfect!
In this tutorial, we expanded on the previous tutorial to parse a more
realistic grammar with multiple levels of precedence, how to parse identifiers directly
within a macro, and how to provide an escape from within the macro context.
#### Full code listing
```lean
{{#include metaprogramming-arith.lean}}
```

View File

@@ -1,12 +0,0 @@
Mission
=======
Empower software developers to design, develop, and reason about programs.
Empower mathematicians and scientists to design, develop, and reason about formal models.
How
---
Lean is an efficient functional programming language based on dependent type theory.
It is under heavy development, but it already generates very efficient code.
It also has a powerful meta-programming framework, extensible parser, and IDE support based on LSP.

View File

@@ -1 +0,0 @@
# Other Commands

View File

@@ -1,83 +0,0 @@
/* Pygments stylesheet generated by Alectryon (style=None) */
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll, .code .hll { background-color: #ffffcc }
.highlight .c, .code .c { color: #555753; font-style: italic } /* Comment */
.highlight .err, .code .err { color: #a40000; border: 1px solid #cc0000 } /* Error */
.highlight .g, .code .g { color: #000000 } /* Generic */
.highlight .k, .code .k { color: #8f5902 } /* Keyword */
.highlight .l, .code .l { color: #2e3436 } /* Literal */
.highlight .n, .code .n { color: #000000 } /* Name */
.highlight .o, .code .o { color: #000000 } /* Operator */
.highlight .x, .code .x { color: #2e3436 } /* Other */
.highlight .p, .code .p { color: #000000 } /* Punctuation */
.highlight .ch, .code .ch { color: #555753; font-weight: bold; font-style: italic } /* Comment.Hashbang */
.highlight .cm, .code .cm { color: #555753; font-style: italic } /* Comment.Multiline */
.highlight .cp, .code .cp { color: #3465a4; font-style: italic } /* Comment.Preproc */
.highlight .cpf, .code .cpf { color: #555753; font-style: italic } /* Comment.PreprocFile */
.highlight .c1, .code .c1 { color: #555753; font-style: italic } /* Comment.Single */
.highlight .cs, .code .cs { color: #3465a4; font-weight: bold; font-style: italic } /* Comment.Special */
.highlight .gd, .code .gd { color: #a40000 } /* Generic.Deleted */
.highlight .ge, .code .ge { color: #000000; font-style: italic } /* Generic.Emph */
.highlight .gr, .code .gr { color: #a40000 } /* Generic.Error */
.highlight .gh, .code .gh { color: #a40000; font-weight: bold } /* Generic.Heading */
.highlight .gi, .code .gi { color: #4e9a06 } /* Generic.Inserted */
.highlight .go, .code .go { color: #000000; font-style: italic } /* Generic.Output */
.highlight .gp, .code .gp { color: #8f5902 } /* Generic.Prompt */
.highlight .gs, .code .gs { color: #000000; font-weight: bold } /* Generic.Strong */
.highlight .gu, .code .gu { color: #000000; font-weight: bold } /* Generic.Subheading */
.highlight .gt, .code .gt { color: #000000; font-style: italic } /* Generic.Traceback */
.highlight .kc, .code .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */
.highlight .kd, .code .kd { color: #4e9a06; font-weight: bold } /* Keyword.Declaration */
.highlight .kn, .code .kn { color: #4e9a06; font-weight: bold } /* Keyword.Namespace */
.highlight .kp, .code .kp { color: #204a87 } /* Keyword.Pseudo */
.highlight .kr, .code .kr { color: #8f5902 } /* Keyword.Reserved */
.highlight .kt, .code .kt { color: #204a87 } /* Keyword.Type */
.highlight .ld, .code .ld { color: #2e3436 } /* Literal.Date */
.highlight .m, .code .m { color: #2e3436 } /* Literal.Number */
.highlight .s, .code .s { color: #ad7fa8 } /* Literal.String */
.highlight .na, .code .na { color: #c4a000 } /* Name.Attribute */
.highlight .nb, .code .nb { color: #75507b } /* Name.Builtin */
.highlight .nc, .code .nc { color: #204a87 } /* Name.Class */
.highlight .no, .code .no { color: #ce5c00 } /* Name.Constant */
.highlight .nd, .code .nd { color: #3465a4; font-weight: bold } /* Name.Decorator */
.highlight .ni, .code .ni { color: #c4a000; text-decoration: underline } /* Name.Entity */
.highlight .ne, .code .ne { color: #cc0000 } /* Name.Exception */
.highlight .nf, .code .nf { color: #a40000 } /* Name.Function */
.highlight .nl, .code .nl { color: #3465a4; font-weight: bold } /* Name.Label */
.highlight .nn, .code .nn { color: #000000 } /* Name.Namespace */
.highlight .nx, .code .nx { color: #000000 } /* Name.Other */
.highlight .py, .code .py { color: #000000 } /* Name.Property */
.highlight .nt, .code .nt { color: #a40000 } /* Name.Tag */
.highlight .nv, .code .nv { color: #ce5c00 } /* Name.Variable */
.highlight .ow, .code .ow { color: #8f5902 } /* Operator.Word */
.highlight .w, .code .w { color: #d3d7cf; text-decoration: underline } /* Text.Whitespace */
.highlight .mb, .code .mb { color: #2e3436 } /* Literal.Number.Bin */
.highlight .mf, .code .mf { color: #2e3436 } /* Literal.Number.Float */
.highlight .mh, .code .mh { color: #2e3436 } /* Literal.Number.Hex */
.highlight .mi, .code .mi { color: #2e3436 } /* Literal.Number.Integer */
.highlight .mo, .code .mo { color: #2e3436 } /* Literal.Number.Oct */
.highlight .sa, .code .sa { color: #ad7fa8 } /* Literal.String.Affix */
.highlight .sb, .code .sb { color: #ad7fa8 } /* Literal.String.Backtick */
.highlight .sc, .code .sc { color: #ad7fa8; font-weight: bold } /* Literal.String.Char */
.highlight .dl, .code .dl { color: #ad7fa8 } /* Literal.String.Delimiter */
.highlight .sd, .code .sd { color: #ad7fa8 } /* Literal.String.Doc */
.highlight .s2, .code .s2 { color: #ad7fa8 } /* Literal.String.Double */
.highlight .se, .code .se { color: #ad7fa8; font-weight: bold } /* Literal.String.Escape */
.highlight .sh, .code .sh { color: #ad7fa8; text-decoration: underline } /* Literal.String.Heredoc */
.highlight .si, .code .si { color: #ce5c00 } /* Literal.String.Interpol */
.highlight .sx, .code .sx { color: #ad7fa8 } /* Literal.String.Other */
.highlight .sr, .code .sr { color: #ad7fa8 } /* Literal.String.Regex */
.highlight .s1, .code .s1 { color: #ad7fa8 } /* Literal.String.Single */
.highlight .ss, .code .ss { color: #8f5902 } /* Literal.String.Symbol */
.highlight .bp, .code .bp { color: #5c35cc } /* Name.Builtin.Pseudo */
.highlight .fm, .code .fm { color: #a40000 } /* Name.Function.Magic */
.highlight .vc, .code .vc { color: #ce5c00 } /* Name.Variable.Class */
.highlight .vg, .code .vg { color: #ce5c00; text-decoration: underline } /* Name.Variable.Global */
.highlight .vi, .code .vi { color: #ce5c00 } /* Name.Variable.Instance */
.highlight .vm, .code .vm { color: #ce5c00 } /* Name.Variable.Magic */
.highlight .il, .code .il { color: #2e3436 } /* Literal.Number.Integer.Long */
.hljs-doctag { color: green }
.hljs-comment { color: green }

View File

@@ -1,23 +0,0 @@
# Quickstart
These instructions will walk you through setting up Lean 4 together with VS Code as an editor for Lean 4.
See [Setup](./setup.md) for supported platforms and other ways to set up Lean 4.
1. Install [VS Code](https://code.visualstudio.com/).
1. Launch VS Code and install the `Lean 4` extension by clicking on the 'Extensions' sidebar entry and searching for 'Lean 4'.
![installing the vscode-lean4 extension](images/code-ext.png)
1. Open the Lean 4 setup guide by creating a new text file using 'File > New Text File' (`Ctrl+N` / `Cmd+N`), clicking on the ∀-symbol in the top right and selecting 'Documentation… > Docs: Show Setup Guide'.
![show setup guide](images/show-setup-guide.png)
1. Follow the Lean 4 setup guide. It will:
- walk you through learning resources for Lean,
- teach you how to set up Lean's dependencies on your platform,
- install Lean 4 for you at the click of a button,
- help you set up your first project.
![setup guide](images/setup_guide.png)

View File

@@ -1,3 +0,0 @@
# The Lean Reference Manual
The latest version of the Lean reference manual is available [here](https://lean-lang.org/doc/reference/latest).

View File

@@ -1,23 +0,0 @@
Semantic Highlighting
---------------------
The Lean language server provides semantic highlighting information to editors. In order to benefit from this in VSCode, you may need to activate the "Editor > Semantic Highlighting" option in the preferences (this is translates to `"editor.semanticHighlighting.enabled": true,`
in `settings.json`). The default option here is to let your color theme decides whether it activates semantic highlighting (the default themes Dark+ and Light+ do activate it for instance).
However this may be insufficient if your color theme does not distinguish enough syntax categories or distinguishes them very subtly. For instance the default Light+ theme uses color `#001080` for variables. This is awfully close to `#000000` that is used as the default text color. This makes it very easy to miss an accidental use of [auto bound implicit arguments](https://lean-lang.org/lean4/doc/autobound.html). For instance in
```lean
def my_id (n : nat) := n
```
maybe `nat` is a typo and `Nat` was intended. If your color theme is good enough then you should see that `n` and `nat` have the same color since they are both marked as variables by semantic highlighting. If you rather write `(n : Nat)` then `n` keeps its variable color but `Nat` gets the default text color.
If you use such a bad theme, you can fix things by modifying the `Semantic Token Color Customizations` configuration. This cannot be done directly in the preferences dialog but you can click on "Edit in settings.json" to directly edit the settings file. Beware that you must save this file (in the same way you save any file opened in VSCode) before seeing any effect in other tabs or VSCode windows.
In the main config object, you can add something like
```
"editor.semanticTokenColorCustomizations": {
"[Default Light+]": {"rules": {"function": "#ff0000", "property": "#00ff00", "variable": "#ff00ff"}}
},
```
The colors in this example are not meant to be nice but to be easy to spot in your file when testing. Of course you need to replace `Default Light+` with the name of your theme, and you can customize several themes if you use several themes. VSCode will display small colored boxes next to the HTML color specifications. Hovering on top of a color specification opens a convenient color picker dialog.
In order to understand what `function`, `property` and `variable` mean in the above example, the easiest path is to open a Lean file and ask VSCode about its classification of various bits of your file. Open the command palette with Ctrl-shift-p (or ⌘-shift-p on a Mac) and search for "Inspect Editor Tokens and Scopes" (typing the word "tokens" should be enough to see it). You can then click on any word in your file and look if there is a "semantic token type" line in the displayed information.

View File

@@ -1,65 +0,0 @@
# Supported Platforms
### Tier 1
Platforms built & tested by our CI, available as binary releases via elan (see below)
* x86-64 Linux with glibc 2.26+
* x86-64 macOS 10.15+
* aarch64 (Apple Silicon) macOS 10.15+
* x86-64 Windows 11 (any version), Windows 10 (version 1903 or higher), Windows Server 2022, Windows Server 2025
### Tier 2
Platforms cross-compiled but not tested by our CI, available as binary releases
Releases may be silently broken due to the lack of automated testing.
Issue reports and fixes are welcome.
* aarch64 Linux with glibc 2.27+
* x86 (32-bit) Linux
* Emscripten Web Assembly
<!--
### Tier 3
Platforms that are known to work from manual testing, but do not come with CI or official releases
-->
# Setting Up Lean
See also the [quickstart](./quickstart.md) instructions for a standard setup with VS Code as the editor.
Release builds for all supported platforms are available at <https://github.com/leanprover/lean4/releases>.
Instead of downloading these and setting up the paths manually, however, it is recommended to use the Lean version manager [`elan`](https://github.com/leanprover/elan) instead:
```sh
$ elan self update # in case you haven't updated elan in a while
# download & activate latest Lean 4 stable release (https://github.com/leanprover/lean4/releases)
$ elan default leanprover/lean4:stable
```
## `lake`
Lean 4 comes with a package manager named `lake`.
Use `lake init foo` to initialize a Lean package `foo` in the current directory, and `lake build` to typecheck and build it as well as all its dependencies. Use `lake help` to learn about further commands.
The general directory structure of a package `foo` is
```sh
lakefile.lean # package configuration
lean-toolchain # specifies the lean version to use
Foo.lean # main file, import via `import Foo`
Foo/
A.lean # further files, import via e.g. `import Foo.A`
A/... # further nesting
.lake/ # `lake` build output directory
```
After running `lake build` you will see a binary named `./.lake/build/bin/foo` and when you run it you should see the output:
```
Hello, world!
```
## Editing
Lean implements the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) that can be used for interactive development in [Emacs](https://github.com/leanprover/lean4-mode), [VS Code](https://github.com/leanprover-community/vscode-lean4), and possibly other editors.
Changes must be saved to be visible in other files, which must then be invalidated using an editor command (see links above).

View File

@@ -1,91 +0,0 @@
## Simple Type Theory
"Type theory" gets its name from the fact that every expression has an associated *type*.
For example, in a given context, ``x + 0`` may denote a natural number and ``f`` may denote a function on the natural numbers.
For those that don't like math, a Lean natural number is an arbitrary-precision unsigned integer.
Here are some examples of how we can declare objects in Lean and check their types.
```lean
/- Declare some constants. -/
constant m : Nat -- m is a natural number
constant n : Nat
constant b1 : Bool -- b1 is a Boolean
constant b2 : Bool
/- Check their types. -/
#check m -- output: Nat
#check n
#check n + 0 -- Nat
#check m * (n + 0) -- Nat
#check b1 -- Bool
#check b1 && b2 -- "&&" is the Boolean and
#check b1 || b2 -- Boolean or
#check true -- Boolean "true"
```
Any text between ``/-`` and ``-/`` constitutes a comment block that is ignored by Lean.
Similarly, two dashes `--` indicate that the rest of the line contains a comment that is also ignored.
Comment blocks can be nested, making it possible to "comment out" chunks of code, just as in many programming languages.
The ``constant`` command introduce new constant symbols into the working environment.
The ``#check`` command asks Lean to report their types; in Lean, auxiliary commands that query the system for
information typically begin with the hash symbol. You should try declaring some constants and type checking
some expressions on your own. Declaring new objects in this way is a good way to experiment with the system.
What makes simple type theory powerful is that one can build new types out of others.
For example, if ``a`` and ``b`` are types, ``a -> b`` denotes the type of functions from ``a`` to ``b``,
and ``a × b`` denotes the type of pairs consisting of an element of ``a``
paired with an element of ``b``, also known as the *Cartesian product*.
Note that `×` is a Unicode symbol. We believe that judicious use of Unicode improves legibility,
and all modern editors have great support for it. In the Lean standard library, we often use
Greek letters to denote types, and the Unicode symbol `` as a more compact version of `->`.
```lean
constant m : Nat
constant n : Nat
constant f : Nat → Nat -- type the arrow as "\to" or "\r"
constant f' : Nat -> Nat -- alternative ASCII notation
constant p : Nat × Nat -- type the product as "\times"
constant q : Prod Nat Nat -- alternative notation
constant g : Nat → Nat → Nat
constant g' : Nat → (Nat → Nat) -- has the same type as g!
constant h : Nat × Nat → Nat
constant F : (Nat → Nat) → Nat -- a "functional"
#check f -- Nat → Nat
#check f n -- Nat
#check g m n -- Nat
#check g m -- Nat → Nat
#check (m, n) -- Nat × Nat
#check p.1 -- Nat
#check p.2 -- Nat
#check (m, n).1 -- Nat
#check (p.1, n) -- Nat × Nat
#check F f -- Nat
```
Once again, you should try some examples on your own.
Let us dispense with some basic syntax. You can enter the unicode arrow ``→`` by typing ``\to`` or ``\r``.
You can also use the ASCII alternative ``->``, so the expressions ``Nat -> Nat`` and ``Nat → Nat`` mean the same thing.
Both expressions denote the type of functions that take a natural number as input and return a natural number as output.
The unicode symbol ``×`` for the Cartesian product is entered as ``\times``.
We will generally use lower-case Greek letters like ``α``, ``β``, and ``γ`` to range over types.
You can enter these particular ones with ``\a``, ``\b``, and ``\g``.
There are a few more things to notice here. First, the application of a function ``f`` to a value ``x`` is denoted ``f x``.
Second, when writing type expressions, arrows associate to the *right*; for example, the type of ``g`` is ``Nat → (Nat → Nat)``.
Thus we can view ``g`` as a function that takes natural numbers and returns another function that takes a natural number and
returns a natural number.
In type theory, this is generally more convenient than writing ``g`` as a function that takes a pair of natural numbers as input
and returns a natural number as output. For example, it allows us to "partially apply" the function ``g``.
The example above shows that ``g m`` has type ``Nat → Nat``, that is, the function that "waits" for a second argument, ``n``,
and then returns ``g m n``. Taking a function ``h`` of type ``Nat × Nat → Nat`` and "redefining" it to look like ``g`` is a process
known as *currying*, something we will come back to below.
By now you may also have guessed that, in Lean, ``(m, n)`` denotes the ordered pair of ``m`` and ``n``,
and if ``p`` is a pair, ``p.1`` and ``p.2`` denote the two projections.

View File

@@ -1,80 +0,0 @@
# Balanced Parentheses as an Embedded Domain Specific Language
Let's look at how to use macros to extend the Lean 4 parser and embed a language for building _balanced parentheses_.
This language accepts strings given by the [BNF grammar](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form)
```
Dyck ::=
"(" Dyck ")"
| "{" Dyck "}"
| end
```
We begin by defining an inductive data type of the grammar we wish to parse:
```lean,ignore
inductive Dyck : Type where
| round : Dyck → Dyck -- ( <inner> )
| curly : Dyck → Dyck -- { <inner> }
| leaf : Dyck
```
We begin by declaring a _syntax category_ using the `declare_syntax_cat <category>` command.
This names our grammar and allows us to specify parsing rules associated with our grammar.
```lean,ignore
declare_syntax_cat brack
```
Next, we specify the grammar using the `syntax <parse rule>` command:
```lean,ignore
syntax "end" : brack
```
The above means that the token "end" lives in syntax category `brack`.
Similarly, we declare the rules `"(" Dyck ")"` and `"{" Dyck "}"` using the rules:
```lean,ignore
syntax "(" brack ")" : brack
syntax "{" brack "}" : brack
```
Finally, we need a way to build _Lean 4 terms_ from this grammar -- that is, we must translate out of this
grammar into a `Dyck` value, which is a Lean 4 term. For this, we create a new kind of "quotation" that
consumes syntax in `brack` and produces a `term`.
```lean,ignore
syntax "`[Dyck| " brack "]" : term
```
To specify the transformation rules, we use `macro_rules` to declare how the syntax `` `[Dyck| <brack>] ``
produces terms. This is written using a pattern-matching style syntax, where the left-hand side
declares the syntax pattern to be matched, and the right-hand side declares the production. Syntax placeholders (antiquotations)
are introduced via the `$<var-name>` syntax. The right-hand side is
an arbitrary Lean term that we are producing.
```lean,ignore
macro_rules
| `(`[Dyck| end]) => `(Dyck.leaf)
| `(`[Dyck| ($b)]) => `(Dyck.round `[Dyck| $b]) -- recurse
| `(`[Dyck| {$b}]) => `(Dyck.curly `[Dyck| $b]) -- recurse
```
```lean,ignore
#check `[Dyck| end] -- Dyck.leaf
#check `[Dyck| {(end)}] -- Dyck.curl (Dyck.round Dyck.leaf)
```
In summary, we've seen:
- How to declare a syntax category for the Dyck grammar.
- How to specify parse trees of this grammar using `syntax`
- How to translate out of this grammar into Lean 4 terms using `macro_rules`.
The full program listing is given below:
```lean
{{#include syntax_example.lean}}
```

View File

@@ -1,4 +0,0 @@
# Syntax Metaprogramming Examples
- [Balanced Parentheses](./syntax_example.md)
- [Arithmetic DSL](./metaprogramming-arith.md)

View File

@@ -1,91 +0,0 @@
You can copy highlighted code [straight from VS Code](https://code.visualstudio.com/updates/v1_10#_copy-with-syntax-highlighting) to any rich text editor supporting HTML input. For highlighting code in LaTeX, there are two options:
* [listings](https://ctan.org/pkg/listings), which is a common package and simple to set up, but you may run into some restrictions of it and LaTeX around Unicode
* [`minted`](https://ctan.org/pkg/minted), a LaTeX package wrapping the [Pygments](https://pygments.org/) syntax highlighting library. It needs a few more steps to set up, but provides unrestricted support for Unicode when combined with XeLaTeX or LuaLaTex.
## Example with `listings`
Save [`lstlean.tex`](https://raw.githubusercontent.com/leanprover/lean4/master/doc/latex/lstlean.tex) into the same directory, or anywhere in your `TEXINPUTS` path, as the following test file:
```latex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{listings}
\usepackage{amssymb}
\usepackage{color}
\definecolor{keywordcolor}{rgb}{0.7, 0.1, 0.1} % red
\definecolor{tacticcolor}{rgb}{0.0, 0.1, 0.6} % blue
\definecolor{commentcolor}{rgb}{0.4, 0.4, 0.4} % grey
\definecolor{symbolcolor}{rgb}{0.0, 0.1, 0.6} % blue
\definecolor{sortcolor}{rgb}{0.1, 0.5, 0.1} % green
\definecolor{attributecolor}{rgb}{0.7, 0.1, 0.1} % red
\def\lstlanguagefiles{lstlean.tex}
% set default language
\lstset{language=lean}
\begin{document}
\begin{lstlisting}
theorem funext {f₁ f₂ : ∀ (x : α), β x} (h : ∀ x, f₁ x = f₂ x) : f₁ = f₂ := by
show extfunApp (Quotient.mk f₁) = extfunApp (Quotient.mk f₂)
apply congrArg
apply Quotient.sound
exact h
\end{lstlisting}
\end{document}
```
Compile the file via
```bash
$ pdflatex test.tex
```
* for older LaTeX versions, you might need to use `[utf8x]` instead of `[utf8]` with `inputenc`
## Example with `minted`
First [install Pygments](https://pygments.org/download/) (version 2.18 or newer).
Then save the following sample LaTeX file `test.tex` into the same directory:
```latex
\documentclass{article}
\usepackage{fontspec}
% switch to a monospace font supporting more Unicode characters
\setmonofont{FreeMono}
\usepackage{minted}
\newmintinline[lean]{lean4}{bgcolor=white}
\newminted[leancode]{lean4}{fontsize=\footnotesize}
\usemintedstyle{tango} % a nice, colorful theme
\begin{document}
\begin{leancode}
theorem funext {f₁ f₂ : ∀ (x : α), β x} (h : ∀ x, f₁ x = f₂ x) : f₁ = f₂ := by
show extfunApp (Quotient.mk' f₁) = extfunApp (Quotient.mk' f₂)
apply congrArg
apply Quotient.sound
exact h
\end{leancode}
\end{document}
```
You can then compile `test.tex` by executing the following command:
```bash
xelatex --shell-escape test.tex
```
Some remarks:
- either `xelatex` or `lualatex` is required to handle Unicode characters in the code.
- `--shell-escape` is needed to allow `xelatex` to execute `pygmentize` in a shell.
- If the chosen monospace font is missing some Unicode symbols, you can direct them to be displayed using a fallback font or other replacement LaTeX code.
``` latex
\usepackage{newunicodechar}
\newfontfamily{\freeserif}{DejaVu Sans}
\newunicodechar{✝}{\freeserif{✝}}
\newunicodechar{𝓞}{\ensuremath{\mathcal{O}}}
```
- If you are using an old version of Pygments, you can copy
[`lean.py`](https://raw.githubusercontent.com/pygments/pygments/master/pygments/lexers/lean.py) into your working directory,
and use `lean4.py:Lean4Lexer -x` instead of `lean4` above.
If your version of `minted` is v2.7 or newer, but before v3.0,
you will additionally need to follow the workaround described in https://github.com/gpoore/minted/issues/360.

View File

@@ -1,393 +0,0 @@
# Tactics
Tactics are metaprograms, that is, programs that create programs.
Lean is implemented in Lean, you can import its implementation using `import Lean`.
The `Lean` package is part of the Lean distribution.
You can use the functions in the `Lean` package to write your own metaprograms
that automate repetitive tasks when writing programs and proofs.
We provide the **tactic** domain specific language (DSL) for using the tactic framework.
The tactic DSL provides commands for creating terms (and proofs). You
don't need to import the `Lean` package for using the tactic DSL.
Simple extensions can be implemented using macros. More complex extensions require
the `Lean` package. Notation used to write Lean terms can be easily lifted to the tactic DSL.
Tactics are instructions that tell Lean how to construct a term or proof.
Tactics operate on holes also known as goals. Each hole represents a missing
part of the term you are trying to build. Internally these holes are represented
as metavariables. They have a type and a local context. The local context contains
all local variables in scope.
In the following example, we prove the same simple theorem using different tactics.
The keyword `by` instructs Lean to use the tactic DSL to construct a term.
Our initial goal is a hole with type `p q → q p`. The tactic `intro h`
fills this hole using the term `fun h => ?m` where `?m` is a new hole we need to solve.
This hole has type `q p`, and the local context contains `h : p q`.
The tactic `cases` fills the hole using `Or.casesOn h (fun h1 => ?m1) (fun h2 => ?m2)`
where `?m1` and `?m2` are new holes. The tactic `apply Or.inr` fills the hole `?m1`
with the application `Or.inr ?m3`, and `exact h1` fills `?m3` with `h1`.
The tactic `assumption` tries to fill a hole by searching the local context for a term with the same type.
```lean
theorem ex1 : p q q p := by
intro h
cases h with
| inl h1 =>
apply Or.inr
exact h1
| inr h2 =>
apply Or.inl
assumption
#print ex1
/-
theorem ex1 : {p q : Prop} → p q → q p :=
fun {p q : Prop} (h : p q) =>
Or.casesOn h (fun (h1 : p) => Or.inr h1) fun (h2 : q) => Or.inl h2
-/
-- You can use `match-with` in tactics.
theorem ex2 : p q q p := by
intro h
match h with
| Or.inl _ => apply Or.inr; assumption
| Or.inr h2 => apply Or.inl; exact h2
-- As we have the `fun+match` syntax sugar for terms,
-- we have the `intro+match` syntax sugar
theorem ex3 : p q q p := by
intro
| Or.inl h1 =>
apply Or.inr
exact h1
| Or.inr h2 =>
apply Or.inl
assumption
```
The examples above are all structured, but Lean 4 still supports unstructured
proofs. Unstructured proofs are useful when creating reusable scripts that may
discharge different goals.
Here is an unstructured version of the example above.
```lean
theorem ex1 : p q q p := by
intro h
cases h
apply Or.inr
assumption
apply Or.inl
assumption
done -- fails with an error here if there are unsolvable goals
theorem ex2 : p q q p := by
intro h
cases h
focus -- instructs Lean to `focus` on the first goal,
apply Or.inr
assumption
-- it will fail if there are still unsolvable goals here
focus
apply Or.inl
assumption
theorem ex3 : p q q p := by
intro h
cases h
-- You can still use curly braces and semicolons instead of
-- whitespace sensitive notation as in the previous example
{ apply Or.inr;
assumption
-- It will fail if there are unsolved goals
}
{ apply Or.inl;
assumption
}
-- Many tactics tag subgoals. The tactic `cases` tag goals using constructor names.
-- The tactic `case tag => tactics` instructs Lean to solve the goal
-- with the matching tag.
theorem ex4 : p q q p := by
intro h
cases h
case inr =>
apply Or.inl
assumption
case inl =>
apply Or.inr
assumption
-- Same example for curly braces and semicolons aficionados
theorem ex5 : p q q p := by {
intro h;
cases h;
case inr => {
apply Or.inl;
assumption
}
case inl => {
apply Or.inr;
assumption
}
}
```
## Rewrite
TODO
## Pattern matching
As a convenience, pattern-matching has been integrated into tactics such as `intro` and `funext`.
```lean
theorem ex1 : s q r p r q p := by
intro _, hq, _ hp, _
exact hq, hp
theorem ex2 :
(fun (x : Nat × Nat) (y : Nat × Nat) => x.1 + y.2)
=
(fun (x : Nat × Nat) (z : Nat × Nat) => z.2 + x.1) := by
funext (a, b) (c, d)
show a + d = d + a
rw [Nat.add_comm]
```
## Induction
The `induction` tactic now supports user-defined induction principles with
multiple targets (aka major premises).
```lean
/-
theorem Nat.mod.inductionOn
{motive : Nat → Nat → Sort u}
(x y : Nat)
(ind : ∀ x y, 0 < y ∧ y ≤ x → motive (x - y) y → motive x y)
(base : ∀ x y, ¬(0 < y ∧ y ≤ x) → motive x y)
: motive x y :=
-/
theorem ex (x : Nat) {y : Nat} (h : y > 0) : x % y < y := by
induction x, y using Nat.mod.inductionOn with
| ind x y h₁ ih =>
rw [Nat.mod_eq_sub_mod h₁.2]
exact ih h
| base x y h₁ =>
have : ¬ 0 < y ¬ y x := Iff.mp (Decidable.not_and_iff_or_not ..) h₁
match this with
| Or.inl h₁ => exact absurd h h₁
| Or.inr h₁ =>
have hgt : y > x := Nat.gt_of_not_le h₁
rw [ Nat.mod_eq_of_lt hgt] at hgt
assumption
```
## Cases
TODO
## Injection
TODO
## Split
The `split` tactic can be used to split the cases of an if-then-else or
match into new subgoals, which can then be discharged individually.
```lean
def addMoreIfOdd (n : Nat) := if n % 2 = 0 then n + 1 else n + 2
/- Examine each branch of the conditional to show that the result
is always positive -/
example (n : Nat) : 0 < addMoreIfOdd n := by
simp only [addMoreIfOdd]
split
next => exact Nat.zero_lt_succ _
next => exact Nat.zero_lt_succ _
```
```lean
def binToChar (n : Nat) : Option Char :=
match n with
| 0 => some '0'
| 1 => some '1'
| _ => none
example (n : Nat) : (binToChar n).isSome -> n = 0 n = 1 := by
simp only [binToChar]
split
next => exact fun _ => Or.inl rfl
next => exact fun _ => Or.inr rfl
next => intro h; cases h
/- Hypotheses about previous cases can be accessed by assigning them a
name, like `ne_zero` below. Information about the matched term can also
be preserved using the `generalizing` tactic: -/
example (n : Nat) : (n = 0) -> (binToChar n = some '0') := by
simp only [binToChar]
split
case h_1 => intro _; rfl
case h_2 => intro h; cases h
/- Here, we can introduce `n ≠ 0` and `n ≠ 1` this case assumes
neither of the previous cases matched. -/
case h_3 ne_zero _ => intro eq_zero; exact absurd eq_zero ne_zero
```
## Dependent pattern matching
The `match-with` expression implements dependent pattern matching. You can use it to create concise proofs.
```lean
inductive Mem : α List α Prop where
| head (a : α) (as : List α) : Mem a (a::as)
| tail (a b : α) (bs : List α) : Mem a bs Mem a (b::bs)
infix:50 (priority := high) "" => Mem
theorem mem_split {a : α} {as : List α} (h : a as) : s t, as = s ++ a :: t :=
match a, as, h with
| _, _, Mem.head a bs => [], bs, rfl
| _, _, Mem.tail a b bs h =>
match bs, mem_split h with
| _, s, t, rfl => b::s, t, List.cons_append .. rfl
```
In the tactic DSL, the right-hand-side of each alternative in a `match-with` is a sequence of tactics instead of a term.
Here is a similar proof using the tactic DSL.
```lean
# inductive Mem : α List α Prop where
# | head (a : α) (as : List α) : Mem a (a::as)
# | tail (a b : α) (bs : List α) : Mem a bs Mem a (b::bs)
# infix:50 (priority := high) "" => Mem
theorem mem_split {a : α} {as : List α} (h : a as) : s t, as = s ++ a :: t := by
match a, as, h with
| _, _, Mem.head a bs => exists []; exists bs; rfl
| _, _, Mem.tail a b bs h =>
match bs, mem_split h with
| _, s, t, rfl =>
exists b::s; exists t;
rw [List.cons_append]
```
We can use `match-with` nested in tactics.
Here is a similar proof that uses the `induction` tactic instead of recursion.
```lean
# inductive Mem : α List α Prop where
# | head (a : α) (as : List α) : Mem a (a::as)
# | tail (a b : α) (bs : List α) : Mem a bs Mem a (b::bs)
# infix:50 (priority := high) "" => Mem
theorem mem_split {a : α} {as : List α} (h : a as) : s t, as = s ++ a :: t := by
induction as with
| nil => cases h
| cons b bs ih => cases h with
| head a bs => exact [], bs, rfl
| tail a b bs h =>
match bs, ih h with
| _, s, t, rfl =>
exists b::s; exists t
rw [List.cons_append]
```
You can create your own notation using existing tactics. In the following example,
we define a simple `obtain` tactic using macros. We say it is simple because it takes only one
discriminant. Later, we show how to create more complex automation using macros.
```lean
# inductive Mem : α List α Prop where
# | head (a : α) (as : List α) : Mem a (a::as)
# | tail (a b : α) (bs : List α) : Mem a bs Mem a (b::bs)
# infix:50 (priority := high) "" => Mem
macro "obtain " p:term " from " d:term : tactic =>
`(tactic| match $d:term with | $p:term => ?_)
theorem mem_split {a : α} {as : List α} (h : a as) : s t, as = s ++ a :: t := by
induction as with
| cons b bs ih => cases h with
| tail a b bs h =>
obtain s, t, h from ih h
exists b::s; exists t
rw [h, List.cons_append]
| head a bs => exact [], bs, rfl
| nil => cases h
```
## Extensible tactics
In the following example, we define the notation `triv` for the tactic DSL using
the command `syntax`. Then, we use the command `macro_rules` to specify what should
be done when `triv` is used. You can provide different expansions, and the tactic DSL
interpreter will try all of them until one succeeds.
```lean
-- Define a new notation for the tactic DSL
syntax "triv" : tactic
macro_rules
| `(tactic| triv) => `(tactic| assumption)
theorem ex1 (h : p) : p := by
triv
-- You cannot prove the following theorem using `triv`
-- theorem ex2 (x : α) : x = x := by
-- triv
-- Let's extend `triv`. The `by` DSL interpreter
-- tries all possible macro extensions for `triv` until one succeeds
macro_rules
| `(tactic| triv) => `(tactic| rfl)
theorem ex2 (x : α) : x = x := by
triv
theorem ex3 (x : α) (h : p) : x = x p := by
apply And.intro <;> triv
```
# `let-rec`
You can use `let rec` to write local recursive functions. We lifted it to the tactic DSL,
and you can use it to create proofs by induction.
```lean
theorem length_replicateTR {α} (n : Nat) (a : α) : (List.replicateTR n a).length = n := by
let rec aux (n : Nat) (as : List α)
: (List.replicateTR.loop a n as).length = n + as.length := by
match n with
| 0 => rw [Nat.zero_add]; rfl
| n+1 =>
show List.length (List.replicateTR.loop a n (a::as)) = Nat.succ n + as.length
rw [aux n, List.length_cons, Nat.add_succ, Nat.succ_add]
exact aux n []
```
You can also introduce auxiliary recursive declarations using `where` clause after your definition.
Lean converts them into a `let rec`.
```lean
theorem length_replicateTR {α} (n : Nat) (a : α) : (List.replicateTR n a).length = n :=
loop n []
where
loop n as : (List.replicateTR.loop a n as).length = n + as.length := by
match n with
| 0 => rw [Nat.zero_add]; rfl
| n+1 =>
show List.length (List.replicateTR.loop a n (a::as)) = Nat.succ n + as.length
rw [loop n, List.length_cons, Nat.add_succ, Nat.succ_add]
```
# `begin-end` lovers
If you love Lean 3 `begin ... end` tactic blocks and commas, you can define this notation
in Lean 4 using macros in a few lines of code.
```lean
{{#include ../tests/lean/beginEndAsMacro.lean:doc}}
```

View File

@@ -1,200 +0,0 @@
# Tour of Lean
The best way to learn about Lean is to read and write Lean code.
This article will act as a tour through some of the key features of the Lean
language and give you some code snippets that you can execute on your machine.
To learn about setting up a development environment, check out [Setting Up Lean](./setup.md).
There are two primary concepts in Lean: functions and types.
This tour will emphasize features of the language which fall into
these two concepts.
# Functions and Namespaces
The most fundamental pieces of any Lean program are functions organized into namespaces.
[Functions](./functions.md) perform work on inputs to produce outputs,
and they are organized under [namespaces](./namespaces.md),
which are the primary way you group things in Lean.
They are defined using the `def` command,
which give the function a name and define its arguments.
```lean
namespace BasicFunctions
-- The `#eval` command evaluates an expression on the fly and prints the result.
#eval 2+2
-- You use 'def' to define a function. This one accepts a natural number
-- and returns a natural number.
-- Parentheses are optional for function arguments, except for when
-- you use an explicit type annotation.
-- Lean can often infer the type of the function's arguments.
def sampleFunction1 x := x*x + 3
-- Apply the function, naming the function return result using 'def'.
-- The variable type is inferred from the function return type.
def result1 := sampleFunction1 4573
-- This line uses an interpolated string to print the result. Expressions inside
-- braces `{}` are converted into strings using the polymorphic method `toString`
#eval println! "The result of squaring the integer 4573 and adding 3 is {result1}"
-- When needed, annotate the type of a parameter name using '(argument : type)'.
def sampleFunction2 (x : Nat) := 2*x*x - x + 3
def result2 := sampleFunction2 (7 + 4)
#eval println! "The result of applying the 2nd sample function to (7 + 4) is {result2}"
-- Conditionals use if/then/else
def sampleFunction3 (x : Int) :=
if x > 100 then
2*x*x - x + 3
else
2*x*x + x - 37
#eval println! "The result of applying sampleFunction3 to 2 is {sampleFunction3 2}"
end BasicFunctions
```
```lean
-- Lean has first-class functions.
-- `twice` takes two arguments `f` and `a` where
-- `f` is a function from natural numbers to natural numbers, and
-- `a` is a natural number.
def twice (f : Nat Nat) (a : Nat) :=
f (f a)
-- `fun` is used to declare anonymous functions
#eval twice (fun x => x + 2) 10
-- You can prove theorems about your functions.
-- The following theorem states that for any natural number `a`,
-- adding 2 twice produces a value equal to `a + 4`.
theorem twiceAdd2 (a : Nat) : twice (fun x => x + 2) a = a + 4 :=
-- The proof is by reflexivity. Lean "symbolically" reduces both sides of the equality
-- until they are identical.
rfl
-- `(· + 2)` is syntax sugar for `(fun x => x + 2)`. The parentheses + `·` notation
-- is useful for defining simple anonymous functions.
#eval twice (· + 2) 10
-- Enumerated types are a special case of inductive types in Lean,
-- which we will learn about later.
-- The following command creates a new type `Weekday`.
inductive Weekday where
| sunday : Weekday
| monday : Weekday
| tuesday : Weekday
| wednesday : Weekday
| thursday : Weekday
| friday : Weekday
| saturday : Weekday
-- `Weekday` has 7 constructors/elements.
-- The constructors live in the `Weekday` namespace.
-- Think of `sunday`, `monday`, …, `saturday` as being distinct elements of `Weekday`,
-- with no other distinguishing properties.
-- The command `#check` prints the type of a term in Lean.
#check Weekday.sunday
#check Weekday.monday
-- The `open` command opens a namespace, making all declarations in it accessible without
-- qualification.
open Weekday
#check sunday
#check tuesday
-- You can define functions by pattern matching.
-- The following function converts a `Weekday` into a natural number.
def natOfWeekday (d : Weekday) : Nat :=
match d with
| sunday => 1
| monday => 2
| tuesday => 3
| wednesday => 4
| thursday => 5
| friday => 6
| saturday => 7
#eval natOfWeekday tuesday
def isMonday : Weekday Bool :=
-- `fun` + `match` is a common idiom.
-- The following expression is syntax sugar for
-- `fun d => match d with | monday => true | _ => false`.
fun
| monday => true
| _ => false
#eval isMonday monday
#eval isMonday sunday
-- Lean has support for type classes and polymorphic methods.
-- The `toString` method converts a value into a `String`.
#eval toString 10
#eval toString (10, 20)
-- The method `toString` converts values of any type that implements
-- the class `ToString`.
-- You can implement instances of `ToString` for your own types.
instance : ToString Weekday where
toString (d : Weekday) : String :=
match d with
| sunday => "Sunday"
| monday => "Monday"
| tuesday => "Tuesday"
| wednesday => "Wednesday"
| thursday => "Thursday"
| friday => "Friday"
| saturday => "Saturday"
#eval toString (sunday, 10)
def Weekday.next (d : Weekday) : Weekday :=
match d with
| sunday => monday
| monday => tuesday
| tuesday => wednesday
| wednesday => thursday
| thursday => friday
| friday => saturday
| saturday => sunday
#eval Weekday.next Weekday.wednesday
-- Since the `Weekday` namespace has already been opened, you can also write
#eval next wednesday
-- Matching on a parameter like in the previous definition
-- is so common that Lean provides syntax sugar for it. The following
-- function uses it.
def Weekday.previous : Weekday -> Weekday
| sunday => saturday
| monday => sunday
| tuesday => monday
| wednesday => tuesday
| thursday => wednesday
| friday => thursday
| saturday => friday
#eval next (previous wednesday)
-- We can prove that for any `Weekday` `d`, `next (previous d) = d`
theorem Weekday.nextOfPrevious (d : Weekday) : next (previous d) = d :=
match d with
| sunday => rfl
| monday => rfl
| tuesday => rfl
| wednesday => rfl
| thursday => rfl
| friday => rfl
| saturday => rfl
-- You can automate definitions such as `Weekday.nextOfPrevious`
-- using metaprogramming (or "tactics").
theorem Weekday.nextOfPrevious' (d : Weekday) : next (previous d) = d := by
cases d -- A proof by case distinction
all_goals rfl -- Each case is solved using `rfl`
```

View File

@@ -1,5 +0,0 @@
Theorem Proving in Lean
=======================
We strongly encourage you to read the book [Theorem Proving in Lean](https://lean-lang.org/theorem_proving_in_lean4/title_page.html).
Many Lean users consider it to be the Lean Bible.

View File

@@ -1,129 +0,0 @@
## Types as objects
One way in which Lean's dependent type theory extends simple type theory is that types themselves --- entities like ``Nat`` and ``Bool`` ---
are first-class citizens, which is to say that they themselves are objects. For that to be the case, each of them also has to have a type.
```lean
#check Nat -- Type
#check Bool -- Type
#check Nat → Bool -- Type
#check Nat × Bool -- Type
#check Nat → Nat -- ...
#check Nat × Nat → Nat
#check Nat → Nat → Nat
#check Nat → (Nat → Nat)
#check Nat → Nat → Bool
#check (Nat → Nat) → Nat
```
We see that each one of the expressions above is an object of type ``Type``. We can also declare new constants and constructors for types:
```lean
constant α : Type
constant β : Type
constant F : Type → Type
constant G : Type → Type → Type
#check α -- Type
#check F α -- Type
#check F Nat -- Type
#check G α -- Type → Type
#check G α β -- Type
#check G α Nat -- Type
```
Indeed, we have already seen an example of a function of type ``Type → Type → Type``, namely, the Cartesian product.
```lean
constant α : Type
constant β : Type
#check Prod α β -- Type
#check Prod Nat Nat -- Type
```
Here is another example: given any type ``α``, the type ``List α`` denotes the type of lists of elements of type ``α``.
```lean
constant α : Type
#check List α -- Type
#check List Nat -- Type
```
Given that every expression in Lean has a type, it is natural to ask: what type does ``Type`` itself have?
```lean
#check Type -- Type 1
```
We have actually come up against one of the most subtle aspects of Lean's typing system.
Lean's underlying foundation has an infinite hierarchy of types:
```lean
#check Type -- Type 1
#check Type 1 -- Type 2
#check Type 2 -- Type 3
#check Type 3 -- Type 4
#check Type 4 -- Type 5
```
Think of ``Type 0`` as a universe of "small" or "ordinary" types.
``Type 1`` is then a larger universe of types, which contains ``Type 0`` as an element,
and ``Type 2`` is an even larger universe of types, which contains ``Type 1`` as an element.
The list is indefinite, so that there is a ``Type n`` for every natural number ``n``.
``Type`` is an abbreviation for ``Type 0``:
```lean
#check Type
#check Type 0
```
There is also another type universe, ``Prop``, which has special properties.
```lean
#check Prop -- Type
```
We will discuss ``Prop`` later.
We want some operations, however, to be *polymorphic* over type universes. For example, ``List α`` should
make sense for any type ``α``, no matter which type universe ``α`` lives in. This explains the type annotation of the function ``List``:
```lean
#check List -- Type u_1 → Type u_1
```
Here ``u_1`` is a variable ranging over type levels. The output of the ``#check`` command means that whenever ``α`` has type ``Type n``, ``List α`` also has type ``Type n``. The function ``Prod`` is similarly polymorphic:
```lean
#check Prod -- Type u_1 → Type u_2 → Type (max u_1 u_2)
```
To define polymorphic constants and variables, Lean allows us to declare universe variables explicitly using the `universe` command:
```lean
universe u
constant α : Type u
#check α
```
Equivalently, we can write ``Type _`` to avoid giving the arbitrary universe a name:
```lean
constant α : Type _
#check α
```
Several Lean 3 users use the shorthand `Type*` for `Type _`. The `Type*` notation is not builtin in Lean 4, but you can easily define it using a `macro`.
```lean
macro "Type*" : term => `(Type _)
def f (α : Type*) (a : α) := a
def g (α : Type _) (a : α) := a
#check f
#check g
```
We explain later how the `macro` command works.

View File

@@ -1,56 +0,0 @@
# Types
Every programming language needs a type system and
Lean has a rich extensible inductive type system.
## Basic Types
Lean has built in support for the following basic types:
- [Bool](bool.md) : a `true` or `false` value.
- [Int](integers.md) : multiple precision integers (with no overflows!).
- [Nat](integers.md) : natural numbers, or non-negative integers (also with no overflows!).
- [Float](float.md): floating point numbers.
- [Char](char.md): a Unicode character.
- [String](string.md): a UTF-8 encoded string of characters.
- [Array](array.md): a dynamic (aka growable) array of typed objects.
- [List](list.md): a linked list of typed objects.
- TODO: what else?
And Lean allows you to create your own custom types using:
- [Enumerated Types](enum.md): a special case of inductive types.
- [Type Classes](typeclasses.md): a way of creating custom polymorphism.
- [Types as objects](typeobjs.md): a way of manipulating types themselves.
- [Structures](struct.md): a collection of named and typed fields. A
structure is actually special case of inductive datatype.
- [Inductive Types](inductive.md): TODO: add one liner...
## Universes
Every type in Lean is, by definition, an expression of type `Sort u`
for some universe level `u`. A universe level is one of the
following:
* a natural number, `n`
* a universe variable, `u` (declared with the command `universe` or `universes`)
* an expression `u + n`, where `u` is a universe level and `n` is a natural number
* an expression `max u v`, where `u` and `v` are universes
* an expression `imax u v`, where `u` and `v` are universe levels
The last one denotes the universe level `0` if `v` is `0`, and `max u v` otherwise.
```lean
universe u v
#check Sort u -- Type u
#check Sort 5 -- Type 4 : Type 5
#check Sort (u + 1) -- Type u : Type (u + 1)
#check Sort (u + 3) -- Type (u + 2) : Type (u + 3)
#check Sort (max u v) -- Sort (max u v) : Type (max u v)
#check Sort (max (u + 3) v) -- Sort (max (u + 3) v) : Type (max (u + 3) v)
#check Sort (imax (u + 3) v) -- Sort (imax (u + 3) v) : Type (imax (u + 3) v)
#check Prop -- Type
#check Type -- Type 1
#check Type 1 -- Type 1 : Type 2
```

View File

@@ -1 +0,0 @@
# Using Lean

View File

@@ -1,42 +0,0 @@
# What is Lean
Lean is a functional programming language that makes it easy to
write correct and maintainable code.
You can also use Lean as an interactive theorem prover.
Lean programming primarily involves defining types and functions.
This allows your focus to remain on the problem domain and manipulating its data,
rather than the details of programming.
```lean
-- Defines a function that takes a name and produces a greeting.
def getGreeting (name : String) := s!"Hello, {name}! Isn't Lean great?"
-- The `main` function is the entry point of your program.
-- Its type is `IO Unit` because it can perform `IO` operations (side effects).
def main : IO Unit :=
-- Define a list of names
let names := ["Sebastian", "Leo", "Daniel"]
-- Map each name to a greeting
let greetings := names.map getGreeting
-- Print the list of greetings
for greeting in greetings do
IO.println greeting
```
Lean has numerous features, including:
- Type inference
- First-class functions
- Powerful data types
- Pattern matching
- [Type classes](./typeclass.md)
- [Monads](./monads/intro.md)
- [Extensible syntax](./syntax.md)
- Hygienic macros
- [Dependent types](https://lean-lang.org/theorem_proving_in_lean4/dependent_type_theory.html)
- [Metaprogramming](./macro_overview.md)
- Multithreading
- Verification: you can prove properties of your functions using Lean itself

View File

@@ -8,12 +8,12 @@ open Lean.JsonRpc
Tests language server memory use by repeatedly re-elaborate a given file.
NOTE: only works on Linux for now.
ot to touch the imports for usual files.
-/
def main (args : List String) : IO Unit := do
let leanCmd :: file :: iters :: args := args | panic! "usage: script <lean> <file> <#iterations> <server-args>..."
let uri := s!"file:///{file}"
let file IO.FS.realPath file
let uri := s!"file://{file}"
Ipc.runWith leanCmd (#["--worker", "-DstderrAsMessages=false"] ++ args ++ #[uri]) do
-- for use with heaptrack:
--Ipc.runWith "heaptrack" (#[leanCmd, "--worker", "-DstderrAsMessages=false"] ++ args ++ #[uri]) do

View File

@@ -10,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 20)
set(LEAN_VERSION_MINOR 21)
set(LEAN_VERSION_PATCH 0)
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
@@ -689,7 +689,7 @@ add_custom_target(make_stdlib ALL
# The actual rule is in a separate makefile because we want to prefix it with '+' to use the Make job server
# for a parallelized nested build, but CMake doesn't let us do that.
# We use `lean` from the previous stage, but `leanc`, headers, etc. from the current stage
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make Init Std Lean
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make Init Std Lean Leanc
VERBATIM)
# if we have LLVM enabled, then build `lean.h.bc` which has the LLVM bitcode
@@ -768,7 +768,7 @@ if(${STAGE} GREATER 0 AND EXISTS ${LEAN_SOURCE_DIR}/Leanc.lean AND NOT ${CMAKE_S
add_custom_target(leanc ALL
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/leanc
DEPENDS leanshared
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make Leanc
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanc
VERBATIM)
endif()
@@ -823,7 +823,6 @@ endif()
# Escape for `make`. Yes, twice.
string(REPLACE "$" "\\\$$" CMAKE_EXE_LINKER_FLAGS_MAKE "${CMAKE_EXE_LINKER_FLAGS}")
string(REPLACE "$" "$$" CMAKE_EXE_LINKER_FLAGS_MAKE_MAKE "${CMAKE_EXE_LINKER_FLAGS_MAKE}")
configure_file(${LEAN_SOURCE_DIR}/stdlib.make.in ${CMAKE_BINARY_DIR}/stdlib.make)
# hacky

View File

@@ -40,5 +40,5 @@ This gadget is supported by
It is ineffective in other positions (hyptheses of rewrite rules) or when used by other tactics
(e.g. `apply`).
-/
@[simp ]
@[simp , expose]
def binderNameHint {α : Sort u} {β : Sort v} {γ : Sort w} (v : α) (binder : β) (e : γ) : γ := e

View File

@@ -42,24 +42,3 @@ theorem apply_ite (f : α → β) (P : Prop) [Decidable P] (x y : α) :
/-- A `dite` whose results do not actually depend on the condition may be reduced to an `ite`. -/
@[simp] theorem dite_eq_ite [Decidable P] :
(dite P (fun _ => a) (fun _ => b)) = ite P a b := rfl
@[deprecated "Use `ite_eq_right_iff`" (since := "2024-09-18")]
theorem ite_some_none_eq_none [Decidable P] :
(if P then some x else none) = none ¬ P := by
simp only [ite_eq_right_iff, reduceCtorEq]
rfl
@[deprecated "Use `Option.ite_none_right_eq_some`" (since := "2024-09-18")]
theorem ite_some_none_eq_some [Decidable P] :
(if P then some x else none) = some y P x = y := by
split <;> simp_all
@[deprecated "Use `dite_eq_right_iff" (since := "2024-09-18")]
theorem dite_some_none_eq_none [Decidable P] {x : P α} :
(if h : P then some (x h) else none) = none ¬P := by
simp
@[deprecated "Use `Option.dite_none_right_eq_some`" (since := "2024-09-18")]
theorem dite_some_none_eq_some [Decidable P] {x : P α} {y : α} :
(if h : P then some (x h) else none) = some y h : P, x h = y := by
by_cases h : P <;> simp [h]

View File

@@ -107,8 +107,8 @@ noncomputable def epsilon {α : Sort u} [h : Nonempty α] (p : α → Prop) : α
theorem epsilon_spec_aux {α : Sort u} (h : Nonempty α) (p : α Prop) : ( y, p y) p (@epsilon α h p) :=
(strongIndefiniteDescription p h).property
theorem epsilon_spec {α : Sort u} {p : α Prop} (hex : y, p y) : p (@epsilon α (nonempty_of_exists hex) p) :=
epsilon_spec_aux (nonempty_of_exists hex) p hex
theorem epsilon_spec {α : Sort u} {p : α Prop} (hex : y, p y) : p (@epsilon α hex.nonempty p) :=
epsilon_spec_aux hex.nonempty p hex
theorem epsilon_singleton {α : Sort u} (x : α) : @epsilon α x (fun y => y = x) = x :=
@epsilon_spec α (fun y => y = x) x, rfl

View File

@@ -49,7 +49,7 @@ abbrev forIn_eq_forin' := @forIn_eq_forIn'
/--
Extracts the value from a `ForInStep`, ignoring whether it is `ForInStep.done` or `ForInStep.yield`.
-/
def ForInStep.value (x : ForInStep α) : α :=
@[expose] def ForInStep.value (x : ForInStep α) : α :=
match x with
| ForInStep.done b => b
| ForInStep.yield b => b

View File

@@ -127,7 +127,7 @@ end Except
/--
Adds exceptions of type `ε` to a monad `m`.
-/
def ExceptT (ε : Type u) (m : Type u Type v) (α : Type u) : Type v :=
@[expose] def ExceptT (ε : Type u) (m : Type u Type v) (α : Type u) : Type v :=
m (Except ε α)
/--
@@ -136,7 +136,7 @@ may throw the corresponding exception.
This is the inverse of `ExceptT.run`.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def ExceptT.mk {ε : Type u} {m : Type u Type v} {α : Type u} (x : m (Except ε α)) : ExceptT ε m α := x
/--
@@ -144,7 +144,7 @@ Use a monadic action that may throw an exception as an action that may return an
This is the inverse of `ExceptT.mk`.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def ExceptT.run {ε : Type u} {m : Type u Type v} {α : Type u} (x : ExceptT ε m α) : m (Except ε α) := x
namespace ExceptT
@@ -154,14 +154,14 @@ variable {ε : Type u} {m : Type u → Type v} [Monad m]
/--
Returns the value `a` without throwing exceptions or having any other effect.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def pure {α : Type u} (a : α) : ExceptT ε m α :=
ExceptT.mk <| pure (Except.ok a)
/--
Handles exceptions thrown by an action that can have no effects _other_ than throwing exceptions.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def bindCont {α β : Type u} (f : α ExceptT ε m β) : Except ε α m (Except ε β)
| Except.ok a => f a
| Except.error e => pure (Except.error e)
@@ -170,14 +170,14 @@ protected def bindCont {α β : Type u} (f : α → ExceptT ε m β) : Except ε
Sequences two actions that may throw exceptions. Typically used via `do`-notation or the `>>=`
operator.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def bind {α β : Type u} (ma : ExceptT ε m α) (f : α ExceptT ε m β) : ExceptT ε m β :=
ExceptT.mk <| ma >>= ExceptT.bindCont f
/--
Transforms a successful computation's value using `f`. Typically used via the `<$>` operator.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def map {α β : Type u} (f : α β) (x : ExceptT ε m α) : ExceptT ε m β :=
ExceptT.mk <| x >>= fun a => match a with
| (Except.ok a) => pure <| Except.ok (f a)
@@ -186,7 +186,7 @@ protected def map {α β : Type u} (f : α → β) (x : ExceptT ε m α) : Excep
/--
Runs a computation from an underlying monad in the transformed monad with exceptions.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def lift {α : Type u} (t : m α) : ExceptT ε m α :=
ExceptT.mk <| Except.ok <$> t
@@ -197,7 +197,7 @@ instance : MonadLift m (ExceptT ε m) := ⟨ExceptT.lift⟩
/--
Handles exceptions produced in the `ExceptT ε` transformer.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def tryCatch {α : Type u} (ma : ExceptT ε m α) (handle : ε ExceptT ε m α) : ExceptT ε m α :=
ExceptT.mk <| ma >>= fun res => match res with
| Except.ok a => pure (Except.ok a)

View File

@@ -18,14 +18,14 @@ Adds exceptions of type `ε` to a monad `m`.
Instead of using `Except ε` to model exceptions, this implementation uses continuation passing
style. This has different performance characteristics from `ExceptT ε`.
-/
def ExceptCpsT (ε : Type u) (m : Type u Type v) (α : Type u) := (β : Type u) (α m β) (ε m β) m β
@[expose] def ExceptCpsT (ε : Type u) (m : Type u Type v) (α : Type u) := (β : Type u) (α m β) (ε m β) m β
namespace ExceptCpsT
/--
Use a monadic action that may throw an exception as an action that may return an exception's value.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def run {ε α : Type u} [Monad m] (x : ExceptCpsT ε m α) : m (Except ε α) :=
x _ (fun a => pure (Except.ok a)) (fun e => pure (Except.error e))
@@ -43,7 +43,7 @@ Returns the value of a computation, forgetting whether it was an exception or a
This corresponds to early return.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def runCatch [Monad m] (x : ExceptCpsT α m α) : m α :=
x α pure pure
@@ -63,7 +63,7 @@ instance : MonadExceptOf ε (ExceptCpsT ε m) where
/--
Run an action from the transformed monad in the exception monad.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def lift [Monad m] (x : m α) : ExceptCpsT ε m α :=
fun _ k _ => x >>= k

View File

@@ -34,7 +34,7 @@ def containsFive (xs : List Nat) : Bool := Id.run do
true
```
-/
def Id (type : Type u) : Type u := type
@[expose] def Id (type : Type u) : Type u := type
namespace Id
@@ -56,7 +56,7 @@ Runs a computation in the identity monad.
This function is the identity function. Because its parameter has type `Id α`, it causes
`do`-notation in its arguments to use the `Monad Id` instance.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def run (x : Id α) : α := x
instance [OfNat α n] : OfNat (Id α) n :=

View File

@@ -9,3 +9,4 @@ prelude
import Init.Control.Lawful.Basic
import Init.Control.Lawful.Instances
import Init.Control.Lawful.Lemmas
import Init.Control.Lawful.MonadLift

View File

@@ -6,6 +6,7 @@ Authors: Sebastian Ullrich, Leonardo de Moura, Mario Carneiro
module
prelude
import Init.Ext
import Init.SimpLemmas
import Init.Meta
@@ -241,13 +242,23 @@ theorem LawfulMonad.mk' (m : Type u → Type v) [Monad m]
namespace Id
@[simp] theorem map_eq (x : Id α) (f : α β) : f <$> x = f x := rfl
@[simp] theorem bind_eq (x : Id α) (f : α id β) : x >>= f = f x := rfl
@[simp] theorem pure_eq (a : α) : (pure a : Id α) = a := rfl
@[ext] theorem ext {x y : Id α} (h : x.run = y.run) : x = y := h
instance : LawfulMonad Id := by
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
@[simp] theorem run_map (x : Id α) (f : α β) : (f <$> x).run = f x.run := rfl
@[simp] theorem run_bind (x : Id α) (f : α Id β) : (x >>= f).run = (f x.run).run := rfl
@[simp] theorem run_pure (a : α) : (pure a : Id α).run = a := rfl
@[simp] theorem run_seqRight (x y : Id α) : (x *> y).run = y.run := rfl
@[simp] theorem run_seqLeft (x y : Id α) : (x <* y).run = x.run := rfl
@[simp] theorem run_seq (f : Id (α β)) (x : Id α) : (f <*> x).run = f.run x.run := rfl
-- These lemmas are bad as they abuse the defeq of `Id α` and `α`
@[deprecated run_map (since := "2025-03-05")] theorem map_eq (x : Id α) (f : α β) : f <$> x = f x := rfl
@[deprecated run_bind (since := "2025-03-05")] theorem bind_eq (x : Id α) (f : α id β) : x >>= f = f x := rfl
@[deprecated run_pure (since := "2025-03-05")] theorem pure_eq (a : α) : (pure a : Id α) = a := rfl
end Id
/-! # Option -/

View File

@@ -7,7 +7,8 @@ module
prelude
import Init.Control.Lawful.Basic
import Init.Control.Except
import all Init.Control.Except
import all Init.Control.State
import Init.Control.StateRef
import Init.Ext
@@ -98,7 +99,7 @@ end ExceptT
instance : LawfulMonad (Except ε) := LawfulMonad.mk'
(id_map := fun x => by cases x <;> rfl)
(pure_bind := fun _ _ => rfl)
(pure_bind := fun _ _ => by rfl)
(bind_assoc := fun a _ _ => by cases a <;> rfl)
instance : LawfulApplicative (Except ε) := inferInstance
@@ -247,7 +248,7 @@ instance : LawfulMonad (EStateM ε σ) := .mk'
match x s with
| .ok _ _ => rfl
| .error _ _ => rfl)
(pure_bind := fun _ _ => rfl)
(pure_bind := fun _ _ => by rfl)
(bind_assoc := fun x _ _ => funext <| fun s => by
dsimp only [EStateM.instMonad, EStateM.bind]
match x s with

View File

@@ -0,0 +1,11 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
import Init.Control.Lawful.MonadLift.Basic
import Init.Control.Lawful.MonadLift.Lemmas
import Init.Control.Lawful.MonadLift.Instances

View File

@@ -0,0 +1,52 @@
/-
Copyright (c) 2025 Quang Dao. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Quang Dao
-/
module
prelude
import Init.Control.Basic
/-!
# LawfulMonadLift and LawfulMonadLiftT
This module provides classes asserting that `MonadLift` and `MonadLiftT` are lawful, which means
that `monadLift` is compatible with `pure` and `bind`.
-/
section MonadLift
/-- The `MonadLift` typeclass only contains the lifting operation. `LawfulMonadLift` further
asserts that lifting commutes with `pure` and `bind`:
```
monadLift (pure a) = pure a
monadLift (ma >>= f) = monadLift ma >>= monadLift ∘ f
```
-/
class LawfulMonadLift (m : semiOutParam (Type u Type v)) (n : Type u Type w)
[Monad m] [Monad n] [inst : MonadLift m n] : Prop where
/-- Lifting preserves `pure` -/
monadLift_pure {α : Type u} (a : α) : inst.monadLift (pure a) = pure a
/-- Lifting preserves `bind` -/
monadLift_bind {α β : Type u} (ma : m α) (f : α m β) :
inst.monadLift (ma >>= f) = inst.monadLift ma >>= (fun x => inst.monadLift (f x))
/-- The `MonadLiftT` typeclass only contains the transitive lifting operation.
`LawfulMonadLiftT` further asserts that lifting commutes with `pure` and `bind`:
```
monadLift (pure a) = pure a
monadLift (ma >>= f) = monadLift ma >>= monadLift ∘ f
```
-/
class LawfulMonadLiftT (m : Type u Type v) (n : Type u Type w) [Monad m] [Monad n]
[inst : MonadLiftT m n] : Prop where
/-- Lifting preserves `pure` -/
monadLift_pure {α : Type u} (a : α) : inst.monadLift (pure a) = pure a
/-- Lifting preserves `bind` -/
monadLift_bind {α β : Type u} (ma : m α) (f : α m β) :
inst.monadLift (ma >>= f) = monadLift ma >>= (fun x => monadLift (f x))
export LawfulMonadLiftT (monadLift_pure monadLift_bind)
end MonadLift

View File

@@ -0,0 +1,137 @@
/-
Copyright (c) 2025 Quang Dao. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Quang Dao, Paul Reichert
-/
module
prelude
import all Init.Control.Option
import all Init.Control.Except
import all Init.Control.ExceptCps
import all Init.Control.StateRef
import all Init.Control.StateCps
import Init.Control.Lawful.MonadLift.Lemmas
import Init.Control.Lawful.Instances
universe u v w x
variable {m : Type u Type v} {n : Type u Type w} {o : Type u Type x}
variable (m n o) in
instance [Monad m] [Monad n] [Monad o] [MonadLift n o] [MonadLiftT m n]
[LawfulMonadLift n o] [LawfulMonadLiftT m n] : LawfulMonadLiftT m o where
monadLift_pure := fun a => by
simp only [monadLift, LawfulMonadLift.monadLift_pure, liftM_pure]
monadLift_bind := fun ma f => by
simp only [monadLift, LawfulMonadLift.monadLift_bind, liftM_bind]
variable (m) in
instance [Monad m] : LawfulMonadLiftT m m where
monadLift_pure _ := rfl
monadLift_bind _ _ := rfl
namespace StateT
variable [Monad m] [LawfulMonad m]
instance {σ : Type u} : LawfulMonadLift m (StateT σ m) where
monadLift_pure _ := by ext; simp [MonadLift.monadLift]
monadLift_bind _ _ := by ext; simp [MonadLift.monadLift]
end StateT
namespace ReaderT
variable [Monad m]
instance {ρ : Type u} : LawfulMonadLift m (ReaderT ρ m) where
monadLift_pure _ := rfl
monadLift_bind _ _ := rfl
end ReaderT
namespace OptionT
variable [Monad m] [LawfulMonad m]
@[simp]
theorem lift_pure {α : Type u} (a : α) : OptionT.lift (pure a : m α) = pure a := by
simp only [OptionT.lift, OptionT.mk, bind_pure_comp, map_pure, pure, OptionT.pure]
@[simp]
theorem lift_bind {α β : Type u} (ma : m α) (f : α m β) :
OptionT.lift (ma >>= f) = OptionT.lift ma >>= (fun a => OptionT.lift (f a)) := by
simp only [instMonad, OptionT.bind, OptionT.mk, OptionT.lift, bind_pure_comp, bind_map_left,
map_bind]
instance : LawfulMonadLift m (OptionT m) where
monadLift_pure := lift_pure
monadLift_bind := lift_bind
end OptionT
namespace ExceptT
variable [Monad m] [LawfulMonad m]
@[simp]
theorem lift_bind {α β ε : Type u} (ma : m α) (f : α m β) :
ExceptT.lift (ε := ε) (ma >>= f) = ExceptT.lift ma >>= (fun a => ExceptT.lift (f a)) := by
simp only [instMonad, ExceptT.bind, mk, ExceptT.lift, bind_map_left, ExceptT.bindCont, map_bind]
instance : LawfulMonadLift m (ExceptT ε m) where
monadLift_pure := lift_pure
monadLift_bind := lift_bind
instance : LawfulMonadLift (Except ε) (ExceptT ε m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, mk, pure, Except.pure, ExceptT.pure]
monadLift_bind ma _ := by
simp only [instMonad, ExceptT.bind, mk, MonadLift.monadLift, pure_bind, ExceptT.bindCont,
Except.instMonad, Except.bind]
rcases ma with _ | _ <;> simp
end ExceptT
namespace StateRefT'
instance {ω σ : Type} {m : Type Type} [Monad m] : LawfulMonadLift m (StateRefT' ω σ m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, pure]
unfold StateRefT'.lift ReaderT.pure
simp only
monadLift_bind _ _ := by
simp only [MonadLift.monadLift, bind]
unfold StateRefT'.lift ReaderT.bind
simp only
end StateRefT'
namespace StateCpsT
instance {σ : Type u} [Monad m] [LawfulMonad m] : LawfulMonadLift m (StateCpsT σ m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, pure]
unfold StateCpsT.lift
simp only [pure_bind]
monadLift_bind _ _ := by
simp only [MonadLift.monadLift, bind]
unfold StateCpsT.lift
simp only [bind_assoc]
end StateCpsT
namespace ExceptCpsT
instance {ε : Type u} [Monad m] [LawfulMonad m] : LawfulMonadLift m (ExceptCpsT ε m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, pure]
unfold ExceptCpsT.lift
simp only [pure_bind]
monadLift_bind _ _ := by
simp only [MonadLift.monadLift, bind]
unfold ExceptCpsT.lift
simp only [bind_assoc]
end ExceptCpsT

View File

@@ -0,0 +1,63 @@
/-
Copyright (c) 2025 Quang Dao. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Quang Dao
-/
module
prelude
import Init.Control.Lawful.Basic
import Init.Control.Lawful.MonadLift.Basic
universe u v w
variable {m : Type u Type v} {n : Type u Type w} [Monad m] [Monad n] [MonadLiftT m n]
[LawfulMonadLiftT m n] {α β : Type u}
theorem monadLift_map [LawfulMonad m] [LawfulMonad n] (f : α β) (ma : m α) :
monadLift (f <$> ma) = f <$> (monadLift ma : n α) := by
rw [ bind_pure_comp, bind_pure_comp, monadLift_bind]
simp only [bind_pure_comp, monadLift_pure]
theorem monadLift_seq [LawfulMonad m] [LawfulMonad n] (mf : m (α β)) (ma : m α) :
monadLift (mf <*> ma) = monadLift mf <*> (monadLift ma : n α) := by
simp only [seq_eq_bind, monadLift_map, monadLift_bind]
theorem monadLift_seqLeft [LawfulMonad m] [LawfulMonad n] (x : m α) (y : m β) :
monadLift (x <* y) = (monadLift x : n α) <* (monadLift y : n β) := by
simp only [seqLeft_eq, monadLift_map, monadLift_seq]
theorem monadLift_seqRight [LawfulMonad m] [LawfulMonad n] (x : m α) (y : m β) :
monadLift (x *> y) = (monadLift x : n α) *> (monadLift y : n β) := by
simp only [seqRight_eq, monadLift_map, monadLift_seq]
/-! We duplicate the theorems for `monadLift` to `liftM` since `rw` matches on syntax only. -/
@[simp]
theorem liftM_pure (a : α) : liftM (pure a : m α) = pure (f := n) a :=
monadLift_pure _
@[simp]
theorem liftM_bind (ma : m α) (f : α m β) :
liftM (n := n) (ma >>= f) = liftM ma >>= (fun a => liftM (f a)) :=
monadLift_bind _ _
@[simp]
theorem liftM_map [LawfulMonad m] [LawfulMonad n] (f : α β) (ma : m α) :
liftM (f <$> ma) = f <$> (liftM ma : n α) :=
monadLift_map _ _
@[simp]
theorem liftM_seq [LawfulMonad m] [LawfulMonad n] (mf : m (α β)) (ma : m α) :
liftM (mf <*> ma) = liftM mf <*> (liftM ma : n α) :=
monadLift_seq _ _
@[simp]
theorem liftM_seqLeft [LawfulMonad m] [LawfulMonad n] (x : m α) (y : m β) :
liftM (x <* y) = (liftM x : n α) <* (liftM y : n β) :=
monadLift_seqLeft _ _
@[simp]
theorem liftM_seqRight [LawfulMonad m] [LawfulMonad n] (x : m α) (y : m β) :
liftM (x *> y) = (liftM x : n α) *> (liftM y : n β) :=
monadLift_seqRight _ _

View File

@@ -20,7 +20,7 @@ instance : ToBool (Option α) := ⟨Option.isSome⟩
Adds the ability to fail to a monad. Unlike ordinary exceptions, there is no way to signal why a
failure occurred.
-/
def OptionT (m : Type u Type v) (α : Type u) : Type v :=
@[expose] def OptionT (m : Type u Type v) (α : Type u) : Type v :=
m (Option α)
/--

View File

@@ -22,14 +22,14 @@ Adds a mutable state of type `σ` to a monad.
Actions in the resulting monad are functions that take an initial state and return, in `m`, a tuple
of a value and a state.
-/
def StateT (σ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
@[expose] def StateT (σ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
σ m (α × σ)
/--
Executes an action from a monad with added state in the underlying monad `m`. Given an initial
state, it returns a value paired with the final state.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def StateT.run {σ : Type u} {m : Type u Type v} {α : Type u} (x : StateT σ m α) (s : σ) : m (α × σ) :=
x s
@@ -37,7 +37,7 @@ def StateT.run {σ : Type u} {m : Type u → Type v} {α : Type u} (x : StateT
Executes an action from a monad with added state in the underlying monad `m`. Given an initial
state, it returns a value, discarding the final state.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def StateT.run' {σ : Type u} {m : Type u Type v} [Functor m] {α : Type u} (x : StateT σ m α) (s : σ) : m α :=
(·.1) <$> x s
@@ -47,7 +47,7 @@ A tuple-based state monad.
Actions in `StateM σ` are functions that take an initial state and return a value paired with a
final state.
-/
@[reducible]
@[expose, reducible]
def StateM (σ α : Type u) : Type u := StateT σ Id α
instance {σ α} [Subsingleton σ] [Subsingleton α] : Subsingleton (StateM σ α) where
@@ -66,21 +66,21 @@ variable [Monad m] {α β : Type u}
/--
Returns the given value without modifying the state. Typically used via `Pure.pure`.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def pure (a : α) : StateT σ m α :=
fun s => pure (a, s)
/--
Sequences two actions. Typically used via the `>>=` operator.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def bind (x : StateT σ m α) (f : α StateT σ m β) : StateT σ m β :=
fun s => do let (a, s) x s; f a s
/--
Modifies the value returned by a computation. Typically used via the `<$>` operator.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def map (f : α β) (x : StateT σ m α) : StateT σ m β :=
fun s => do let (a, s) x s; pure (f a, s)
@@ -114,14 +114,14 @@ Retrieves the current value of the monad's mutable state.
This increments the reference count of the state, which may inhibit in-place updates.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def get : StateT σ m σ :=
fun s => pure (s, s)
/--
Replaces the mutable state with a new value.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def set : σ StateT σ m PUnit :=
fun s' _ => pure (, s')
@@ -133,7 +133,7 @@ It is equivalent to `do let (a, s) := f (← StateT.get); StateT.set s; pure a`.
`StateT.modifyGet` may lead to better performance because it doesn't add a new reference to the
state value, and additional references can inhibit in-place updates of data.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def modifyGet (f : σ α × σ) : StateT σ m α :=
fun s => pure (f s)
@@ -143,7 +143,7 @@ Runs an action from the underlying monad in the monad with state. The state is n
This function is typically implicitly accessed via a `MonadLiftT` instance as part of [automatic
lifting](lean-manual://section/monad-lifting).
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def lift {α : Type u} (t : m α) : StateT σ m α :=
fun s => do let a t; pure (a, s)

View File

@@ -18,7 +18,7 @@ The State monad transformer using CPS style.
An alternative implementation of a state monad transformer that internally uses continuation passing
style instead of tuples.
-/
def StateCpsT (σ : Type u) (m : Type u Type v) (α : Type u) := (δ : Type u) σ (α σ m δ) m δ
@[expose] def StateCpsT (σ : Type u) (m : Type u Type v) (α : Type u) := (δ : Type u) σ (α σ m δ) m δ
namespace StateCpsT
@@ -28,7 +28,7 @@ variable {α σ : Type u} {m : Type u → Type v}
Runs a stateful computation that's represented using continuation passing style by providing it with
an initial state and a continuation.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def runK (x : StateCpsT σ m α) (s : σ) (k : α σ m β) : m β :=
x _ s k
@@ -39,7 +39,7 @@ state, it returns a value paired with the final state.
While the state is internally represented in continuation passing style, the resulting value is the
same as for a non-CPS state monad.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def run [Monad m] (x : StateCpsT σ m α) (s : σ) : m (α × σ) :=
runK x s (fun a s => pure (a, s))
@@ -47,7 +47,7 @@ def run [Monad m] (x : StateCpsT σ m α) (s : σ) : m (α × σ) :=
Executes an action from a monad with added state in the underlying monad `m`. Given an initial
state, it returns a value, discarding the final state.
-/
@[always_inline, inline]
@[always_inline, inline, expose]
def run' [Monad m] (x : StateCpsT σ m α) (s : σ) : m α :=
runK x s (fun a _ => pure a)
@@ -72,7 +72,7 @@ Runs an action from the underlying monad in the monad with state. The state is n
This function is typically implicitly accessed via a `MonadLiftT` instance as part of [automatic
lifting](lean-manual://section/monad-lifting).
-/
@[always_inline, inline]
@[always_inline, inline, expose]
protected def lift [Monad m] (x : m α) : StateCpsT σ m α :=
fun _ s k => x >>= (k . s)

View File

@@ -17,7 +17,7 @@ A state monad that uses an actual mutable reference cell (i.e. an `ST.Ref ω σ`
The macro `StateRefT σ m α` infers `ω` from `m`. It should normally be used instead.
-/
def StateRefT' (ω : Type) (σ : Type) (m : Type Type) (α : Type) : Type := ReaderT (ST.Ref ω σ) m α
@[expose] def StateRefT' (ω : Type) (σ : Type) (m : Type Type) (α : Type) : Type := ReaderT (ST.Ref ω σ) m α
/-! Recall that `StateRefT` is a macro that infers `ω` from the `m`. -/

View File

@@ -12,6 +12,8 @@ import Init.Prelude
import Init.SizeOf
set_option linter.missingDocs true -- keep it documented
@[expose] section
universe u v w
/--
@@ -41,16 +43,19 @@ and `flip (·<·)` is the greater-than relation.
theorem Function.comp_def {α β δ} (f : β δ) (g : α β) : f g = fun x => f (g x) := rfl
@[simp] theorem Function.const_comp {f : α β} {c : γ} :
(Function.const β c f) = Function.const α c := by
(Function.const β c f) = Function.const α c :=
rfl
@[simp] theorem Function.comp_const {f : β γ} {b : β} :
(f Function.const α b) = Function.const α (f b) := by
(f Function.const α b) = Function.const α (f b) :=
rfl
@[simp] theorem Function.true_comp {f : α β} : ((fun _ => true) f) = fun _ => true := by
@[simp] theorem Function.true_comp {f : α β} : ((fun _ => true) f) = fun _ => true :=
rfl
@[simp] theorem Function.false_comp {f : α β} : ((fun _ => false) f) = fun _ => false := by
@[simp] theorem Function.false_comp {f : α β} : ((fun _ => false) f) = fun _ => false :=
rfl
@[simp] theorem Function.comp_id (f : α β) : f id = f := rfl
@[simp] theorem Function.id_comp (f : α β) : id f = f := rfl
attribute [simp] namedPattern
/--
@@ -90,7 +95,8 @@ structure Thunk (α : Type u) : Type u where
-/
mk ::
/-- Extract the getter function out of a thunk. Use `Thunk.get` instead. -/
private fn : Unit α
-- The field is public so as to allow computation through it.
fn : Unit α
attribute [extern "lean_mk_thunk"] Thunk.mk
@@ -112,6 +118,10 @@ Computed values are cached, so the value is not recomputed.
@[extern "lean_thunk_get_own"] protected def Thunk.get (x : @& Thunk α) : α :=
x.fn ()
-- Ensure `Thunk.fn` is still computable even if it shouldn't be accessed directly.
@[inline] private def Thunk.fnImpl (x : Thunk α) : Unit α := fun _ => x.get
@[csimp] private theorem Thunk.fn_eq_fnImpl : @Thunk.fn = @Thunk.fnImpl := rfl
/--
Constructs a new thunk that forces `x` and then applies `x` to the result. Upon forcing, the result
of `f` is cached and the reference to the thunk `x` is dropped.
@@ -892,43 +902,43 @@ section
variable {α β φ : Sort u} {a a' : α} {b b' : β} {c : φ}
/-- Non-dependent recursor for `HEq` -/
noncomputable def HEq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : {β : Sort u2} β Sort u1} (m : motive a) {β : Sort u2} {b : β} (h : HEq a b) : motive b :=
noncomputable def HEq.ndrec.{u1, u2} {α : Sort u2} {a : α} {motive : {β : Sort u2} β Sort u1} (m : motive a) {β : Sort u2} {b : β} (h : a b) : motive b :=
h.rec m
/-- `HEq.ndrec` variant -/
noncomputable def HEq.ndrecOn.{u1, u2} {α : Sort u2} {a : α} {motive : {β : Sort u2} β Sort u1} {β : Sort u2} {b : β} (h : HEq a b) (m : motive a) : motive b :=
noncomputable def HEq.ndrecOn.{u1, u2} {α : Sort u2} {a : α} {motive : {β : Sort u2} β Sort u1} {β : Sort u2} {b : β} (h : a b) (m : motive a) : motive b :=
h.rec m
/-- `HEq.ndrec` variant -/
noncomputable def HEq.elim {α : Sort u} {a : α} {p : α Sort v} {b : α} (h₁ : HEq a b) (h₂ : p a) : p b :=
noncomputable def HEq.elim {α : Sort u} {a : α} {p : α Sort v} {b : α} (h₁ : a b) (h₂ : p a) : p b :=
eq_of_heq h₁ h₂
/-- Substitution with heterogeneous equality. -/
theorem HEq.subst {p : (T : Sort u) T Prop} (h₁ : HEq a b) (h₂ : p α a) : p β b :=
theorem HEq.subst {p : (T : Sort u) T Prop} (h₁ : a b) (h₂ : p α a) : p β b :=
HEq.ndrecOn h₁ h₂
/-- Heterogeneous equality is symmetric. -/
@[symm] theorem HEq.symm (h : HEq a b) : HEq b a :=
@[symm] theorem HEq.symm (h : a b) : b a :=
h.rec (HEq.refl a)
/-- Propositionally equal terms are also heterogeneously equal. -/
theorem heq_of_eq (h : a = a') : HEq a a' :=
theorem heq_of_eq (h : a = a') : a a' :=
Eq.subst h (HEq.refl a)
/-- Heterogeneous equality is transitive. -/
theorem HEq.trans (h₁ : HEq a b) (h₂ : HEq b c) : HEq a c :=
theorem HEq.trans (h₁ : a b) (h₂ : b c) : a c :=
HEq.subst h₂ h₁
/-- Heterogeneous equality precomposes with propositional equality. -/
theorem heq_of_heq_of_eq (h₁ : HEq a b) (h₂ : b = b') : HEq a b' :=
theorem heq_of_heq_of_eq (h₁ : a b) (h₂ : b = b') : a b' :=
HEq.trans h₁ (heq_of_eq h₂)
/-- Heterogeneous equality postcomposes with propositional equality. -/
theorem heq_of_eq_of_heq (h₁ : a = a') (h₂ : HEq a' b) : HEq a b :=
theorem heq_of_eq_of_heq (h₁ : a = a') (h₂ : a' b) : a b :=
HEq.trans (heq_of_eq h₁) h₂
/-- If two terms are heterogeneously equal then their types are propositionally equal. -/
theorem type_eq_of_heq (h : HEq a b) : α = β :=
theorem type_eq_of_heq (h : a b) : α = β :=
h.rec (Eq.refl α)
end
@@ -937,25 +947,25 @@ end
Rewriting inside `φ` using `Eq.recOn` yields a term that's heterogeneously equal to the original
term.
-/
theorem eqRec_heq {α : Sort u} {φ : α Sort v} {a a' : α} : (h : a = a') (p : φ a) HEq (Eq.recOn (motive := fun x _ => φ x) h p) p
theorem eqRec_heq {α : Sort u} {φ : α Sort v} {a a' : α} : (h : a = a') (p : φ a) Eq.recOn (motive := fun x _ => φ x) h p p
| rfl, p => HEq.refl p
/--
Heterogenous equality with an `Eq.rec` application on the left is equivalent to a heterogenous
Heterogeneous equality with an `Eq.rec` application on the left is equivalent to a heterogeneous
equality on the original term.
-/
theorem eqRec_heq_iff {α : Sort u} {a : α} {motive : (b : α) a = b Sort v}
{b : α} {refl : motive a (Eq.refl a)} {h : a = b} {c : motive b h} :
HEq (@Eq.rec α a motive refl b h) c HEq refl c :=
{b : α} {refl : motive a (Eq.refl a)} {h : a = b} {c : motive b h}
: @Eq.rec α a motive refl b h c refl c :=
h.rec (fun _ => id, id) c
/--
Heterogenous equality with an `Eq.rec` application on the right is equivalent to a heterogenous
Heterogeneous equality with an `Eq.rec` application on the right is equivalent to a heterogeneous
equality on the original term.
-/
theorem heq_eqRec_iff {α : Sort u} {a : α} {motive : (b : α) a = b Sort v}
{b : α} {refl : motive a (Eq.refl a)} {h : a = b} {c : motive b h} :
HEq c (@Eq.rec α a motive refl b h) HEq c refl :=
c @Eq.rec α a motive refl b h c refl :=
h.rec (fun _ => id, id) c
/--
@@ -972,7 +982,7 @@ theorem apply_eqRec {α : Sort u} {a : α} (motive : (b : α) → a = b → Sort
If casting a term with `Eq.rec` to another type makes it equal to some other term, then the two
terms are heterogeneously equal.
-/
theorem heq_of_eqRec_eq {α β : Sort u} {a : α} {b : β} (h₁ : α = β) (h₂ : Eq.rec (motive := fun α _ => α) a h₁ = b) : HEq a b := by
theorem heq_of_eqRec_eq {α β : Sort u} {a : α} {b : β} (h₁ : α = β) (h₂ : Eq.rec (motive := fun α _ => α) a h₁ = b) : a b := by
subst h₁
apply heq_of_eq
exact h₂
@@ -980,7 +990,7 @@ theorem heq_of_eqRec_eq {α β : Sort u} {a : α} {b : β} (h₁ : α = β) (h
/--
The result of casting a term with `cast` is heterogeneously equal to the original term.
-/
theorem cast_heq {α β : Sort u} : (h : α = β) (a : α) HEq (cast h a) a
theorem cast_heq {α β : Sort u} : (h : α = β) (a : α) cast h a a
| rfl, a => HEq.refl a
variable {a b c d : Prop}
@@ -1009,8 +1019,8 @@ instance : Trans Iff Iff Iff where
theorem Eq.comm {a b : α} : a = b b = a := Iff.intro Eq.symm Eq.symm
theorem eq_comm {a b : α} : a = b b = a := Eq.comm
theorem HEq.comm {a : α} {b : β} : HEq a b HEq b a := Iff.intro HEq.symm HEq.symm
theorem heq_comm {a : α} {b : β} : HEq a b HEq b a := HEq.comm
theorem HEq.comm {a : α} {b : β} : a b b a := Iff.intro HEq.symm HEq.symm
theorem heq_comm {a : α} {b : β} : a b b a := HEq.comm
@[symm] theorem Iff.symm (h : a b) : b a := Iff.intro h.mpr h.mp
theorem Iff.comm : (a b) (b a) := Iff.intro Iff.symm Iff.symm
@@ -1043,11 +1053,6 @@ theorem Exists.elim {α : Sort u} {p : α → Prop} {b : Prop}
| isFalse _ => rfl
| isTrue h => False.elim h
set_option linter.missingDocs false in
@[deprecated decide_true (since := "2024-11-05")] abbrev decide_true_eq_true := decide_true
set_option linter.missingDocs false in
@[deprecated decide_false (since := "2024-11-05")] abbrev decide_false_eq_false := decide_false
/-- Similar to `decide`, but uses an explicit instance -/
@[inline] def toBoolUsing {p : Prop} (d : Decidable p) : Bool :=
decide (h := d)
@@ -1207,10 +1212,7 @@ abbrev noConfusionEnum {α : Sort u} {β : Sort v} [inst : DecidableEq β] (f :
instance : Inhabited Prop where
default := True
deriving instance Inhabited for NonScalar, PNonScalar, True, ForInStep
theorem nonempty_of_exists {α : Sort u} {p : α Prop} : Exists (fun x => p x) Nonempty α
| w, _ => w
deriving instance Inhabited for NonScalar, PNonScalar, True
/-! # Subsingleton -/
@@ -1237,7 +1239,7 @@ protected theorem Subsingleton.elim {α : Sort u} [h : Subsingleton α] : (a b :
If two types are equal and one of them is a subsingleton, then all of their elements are
[heterogeneously equal](lean-manual://section/HEq).
-/
protected theorem Subsingleton.helim {α β : Sort u} [h₁ : Subsingleton α] (h₂ : α = β) (a : α) (b : β) : HEq a b := by
protected theorem Subsingleton.helim {α β : Sort u} [h₁ : Subsingleton α] (h₂ : α = β) (a : α) (b : β) : a b := by
subst h₂
apply heq_of_eq
apply Subsingleton.elim
@@ -1384,16 +1386,7 @@ instance Sum.nonemptyLeft [h : Nonempty α] : Nonempty (Sum α β) :=
instance Sum.nonemptyRight [h : Nonempty β] : Nonempty (Sum α β) :=
Nonempty.elim h (fun b => Sum.inr b)
instance {α : Type u} {β : Type v} [DecidableEq α] [DecidableEq β] : DecidableEq (Sum α β) := fun a b =>
match a, b with
| Sum.inl a, Sum.inl b =>
if h : a = b then isTrue (h rfl)
else isFalse fun h' => Sum.noConfusion h' fun h' => absurd h' h
| Sum.inr a, Sum.inr b =>
if h : a = b then isTrue (h rfl)
else isFalse fun h' => Sum.noConfusion h' fun h' => absurd h' h
| Sum.inr _, Sum.inl _ => isFalse fun h => Sum.noConfusion h
| Sum.inl _, Sum.inr _ => isFalse fun h => Sum.noConfusion h
deriving instance DecidableEq for Sum
end
@@ -1697,7 +1690,7 @@ theorem true_iff_false : (True ↔ False) ↔ False := iff_false_intro (·.mp T
theorem false_iff_true : (False True) False := iff_false_intro (·.mpr True.intro)
theorem iff_not_self : ¬(a ¬a) | H => let f h := H.1 h h; f (H.2 f)
theorem heq_self_iff_true (a : α) : HEq a a True := iff_true_intro HEq.rfl
theorem heq_self_iff_true (a : α) : a a True := iff_true_intro HEq.rfl
/-! ## implies -/
@@ -1897,7 +1890,7 @@ a structure.
protected abbrev hrecOn
(q : Quot r)
(f : (a : α) motive (Quot.mk r a))
(c : (a b : α) (p : r a b) HEq (f a) (f b))
(c : (a b : α) (p : r a b) f a f b)
: motive q :=
Quot.recOn q f fun a b p => eq_of_heq (eqRec_heq_iff.mpr (c a b p))
@@ -2095,7 +2088,7 @@ a structure.
protected abbrev hrecOn
(q : Quotient s)
(f : (a : α) motive (Quotient.mk s a))
(c : (a b : α) (p : a b) HEq (f a) (f b))
(c : (a b : α) (p : a b) f a f b)
: motive q :=
Quot.hrecOn q f c
end
@@ -2524,9 +2517,6 @@ class Antisymm (r : αα → Prop) : Prop where
/-- An antisymmetric relation `r` satisfies `r a b → r b a → a = b`. -/
antisymm (a b : α) : r a b r b a a = b
@[deprecated Antisymm (since := "2024-10-16"), inherit_doc Antisymm]
abbrev _root_.Antisymm (r : α α Prop) : Prop := Std.Antisymm r
/-- `Asymm X r` means that the binary relation `r` on `X` is asymmetric, that is,
`r a b → ¬ r b a`. -/
class Asymm (r : α α Prop) : Prop where

View File

@@ -9,7 +9,7 @@ prelude
import Init.Data.Array.Mem
import Init.Data.Array.Lemmas
import Init.Data.Array.Count
import Init.Data.List.Attach
import all Init.Data.List.Attach
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
@@ -22,7 +22,7 @@ an array `xs : Array α`, given a proof that every element of `xs` in fact satis
`Array.pmap`, named for “partial map,” is the equivalent of `Array.map` for such partial functions.
-/
@[expose]
def pmap {P : α Prop} (f : a, P a β) (xs : Array α) (H : a xs, P a) : Array β :=
(xs.toList.pmap f (fun a m => H a (mem_def.mpr m))).toArray
@@ -39,7 +39,7 @@ of elements in the corresponding subtype `{ x // P x }`.
`O(1)`.
-/
@[implemented_by attachWithImpl] def attachWith
@[implemented_by attachWithImpl, expose] def attachWith
(xs : Array α) (P : α Prop) (H : x xs, P x) : Array {x // P x} :=
xs.toList.attachWith P fun x h => H x (Array.Mem.mk h)
@@ -54,7 +54,7 @@ recursion](lean-manual://section/well-founded-recursion) that use higher-order f
`Array.map`) to prove that an value taken from a list is smaller than the list. This allows the
well-founded recursion mechanism to prove that the function terminates.
-/
@[inline] def attach (xs : Array α) : Array {x // x xs} := xs.attachWith _ fun _ => id
@[inline, expose] def attach (xs : Array α) : Array {x // x xs} := xs.attachWith _ fun _ => id
@[simp, grind =] theorem _root_.List.attachWith_toArray {l : List α} {P : α Prop} {H : x l.toArray, P x} :
l.toArray.attachWith P H = (l.attachWith P (by simpa using H)).toArray := by
@@ -69,11 +69,11 @@ well-founded recursion mechanism to prove that the function terminates.
simp [pmap]
@[simp] theorem toList_attachWith {xs : Array α} {P : α Prop} {H : x xs, P x} :
(xs.attachWith P H).toList = xs.toList.attachWith P (by simpa [mem_toList] using H) := by
(xs.attachWith P H).toList = xs.toList.attachWith P (by simpa [mem_toList_iff] using H) := by
simp [attachWith]
@[simp] theorem toList_attach {xs : Array α} :
xs.attach.toList = xs.toList.attachWith (· xs) (by simp [mem_toList]) := by
xs.attach.toList = xs.toList.attachWith (· xs) (by simp [mem_toList_iff]) := by
simp [attach]
@[simp] theorem toList_pmap {xs : Array α} {P : α Prop} {f : a, P a β} {H : a xs, P a} :
@@ -574,9 +574,12 @@ state, the right approach is usually the tactic `simp [Array.unattach, -Array.ma
-/
def unattach {α : Type _} {p : α Prop} (xs : Array { x // p x }) : Array α := xs.map (·.val)
@[simp] theorem unattach_nil {p : α Prop} : (#[] : Array { x // p x }).unattach = #[] := by
@[simp] theorem unattach_empty {p : α Prop} : (#[] : Array { x // p x }).unattach = #[] := by
simp [unattach]
@[deprecated unattach_empty (since := "2025-05-26")]
abbrev unattach_nil := @unattach_empty
@[simp] theorem unattach_push {p : α Prop} {a : { x // p x }} {xs : Array { x // p x }} :
(xs.push a).unattach = xs.unattach.push a.1 := by
simp only [unattach, Array.map_push]

View File

@@ -13,8 +13,8 @@ import Init.Data.UInt.BasicAux
import Init.Data.Repr
import Init.Data.ToString.Basic
import Init.GetElem
import Init.Data.List.ToArrayImpl
import Init.Data.Array.Set
import all Init.Data.List.ToArrayImpl
import all Init.Data.Array.Set
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
@@ -36,8 +36,6 @@ variable {α : Type u}
namespace Array
@[deprecated toList (since := "2024-09-10")] abbrev data := @toList
/-! ### Preliminary theorems -/
@[simp, grind] theorem size_set {xs : Array α} {i : Nat} {v : α} (h : i < xs.size) :
@@ -93,7 +91,8 @@ theorem ext' {xs ys : Array α} (h : xs.toList = ys.toList) : xs = ys := by
@[simp, grind =] theorem getElem_toList {xs : Array α} {i : Nat} (h : i < xs.size) : xs.toList[i] = xs[i] := rfl
@[simp, grind =] theorem getElem?_toList {xs : Array α} {i : Nat} : xs.toList[i]? = xs[i]? := by
simp [getElem?_def]
simp only [getElem?_def, getElem_toList]
simp only [Array.size]
/-- `a ∈ as` is a predicate which asserts that `a` is in the array `as`. -/
-- NB: This is defined as a structure rather than a plain def so that a lemma
@@ -114,6 +113,10 @@ theorem mem_def {a : α} {as : Array α} : a ∈ as ↔ a ∈ as.toList :=
rw [Array.mem_def, getElem_toList]
apply List.getElem_mem
@[simp, grind =] theorem emptyWithCapacity_eq {α n} : @emptyWithCapacity α n = #[] := rfl
@[simp] theorem mkEmpty_eq {α n} : @mkEmpty α n = #[] := rfl
end Array
namespace List
@@ -148,8 +151,6 @@ namespace Array
theorem size_eq_length_toList {xs : Array α} : xs.size = xs.toList.length := rfl
@[deprecated toList_toArray (since := "2024-09-09")] abbrev data_toArray := @List.toList_toArray
/-! ### Externs -/
/--
@@ -167,7 +168,7 @@ Low-level indexing operator which is as fast as a C array read.
This avoids overhead due to unboxing a `Nat` used as an index.
-/
@[extern "lean_array_uget", simp]
@[extern "lean_array_uget", simp, expose]
def uget (a : @& Array α) (i : USize) (h : i.toNat < a.size) : α :=
a[i.toNat]
@@ -190,7 +191,7 @@ Examples:
* `#["orange", "yellow"].pop = #["orange"]`
* `(#[] : Array String).pop = #[]`
-/
@[extern "lean_array_pop"]
@[extern "lean_array_pop", expose]
def pop (xs : Array α) : Array α where
toList := xs.toList.dropLast
@@ -209,7 +210,7 @@ Examples:
* `Array.replicate 3 () = #[(), (), ()]`
* `Array.replicate 0 "anything" = #[]`
-/
@[extern "lean_mk_array"]
@[extern "lean_mk_array", expose]
def replicate {α : Type u} (n : Nat) (v : α) : Array α where
toList := List.replicate n v
@@ -237,7 +238,7 @@ Examples:
* `#["red", "green", "blue", "brown"].swap 1 2 = #["red", "blue", "green", "brown"]`
* `#["red", "green", "blue", "brown"].swap 3 0 = #["brown", "green", "blue", "red"]`
-/
@[extern "lean_array_fswap"]
@[extern "lean_array_fswap", expose]
def swap (xs : Array α) (i j : @& Nat) (hi : i < xs.size := by get_elem_tactic) (hj : j < xs.size := by get_elem_tactic) : Array α :=
let v₁ := xs[i]
let v₂ := xs[j]
@@ -267,8 +268,6 @@ def swapIfInBounds (xs : Array α) (i j : @& Nat) : Array α :=
else xs
else xs
@[deprecated swapIfInBounds (since := "2024-11-24")] abbrev swap! := @swapIfInBounds
/-! ### GetElem instance for `USize`, backed by `uget` -/
instance : GetElem (Array α) USize α fun xs i => i.toNat < xs.size where
@@ -290,6 +289,7 @@ Examples:
* `#[1, 2].isEmpty = false`
* `#[()].isEmpty = false`
-/
@[expose]
def isEmpty (xs : Array α) : Bool :=
xs.size = 0
@@ -331,12 +331,16 @@ Examples:
* `Array.ofFn (n := 3) toString = #["0", "1", "2"]`
* `Array.ofFn (fun i => #["red", "green", "blue"].get i.val i.isLt) = #["red", "green", "blue"]`
-/
def ofFn {n} (f : Fin n α) : Array α := go 0 (emptyWithCapacity n) where
/-- Auxiliary for `ofFn`. `ofFn.go f i acc = acc ++ #[f i, ..., f(n - 1)]` -/
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
go (i : Nat) (acc : Array α) : Array α :=
if h : i < n then go (i+1) (acc.push (f i, h)) else acc
decreasing_by simp_wf; decreasing_trivial_pre_omega
def ofFn {n} (f : Fin n α) : Array α := go (emptyWithCapacity n) n (Nat.le_refl n) where
/-- Auxiliary for `ofFn`. `ofFn.go f acc i h = acc ++ #[f (n - i), ..., f(n - 1)]` -/
go (acc : Array α) : (i : Nat) i n Array α
| i + 1, h =>
have w : n - i - 1 < n :=
Nat.lt_of_lt_of_le (Nat.sub_one_lt (Nat.sub_ne_zero_iff_lt.mpr h)) (Nat.sub_le n i)
go (acc.push (f n - i - 1, w)) i (Nat.le_of_succ_le h)
| 0, _ => acc
-- See also `Array.ofFnM` defined in `Init.Data.Array.OfFn`.
/--
Constructs an array that contains all the numbers from `0` to `n`, exclusive.
@@ -371,7 +375,7 @@ Examples:
* `Array.singleton 5 = #[5]`
* `Array.singleton "one" = #["one"]`
-/
@[inline] protected def singleton (v : α) : Array α := #[v]
@[inline, expose] protected def singleton (v : α) : Array α := #[v]
/--
Returns the last element of an array, or panics if the array is empty.
@@ -400,7 +404,7 @@ that requires a proof the array is non-empty.
def back? (xs : Array α) : Option α :=
xs[xs.size - 1]?
@[deprecated "Use `a[i]?` instead." (since := "2025-02-12")]
@[deprecated "Use `a[i]?` instead." (since := "2025-02-12"), expose]
def get? (xs : Array α) (i : Nat) : Option α :=
if h : i < xs.size then some xs[i] else none
@@ -414,7 +418,7 @@ Examples:
* `#["spinach", "broccoli", "carrot"].swapAt 1 "pepper" = ("broccoli", #["spinach", "pepper", "carrot"])`
* `#["spinach", "broccoli", "carrot"].swapAt 2 "pepper" = ("carrot", #["spinach", "broccoli", "pepper"])`
-/
@[inline] def swapAt (xs : Array α) (i : Nat) (v : α) (hi : i < xs.size := by get_elem_tactic) : α × Array α :=
@[inline, expose] def swapAt (xs : Array α) (i : Nat) (v : α) (hi : i < xs.size := by get_elem_tactic) : α × Array α :=
let e := xs[i]
let xs' := xs.set i v
(e, xs')
@@ -429,7 +433,7 @@ Examples:
* `#["spinach", "broccoli", "carrot"].swapAt! 1 "pepper" = (#["spinach", "pepper", "carrot"], "broccoli")`
* `#["spinach", "broccoli", "carrot"].swapAt! 2 "pepper" = (#["spinach", "broccoli", "pepper"], "carrot")`
-/
@[inline]
@[inline, expose]
def swapAt! (xs : Array α) (i : Nat) (v : α) : α × Array α :=
if h : i < xs.size then
swapAt xs i v
@@ -542,7 +546,7 @@ Examples:
-/
@[inline]
def modify (xs : Array α) (i : Nat) (f : α α) : Array α :=
Id.run <| modifyM xs i f
Id.run <| modifyM xs i (pure <| f ·)
set_option linter.indexVariables false in -- Changing `idx` causes bootstrapping issues, haven't investigated.
/--
@@ -575,7 +579,7 @@ def modifyOp (xs : Array α) (idx : Nat) (f : αα) : Array α :=
loop 0 b
/-- Reference implementation for `forIn'` -/
@[implemented_by Array.forIn'Unsafe]
@[implemented_by Array.forIn'Unsafe, expose]
protected def forIn' {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (as : Array α) (b : β) (f : (a : α) a as β m (ForInStep β)) : m β :=
let rec loop (i : Nat) (h : i as.size) (b : β) : m β := do
match i, h with
@@ -642,7 +646,7 @@ example [Monad m] (f : α → β → m α) :
```
-/
-- Reference implementation for `foldlM`
@[implemented_by foldlMUnsafe]
@[implemented_by foldlMUnsafe, expose]
def foldlM {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : β α m β) (init : β) (as : Array α) (start := 0) (stop := as.size) : m β :=
let fold (stop : Nat) (h : stop as.size) :=
let rec loop (i : Nat) (j : Nat) (b : β) : m β := do
@@ -707,7 +711,7 @@ example [Monad m] (f : α → β → m β) :
```
-/
-- Reference implementation for `foldrM`
@[implemented_by foldrMUnsafe]
@[implemented_by foldrMUnsafe, expose]
def foldrM {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : α β m β) (init : β) (as : Array α) (start := as.size) (stop := 0) : m β :=
let rec fold (i : Nat) (h : i as.size) (b : β) : m β := do
if i == stop then
@@ -762,13 +766,11 @@ def mapM {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m] (f : α
decreasing_by simp_wf; decreasing_trivial_pre_omega
map 0 (emptyWithCapacity as.size)
@[deprecated mapM (since := "2024-11-11")] abbrev sequenceMap := @mapM
/--
Applies the monadic action `f` to every element in the array, along with the element's index and a
proof that the index is in bounds, from left to right. Returns the array of results.
-/
@[inline]
@[inline, expose]
def mapFinIdxM {α : Type u} {β : Type v} {m : Type v Type w} [Monad m]
(as : Array α) (f : (i : Nat) α (h : i < as.size) m β) : m (Array β) :=
let rec @[specialize] map (i : Nat) (j : Nat) (inv : i + j = as.size) (bs : Array β) : m (Array β) := do
@@ -786,7 +788,7 @@ def mapFinIdxM {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m]
Applies the monadic action `f` to every element in the array, along with the element's index, from
left to right. Returns the array of results.
-/
@[inline]
@[inline, expose]
def mapIdxM {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : Nat α m β) (as : Array α) : m (Array β) :=
as.mapFinIdxM fun i a _ => f i a
@@ -832,7 +834,7 @@ Almost! 5
some 10
```
-/
@[inline]
@[inline, expose]
def findSomeM? {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : α m (Option β)) (as : Array α) : m (Option β) := do
for a in as do
match ( f a) with
@@ -913,7 +915,7 @@ The optional parameters `start` and `stop` control the region of the array to be
elements with indices from `start` (inclusive) to `stop` (exclusive) are checked. By default, the
entire array is checked.
-/
@[implemented_by anyMUnsafe]
@[implemented_by anyMUnsafe, expose]
def anyM {α : Type u} {m : Type Type w} [Monad m] (p : α m Bool) (as : Array α) (start := 0) (stop := as.size) : m Bool :=
let any (stop : Nat) (h : stop as.size) :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
@@ -1055,9 +1057,9 @@ Examples:
* `#[1, 2, 3].foldl (· ++ toString ·) "" = "123"`
* `#[1, 2, 3].foldl (s!"({·} {·})") "" = "((( 1) 2) 3)"`
-/
@[inline]
@[inline, expose]
def foldl {α : Type u} {β : Type v} (f : β α β) (init : β) (as : Array α) (start := 0) (stop := as.size) : β :=
Id.run <| as.foldlM f init start stop
Id.run <| as.foldlM (pure <| f · ·) init start stop
/--
Folds a function over an array from the right, accumulating a value starting with `init`. The
@@ -1072,9 +1074,9 @@ Examples:
* `#[1, 2, 3].foldr (toString · ++ ·) "" = "123"`
* `#[1, 2, 3].foldr (s!"({·} {·})") "!" = "(1 (2 (3 !)))"`
-/
@[inline]
@[inline, expose]
def foldr {α : Type u} {β : Type v} (f : α β β) (init : β) (as : Array α) (start := as.size) (stop := 0) : β :=
Id.run <| as.foldrM f init start stop
Id.run <| as.foldrM (pure <| f · ·) init start stop
/--
Computes the sum of the elements of an array.
@@ -1083,7 +1085,7 @@ Examples:
* `#[a, b, c].sum = a + (b + (c + 0))`
* `#[1, 2, 5].sum = 8`
-/
@[inline]
@[inline, expose]
def sum {α} [Add α] [Zero α] : Array α α :=
foldr (· + ·) 0
@@ -1095,7 +1097,7 @@ Examples:
* `#[1, 2, 3, 4, 5].countP (· < 5) = 4`
* `#[1, 2, 3, 4, 5].countP (· > 5) = 0`
-/
@[inline]
@[inline, expose]
def countP {α : Type u} (p : α Bool) (as : Array α) : Nat :=
as.foldr (init := 0) fun a acc => bif p a then acc + 1 else acc
@@ -1107,7 +1109,7 @@ Examples:
* `#[1, 1, 2, 3, 5].count 5 = 1`
* `#[1, 1, 2, 3, 5].count 4 = 0`
-/
@[inline]
@[inline, expose]
def count {α : Type u} [BEq α] (a : α) (as : Array α) : Nat :=
countP (· == a) as
@@ -1120,9 +1122,9 @@ Examples:
* `#["one", "two", "three"].map (·.length) = #[3, 3, 5]`
* `#["one", "two", "three"].map (·.reverse) = #["eno", "owt", "eerht"]`
-/
@[inline]
@[inline, expose]
def map {α : Type u} {β : Type v} (f : α β) (as : Array α) : Array β :=
Id.run <| as.mapM f
Id.run <| as.mapM (pure <| f ·)
instance : Functor Array where
map := map
@@ -1135,9 +1137,9 @@ that the index is valid.
`Array.mapIdx` is a variant that does not provide the function with evidence that the index is
valid.
-/
@[inline]
@[inline, expose]
def mapFinIdx {α : Type u} {β : Type v} (as : Array α) (f : (i : Nat) α (h : i < as.size) β) : Array β :=
Id.run <| as.mapFinIdxM f
Id.run <| as.mapFinIdxM (pure <| f · · ·)
/--
Applies a function to each element of the array along with the index at which that element is found,
@@ -1146,9 +1148,9 @@ returning the array of results.
`Array.mapFinIdx` is a variant that additionally provides the function with a proof that the index
is valid.
-/
@[inline]
@[inline, expose]
def mapIdx {α : Type u} {β : Type v} (f : Nat α β) (as : Array α) : Array β :=
Id.run <| as.mapIdxM f
Id.run <| as.mapIdxM (pure <| f · ·)
/--
Pairs each element of an array with its index, optionally starting from an index other than `0`.
@@ -1157,6 +1159,7 @@ Examples:
* `#[a, b, c].zipIdx = #[(a, 0), (b, 1), (c, 2)]`
* `#[a, b, c].zipIdx 5 = #[(a, 5), (b, 6), (c, 7)]`
-/
@[expose]
def zipIdx (xs : Array α) (start := 0) : Array (α × Nat) :=
xs.mapIdx fun i a => (a, start + i)
@@ -1170,7 +1173,7 @@ Examples:
* `#[7, 6, 5, 8, 1, 2, 6].find? (· < 5) = some 1`
* `#[7, 6, 5, 8, 1, 2, 6].find? (· < 1) = none`
-/
@[inline]
@[inline, expose]
def find? {α : Type u} (p : α Bool) (as : Array α) : Option α :=
Id.run do
for a in as do
@@ -1194,9 +1197,9 @@ Example:
some 10
```
-/
@[inline]
@[inline, expose]
def findSome? {α : Type u} {β : Type v} (f : α Option β) (as : Array α) : Option β :=
Id.run <| as.findSomeM? f
Id.run <| as.findSomeM? (pure <| f ·)
/--
Returns the first non-`none` result of applying the function `f` to each element of the
@@ -1230,7 +1233,7 @@ Examples:
-/
@[inline]
def findSomeRev? {α : Type u} {β : Type v} (f : α Option β) (as : Array α) : Option β :=
Id.run <| as.findSomeRevM? f
Id.run <| as.findSomeRevM? (pure <| f ·)
/--
Returns the last element of the array for which the predicate `p` returns `true`, or `none` if no
@@ -1242,7 +1245,7 @@ Examples:
-/
@[inline]
def findRev? {α : Type} (p : α Bool) (as : Array α) : Option α :=
Id.run <| as.findRevM? p
Id.run <| as.findRevM? (pure <| p ·)
/--
Returns the index of the first element for which `p` returns `true`, or `none` if there is no such
@@ -1252,7 +1255,7 @@ Examples:
* `#[7, 6, 5, 8, 1, 2, 6].findIdx (· < 5) = some 4`
* `#[7, 6, 5, 8, 1, 2, 6].findIdx (· < 1) = none`
-/
@[inline]
@[inline, expose]
def findIdx? {α : Type u} (p : α Bool) (as : Array α) : Option Nat :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
loop (j : Nat) :=
@@ -1306,7 +1309,7 @@ Examples:
* `#[7, 6, 5, 8, 1, 2, 6].findIdx (· < 5) = 4`
* `#[7, 6, 5, 8, 1, 2, 6].findIdx (· < 1) = 7`
-/
@[inline]
@[inline, expose]
def findIdx (p : α Bool) (as : Array α) : Nat := (as.findIdx? p).getD as.size
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
@@ -1360,10 +1363,6 @@ Examples:
def idxOf? [BEq α] (xs : Array α) (v : α) : Option Nat :=
(xs.finIdxOf? v).map (·.val)
@[deprecated idxOf? (since := "2024-11-20")]
def getIdx? [BEq α] (xs : Array α) (v : α) : Option Nat :=
xs.findIdx? fun a => a == v
/--
Returns `true` if `p` returns `true` for any element of `as`.
@@ -1379,9 +1378,9 @@ Examples:
* `#[2, 4, 5, 6].any (· % 2 = 0) = true`
* `#[2, 4, 5, 6].any (· % 2 = 1) = true`
-/
@[inline]
@[inline, expose]
def any (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.anyM p start stop
Id.run <| as.anyM (pure <| p ·) start stop
/--
Returns `true` if `p` returns `true` for every element of `as`.
@@ -1399,7 +1398,7 @@ Examples:
-/
@[inline]
def all (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.allM p start stop
Id.run <| as.allM (pure <| p ·) start stop
/--
Checks whether `a` is an element of `as`, using `==` to compare elements.
@@ -1410,6 +1409,7 @@ Examples:
* `#[1, 4, 2, 3, 3, 7].contains 3 = true`
* `Array.contains #[1, 4, 2, 3, 3, 7] 5 = false`
-/
@[expose]
def contains [BEq α] (as : Array α) (a : α) : Bool :=
as.any (a == ·)
@@ -1458,6 +1458,7 @@ Examples:
* `#[] ++ #[4, 5] = #[4, 5]`.
* `#[1, 2, 3] ++ #[] = #[1, 2, 3]`.
-/
@[expose]
protected def append (as : Array α) (bs : Array α) : Array α :=
bs.foldl (init := as) fun xs v => xs.push v
@@ -1487,8 +1488,6 @@ The resulting arrays are appended.
def flatMapM [Monad m] (f : α m (Array β)) (as : Array α) : m (Array β) :=
as.foldlM (init := empty) fun bs a => do return bs ++ ( f a)
@[deprecated flatMapM (since := "2024-10-16")] abbrev concatMapM := @flatMapM
/--
Applies a function that returns an array to each element of an array. The resulting arrays are
appended.
@@ -1497,12 +1496,10 @@ Examples:
* `#[2, 3, 2].flatMap Array.range = #[0, 1, 0, 1, 2, 0, 1]`
* `#[['a', 'b'], ['c', 'd', 'e']].flatMap List.toArray = #['a', 'b', 'c', 'd', 'e']`
-/
@[inline]
@[inline, expose]
def flatMap (f : α Array β) (as : Array α) : Array β :=
as.foldl (init := empty) fun bs a => bs ++ f a
@[deprecated flatMap (since := "2024-10-16")] abbrev concatMap := @flatMap
/--
Appends the contents of array of arrays into a single array. The resulting array contains the same
elements as the nested arrays in the same order.
@@ -1512,7 +1509,7 @@ Examples:
* `#[#[0, 1], #[], #[2], #[1, 0, 1]].flatten = #[0, 1, 2, 1, 0, 1]`
* `(#[] : Array Nat).flatten = #[]`
-/
@[inline] def flatten (xss : Array (Array α)) : Array α :=
@[inline, expose] def flatten (xss : Array (Array α)) : Array α :=
xss.foldl (init := empty) fun acc xs => acc ++ xs
/--
@@ -1525,6 +1522,7 @@ Examples:
* `#[0, 1].reverse = #[1, 0]`
* `#[0, 1, 2].reverse = #[2, 1, 0]`
-/
@[expose]
def reverse (as : Array α) : Array α :=
if h : as.size 1 then
as
@@ -1557,7 +1555,7 @@ Examples:
* `#[1, 2, 5, 2, 7, 7].filter (fun _ => true) (start := 3) = #[2, 7, 7]`
* `#[1, 2, 5, 2, 7, 7].filter (fun _ => true) (stop := 3) = #[1, 2, 5]`
-/
@[inline]
@[inline, expose]
def filter (p : α Bool) (as : Array α) (start := 0) (stop := as.size) : Array α :=
as.foldl (init := #[]) (start := start) (stop := stop) fun acc a =>
if p a then acc.push a else acc
@@ -1650,7 +1648,7 @@ Examining 7
#[10, 14, 14]
```
-/
@[specialize]
@[specialize, expose]
def filterMapM [Monad m] (f : α m (Option β)) (as : Array α) (start := 0) (stop := as.size) : m (Array β) :=
as.foldlM (init := #[]) (start := start) (stop := stop) fun bs a => do
match ( f a) with
@@ -1670,9 +1668,9 @@ Example:
#[10, 14, 14]
```
-/
@[inline]
@[inline, expose]
def filterMap (f : α Option β) (as : Array α) (start := 0) (stop := as.size) : Array β :=
Id.run <| as.filterMapM f (start := start) (stop := stop)
Id.run <| as.filterMapM (pure <| f ·) (start := start) (stop := stop)
/--
Returns the largest element of the array, as determined by the comparison `lt`, or `none` if
@@ -1883,8 +1881,6 @@ Examples:
let as := as.push a
loop as j, size_push .. j.lt_succ_self
@[deprecated insertIdx (since := "2024-11-20")] abbrev insertAt := @insertIdx
/--
Inserts an element into an array at the specified index. Panics if the index is greater than the
size of the array.
@@ -1905,8 +1901,6 @@ def insertIdx! (as : Array α) (i : Nat) (a : α) : Array α :=
insertIdx as i a
else panic! "invalid index"
@[deprecated insertIdx! (since := "2024-11-20")] abbrev insertAt! := @insertIdx!
/--
Inserts an element into an array at the specified index. The array is returned unmodified if the
index is greater than the size of the array.
@@ -2029,11 +2023,6 @@ Examples:
def unzip (as : Array (α × β)) : Array α × Array β :=
as.foldl (init := (#[], #[])) fun (as, bs) (a, b) => (as.push a, bs.push b)
@[deprecated partition (since := "2024-11-06")]
def split (as : Array α) (p : α Bool) : Array α × Array α :=
as.foldl (init := (#[], #[])) fun (as, bs) a =>
if p a then (as.push a, bs) else (as, bs.push a)
/--
Replaces the first occurrence of `a` with `b` in an array. The modification is performed in-place
when the reference to the array is unique. Returns the array unmodified when `a` is not present.

View File

@@ -6,7 +6,7 @@ Authors: Leonardo de Moura
module
prelude
import Init.Data.Array.Basic
import all Init.Data.Array.Basic
import Init.Data.Nat.Linear
import Init.NotationExtra
@@ -88,4 +88,4 @@ pointer equality, and does not allocate a new array if the result of each functi
pointer-equal to its argument.
-/
@[inline] def Array.mapMono (as : Array α) (f : α α) : Array α :=
Id.run <| as.mapMonoM f
Id.run <| as.mapMonoM (pure <| f ·)

View File

@@ -129,6 +129,6 @@ Examples:
* `#[].binInsert (· < ·) 1 = #[1]`
-/
@[inline] def binInsert {α : Type u} (lt : α α Bool) (as : Array α) (k : α) : Array α :=
Id.run <| binInsertM lt (fun _ => k) (fun _ => k) as k
Id.run <| binInsertM lt (fun _ => pure k) (fun _ => pure k) as k
end Array

View File

@@ -8,6 +8,7 @@ module
prelude
import Init.Data.List.TakeDrop
import all Init.Data.Array.Basic
/-!
## Bootstrapping theorems about arrays
@@ -39,7 +40,7 @@ Use the indexing notation `a[i]!` instead.
Access an element from an array, or panic if the index is out of bounds.
-/
@[deprecated "Use indexing notation `as[i]!` instead" (since := "2025-02-17")]
@[deprecated "Use indexing notation `as[i]!` instead" (since := "2025-02-17"), expose]
def get! {α : Type u} [Inhabited α] (a : @& Array α) (i : @& Nat) : α :=
Array.getD a i default
@@ -52,8 +53,8 @@ theorem foldlM_toList.aux [Monad m]
· rename_i i; rw [Nat.succ_add] at H
simp [foldlM_toList.aux (j := j+1) H]
rw (occs := [2]) [ List.getElem_cons_drop_succ_eq_drop _]
rfl
· rw [List.drop_of_length_le (Nat.ge_of_not_lt _)]; rfl
simp
· rw [List.drop_of_length_le (Nat.ge_of_not_lt _)]; simp
@[simp, grind =] theorem foldlM_toList [Monad m]
{f : β α m β} {init : β} {xs : Array α} :
@@ -69,15 +70,16 @@ theorem foldrM_eq_reverse_foldlM_toList.aux [Monad m]
(xs.toList.take i).reverse.foldlM (fun x y => f y x) init = foldrM.fold f xs 0 i h init := by
unfold foldrM.fold
match i with
| 0 => simp [List.foldlM, List.take]
| 0 => simp
| i+1 => rw [ List.take_concat_get h]; simp [ aux]
theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α β m β} {init : β} {xs : Array α} :
xs.foldrM f init = xs.toList.reverse.foldlM (fun x y => f y x) init := by
have : xs = #[] 0 < xs.size :=
match xs with | [] => .inl rfl | a::l => .inr (Nat.zero_lt_succ _)
match xs, this with | _, .inl rfl => rfl | xs, .inr h => ?_
simp [foldrM, h, foldrM_eq_reverse_foldlM_toList.aux, List.take_length]
match xs, this with | _, .inl rfl => simp [foldrM] | xs, .inr h => ?_
simp only [foldrM, h, foldrM_eq_reverse_foldlM_toList.aux]
simp [Array.size]
@[simp, grind =] theorem foldrM_toList [Monad m]
{f : α β m β} {init : β} {xs : Array α} :
@@ -88,9 +90,13 @@ theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α → β → m β} {init
xs.toList.foldr f init = xs.foldr f init :=
List.foldr_eq_foldrM .. foldrM_toList ..
@[simp, grind =] theorem push_toList {xs : Array α} {a : α} : (xs.push a).toList = xs.toList ++ [a] := by
@[simp, grind =] theorem toList_push {xs : Array α} {x : α} : (xs.push x).toList = xs.toList ++ [x] := by
rcases xs with xs
simp [push, List.concat_eq_append]
@[deprecated toList_push (since := "2025-05-26")]
abbrev push_toList := @toList_push
@[simp, grind =] theorem toListAppend_eq {xs : Array α} {l : List α} : xs.toListAppend l = xs.toList ++ l := by
simp [toListAppend, foldr_toList]
@@ -137,56 +143,4 @@ abbrev nil_append := @empty_append
@[deprecated toList_appendList (since := "2024-12-11")]
abbrev appendList_toList := @toList_appendList
@[deprecated "Use the reverse direction of `foldrM_toList`." (since := "2024-11-13")]
theorem foldrM_eq_foldrM_toList [Monad m]
{f : α β m β} {init : β} {xs : Array α} :
xs.foldrM f init = xs.toList.foldrM f init := by
simp
@[deprecated "Use the reverse direction of `foldlM_toList`." (since := "2024-11-13")]
theorem foldlM_eq_foldlM_toList [Monad m]
{f : β α m β} {init : β} {xs : Array α} :
xs.foldlM f init = xs.toList.foldlM f init:= by
simp
@[deprecated "Use the reverse direction of `foldr_toList`." (since := "2024-11-13")]
theorem foldr_eq_foldr_toList {f : α β β} {init : β} {xs : Array α} :
xs.foldr f init = xs.toList.foldr f init := by
simp
@[deprecated "Use the reverse direction of `foldl_toList`." (since := "2024-11-13")]
theorem foldl_eq_foldl_toList {f : β α β} {init : β} {xs : Array α} :
xs.foldl f init = xs.toList.foldl f init:= by
simp
@[deprecated foldlM_toList (since := "2024-09-09")]
abbrev foldlM_eq_foldlM_data := @foldlM_toList
@[deprecated foldl_toList (since := "2024-09-09")]
abbrev foldl_eq_foldl_data := @foldl_toList
@[deprecated foldrM_eq_reverse_foldlM_toList (since := "2024-09-09")]
abbrev foldrM_eq_reverse_foldlM_data := @foldrM_eq_reverse_foldlM_toList
@[deprecated foldrM_toList (since := "2024-09-09")]
abbrev foldrM_eq_foldrM_data := @foldrM_toList
@[deprecated foldr_toList (since := "2024-09-09")]
abbrev foldr_eq_foldr_data := @foldr_toList
@[deprecated push_toList (since := "2024-09-09")]
abbrev push_data := @push_toList
@[deprecated toListImpl_eq (since := "2024-09-09")]
abbrev toList_eq := @toListImpl_eq
@[deprecated pop_toList (since := "2024-09-09")]
abbrev pop_data := @toList_pop
@[deprecated toList_append (since := "2024-09-09")]
abbrev append_data := @toList_append
@[deprecated toList_appendList (since := "2024-09-09")]
abbrev appendList_data := @toList_appendList
end Array

View File

@@ -6,6 +6,7 @@ Authors: Kim Morrison
module
prelude
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.List.Nat.Count
@@ -51,8 +52,8 @@ theorem countP_push {a : α} {xs : Array α} : countP p (xs.push a) = countP p x
rcases xs with xs
simp_all
@[simp] theorem countP_singleton {a : α} : countP p #[a] = if p a then 1 else 0 := by
simp [countP_push]
theorem countP_singleton {a : α} : countP p #[a] = if p a then 1 else 0 := by
simp
theorem size_eq_countP_add_countP {xs : Array α} : xs.size = countP p xs + countP (fun a => ¬p a) xs := by
rcases xs with xs
@@ -104,6 +105,7 @@ theorem boole_getElem_le_countP {xs : Array α} {i : Nat} (h : i < xs.size) :
theorem countP_set {xs : Array α} {i : Nat} {a : α} (h : i < xs.size) :
(xs.set i a).countP p = xs.countP p - (if p xs[i] then 1 else 0) + (if p a then 1 else 0) := by
rcases xs with xs
simp at h
simp [List.countP_set, h]
theorem countP_filter {xs : Array α} :

View File

@@ -6,7 +6,7 @@ Authors: Leonardo de Moura
module
prelude
import Init.Data.Array.Basic
import all Init.Data.Array.Basic
import Init.Data.BEq
import Init.Data.List.Nat.BEq
import Init.ByCases
@@ -69,7 +69,7 @@ theorem isEqv_eq_decide (xs ys : Array α) (r) :
simpa [isEqv_iff_rel] using h'
@[simp, grind =] theorem isEqv_toList [BEq α] (xs ys : Array α) : (xs.toList.isEqv ys.toList r) = (xs.isEqv ys r) := by
simp [isEqv_eq_decide, List.isEqv_eq_decide]
simp [isEqv_eq_decide, List.isEqv_eq_decide, Array.size]
theorem eq_of_isEqv [DecidableEq α] (xs ys : Array α) (h : Array.isEqv xs ys (fun x y => x = y)) : xs = ys := by
have h, h' := rel_of_isEqv h
@@ -100,7 +100,7 @@ theorem beq_eq_decide [BEq α] (xs ys : Array α) :
simp [BEq.beq, isEqv_eq_decide]
@[simp, grind =] theorem beq_toList [BEq α] (xs ys : Array α) : (xs.toList == ys.toList) = (xs == ys) := by
simp [beq_eq_decide, List.beq_eq_decide]
simp [beq_eq_decide, List.beq_eq_decide, Array.size]
end Array

View File

@@ -6,6 +6,7 @@ Authors: Kim Morrison
module
prelude
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.List.Nat.Erase
import Init.Data.List.Nat.Basic
@@ -23,7 +24,7 @@ open Nat
/-! ### eraseP -/
@[simp] theorem eraseP_empty : #[].eraseP p = #[] := by simp
theorem eraseP_empty : #[].eraseP p = #[] := by simp
theorem eraseP_of_forall_mem_not {xs : Array α} (h : a, a xs ¬p a) : xs.eraseP p = xs := by
rcases xs with xs

View File

@@ -238,11 +238,9 @@ theorem extract_append_left {as bs : Array α} :
(as ++ bs).extract 0 as.size = as.extract 0 as.size := by
simp
@[simp] theorem extract_append_right {as bs : Array α} :
theorem extract_append_right {as bs : Array α} :
(as ++ bs).extract as.size (as.size + i) = bs.extract 0 i := by
simp only [extract_append, extract_size_left, Nat.sub_self, empty_append]
congr 1
omega
simp
@[simp] theorem map_extract {as : Array α} {i j : Nat} :
(as.extract i j).map f = (as.map f).extract i j := by

View File

@@ -7,6 +7,7 @@ module
prelude
import Init.Data.List.Nat.Find
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.Array.Attach
import Init.Data.Array.Range
@@ -141,9 +142,9 @@ abbrev findSome?_mkArray_of_isNone := @findSome?_replicate_of_isNone
@[simp] theorem find?_empty : find? p #[] = none := rfl
@[simp] theorem find?_singleton {a : α} {p : α Bool} :
theorem find?_singleton {a : α} {p : α Bool} :
#[a].find? p = if p a then some a else none := by
simp [singleton_eq_toArray_singleton]
simp
@[simp] theorem findRev?_push_of_pos {xs : Array α} (h : p a) :
findRev? p (xs.push a) = some a := by
@@ -346,7 +347,8 @@ theorem find?_eq_some_iff_getElem {xs : Array α} {p : α → Bool} {b : α} :
/-! ### findIdx -/
@[simp] theorem findIdx_empty : findIdx p #[] = 0 := rfl
theorem findIdx_empty : findIdx p #[] = 0 := rfl
theorem findIdx_singleton {a : α} {p : α Bool} :
#[a].findIdx p = if p a then 0 else 1 := by
simp
@@ -599,7 +601,8 @@ theorem findIdx?_eq_some_le_of_findIdx?_eq_some {xs : Array α} {p q : α → Bo
/-! ### findFinIdx? -/
@[simp] theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp
theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp
theorem findFinIdx?_singleton {a : α} {p : α Bool} :
#[a].findFinIdx? p = if p a then some 0, by simp else none := by
simp
@@ -652,13 +655,13 @@ theorem findFinIdx?_append {xs ys : Array α} {p : α → Bool} :
theorem isSome_findFinIdx? {xs : Array α} {p : α Bool} :
(xs.findFinIdx? p).isSome = xs.any p := by
rcases xs with xs
simp
simp [Array.size]
@[simp]
theorem isNone_findFinIdx? {xs : Array α} {p : α Bool} :
(xs.findFinIdx? p).isNone = xs.all (fun x => ¬ p x) := by
rcases xs with xs
simp
simp [Array.size]
@[simp] theorem findFinIdx?_subtype {p : α Prop} {xs : Array { x // p x }}
{f : { x // p x } Bool} {g : α Bool} (hf : x h, f x, h = g x) :
@@ -666,7 +669,8 @@ theorem isNone_findFinIdx? {xs : Array α} {p : α → Bool} :
cases xs
simp only [List.findFinIdx?_toArray, hf, List.findFinIdx?_subtype]
rw [findFinIdx?_congr List.unattach_toArray]
simp [Function.comp_def]
simp only [Option.map_map, Function.comp_def, Fin.cast_trans]
simp [Array.size]
/-! ### idxOf
@@ -698,7 +702,7 @@ The verification API for `idxOf?` is still incomplete.
The lemmas below should be made consistent with those for `findIdx?` (and proved using them).
-/
@[simp] theorem idxOf?_empty [BEq α] : (#[] : Array α).idxOf? a = none := by simp
theorem idxOf?_empty [BEq α] : (#[] : Array α).idxOf? a = none := by simp
@[simp] theorem idxOf?_eq_none_iff [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
xs.idxOf? a = none a xs := by
@@ -711,14 +715,10 @@ theorem isSome_idxOf? [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
rcases xs with xs
simp
@[simp]
theorem isNone_idxOf? [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
(xs.idxOf? a).isNone = ¬ a xs := by
rcases xs with xs
simp
/-! ### finIdxOf?
The verification API for `finIdxOf?` is still incomplete.
@@ -729,28 +729,27 @@ theorem idxOf?_eq_map_finIdxOf?_val [BEq α] {xs : Array α} {a : α} :
xs.idxOf? a = (xs.finIdxOf? a).map (·.val) := by
simp [idxOf?, finIdxOf?, findIdx?_eq_map_findFinIdx?_val]
@[simp] theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp
theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp
@[simp] theorem finIdxOf?_eq_none_iff [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
xs.finIdxOf? a = none a xs := by
rcases xs with xs
simp [List.finIdxOf?_eq_none_iff]
simp [List.finIdxOf?_eq_none_iff, Array.size]
@[simp] theorem finIdxOf?_eq_some_iff [BEq α] [LawfulBEq α] {xs : Array α} {a : α} {i : Fin xs.size} :
xs.finIdxOf? a = some i xs[i] = a j (_ : j < i), ¬xs[j] = a := by
rcases xs with xs
unfold Array.size at i
simp [List.finIdxOf?_eq_some_iff]
@[simp]
theorem isSome_finIdxOf? [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
(xs.finIdxOf? a).isSome a xs := by
rcases xs with xs
simp
simp [Array.size]
@[simp]
theorem isNone_finIdxOf? [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
(xs.finIdxOf? a).isNone = ¬ a xs := by
rcases xs with xs
simp
end Array

View File

@@ -27,11 +27,13 @@ theorem extLit {n : Nat}
(h : (i : Nat) (hi : i < n) xs.getLit i hsz₁ hi = ys.getLit i hsz₂ hi) : xs = ys :=
Array.ext (hsz₁.trans hsz₂.symm) fun i hi₁ _ => h i (hsz₁ hi₁)
def toListLitAux (xs : Array α) (n : Nat) (hsz : xs.size = n) : (i : Nat), i xs.size List α List α
-- has to be expose for array literal support
@[expose] def toListLitAux (xs : Array α) (n : Nat) (hsz : xs.size = n) : (i : Nat), i xs.size List α List α
| 0, _, acc => acc
| (i+1), hi, acc => toListLitAux xs n hsz i (Nat.le_of_succ_le hi) (xs.getLit i hsz (Nat.lt_of_lt_of_eq (Nat.lt_of_lt_of_le (Nat.lt_succ_self i) hi) hsz) :: acc)
def toArrayLit (xs : Array α) (n : Nat) (hsz : xs.size = n) : Array α :=
-- has to be expose for array literal support
@[expose] def toArrayLit (xs : Array α) (n : Nat) (hsz : xs.size = n) : Array α :=
List.toArray <| toListLitAux xs n hsz n (hsz Nat.le_refl _) []
theorem toArrayLit_eq (xs : Array α) (n : Nat) (hsz : xs.size = n) : xs = toArrayLit xs n hsz := by

View File

@@ -44,6 +44,7 @@ theorem insertIdx_zero {xs : Array α} {x : α} : xs.insertIdx 0 x = #[x] ++ xs
@[simp] theorem size_insertIdx {xs : Array α} (h : i xs.size) : (xs.insertIdx i a).size = xs.size + 1 := by
rcases xs with xs
simp at h
simp [List.length_insertIdx, h]
theorem eraseIdx_insertIdx {i : Nat} {xs : Array α} (h : i xs.size) :

View File

@@ -8,11 +8,13 @@ module
prelude
import Init.Data.Nat.Lemmas
import Init.Data.List.Range
import all Init.Data.List.Control
import Init.Data.List.Nat.TakeDrop
import Init.Data.List.Nat.Modify
import Init.Data.List.Nat.Basic
import Init.Data.List.Monadic
import Init.Data.List.OfFn
import all Init.Data.Array.Bootstrap
import Init.Data.Array.Mem
import Init.Data.Array.DecidableEq
import Init.Data.Array.Lex.Basic
@@ -59,14 +61,9 @@ theorem toArray_eq : List.toArray as = xs ↔ as = xs.toList := by
@[grind] theorem size_empty : (#[] : Array α).size = 0 := rfl
@[simp] theorem emptyWithCapacity_eq {α n} : @emptyWithCapacity α n = #[] := rfl
@[deprecated emptyWithCapacity_eq (since := "2025-03-12")]
theorem mkEmpty_eq {α n} : @mkEmpty α n = #[] := rfl
/-! ### size -/
@[grind ] theorem eq_empty_of_size_eq_zero (h : xs.size = 0) : xs = #[] := by
theorem eq_empty_of_size_eq_zero (h : xs.size = 0) : xs = #[] := by
cases xs
simp_all
@@ -78,7 +75,7 @@ theorem ne_empty_of_size_pos (h : 0 < xs.size) : xs ≠ #[] := by
cases xs
simpa using List.ne_nil_of_length_pos h
theorem size_eq_zero_iff : xs.size = 0 xs = #[] :=
@[simp] theorem size_eq_zero_iff : xs.size = 0 xs = #[] :=
eq_empty_of_size_eq_zero, fun h => h rfl
@[deprecated size_eq_zero_iff (since := "2025-02-24")]
@@ -119,14 +116,11 @@ abbrev size_eq_one := @size_eq_one_iff
/-! ## L[i] and L[i]? -/
@[simp] theorem getElem?_eq_none_iff {xs : Array α} : xs[i]? = none xs.size i := by
by_cases h : i < xs.size
· simp [getElem?_pos, h]
· rw [getElem?_neg xs i h]
simp_all
theorem getElem?_eq_none_iff {xs : Array α} : xs[i]? = none xs.size i := by
simp
@[simp] theorem none_eq_getElem?_iff {xs : Array α} {i : Nat} : none = xs[i]? xs.size i := by
simp [eq_comm (a := none)]
theorem none_eq_getElem?_iff {xs : Array α} {i : Nat} : none = xs[i]? xs.size i := by
simp
theorem getElem?_eq_none {xs : Array α} (h : xs.size i) : xs[i]? = none := by
simp [getElem?_eq_none_iff, h]
@@ -136,23 +130,22 @@ grind_pattern Array.getElem?_eq_none => xs.size ≤ i, xs[i]?
@[simp] theorem getElem?_eq_getElem {xs : Array α} {i : Nat} (h : i < xs.size) : xs[i]? = some xs[i] :=
getElem?_pos ..
theorem getElem?_eq_some_iff {xs : Array α} : xs[i]? = some b h : i < xs.size, xs[i] = b := by
simp [getElem?_def]
theorem getElem?_eq_some_iff {xs : Array α} : xs[i]? = some b h : i < xs.size, xs[i] = b :=
_root_.getElem?_eq_some_iff
@[grind ]
theorem getElem_of_getElem? {xs : Array α} : xs[i]? = some a h : i < xs.size, xs[i] = a :=
getElem?_eq_some_iff.mp
theorem some_eq_getElem?_iff {xs : Array α} : some b = xs[i]? h : i < xs.size, xs[i] = b := by
rw [eq_comm, getElem?_eq_some_iff]
@[simp] theorem some_getElem_eq_getElem?_iff (xs : Array α) (i : Nat) (h : i < xs.size) :
theorem some_getElem_eq_getElem?_iff (xs : Array α) (i : Nat) (h : i < xs.size) :
(some xs[i] = xs[i]?) True := by
simp [h]
simp
@[simp] theorem getElem?_eq_some_getElem_iff (xs : Array α) (i : Nat) (h : i < xs.size) :
theorem getElem?_eq_some_getElem_iff (xs : Array α) (i : Nat) (h : i < xs.size) :
(xs[i]? = some xs[i]) True := by
simp [h]
simp
theorem getElem_eq_iff {xs : Array α} {i : Nat} {h : i < xs.size} : xs[i] = x xs[i]? = some x := by
simp only [getElem?_eq_some_iff]
@@ -175,29 +168,29 @@ theorem getD_getElem? {xs : Array α} {i : Nat} {d : α} :
theorem getElem_push_lt {xs : Array α} {x : α} {i : Nat} (h : i < xs.size) :
have : i < (xs.push x).size := by simp [*, Nat.lt_succ_of_le, Nat.le_of_lt]
(xs.push x)[i] = xs[i] := by
rw [Array.size] at h
simp only [push, getElem_toList, List.concat_eq_append, List.getElem_append_left, h]
@[simp] theorem getElem_push_eq {xs : Array α} {x : α} : (xs.push x)[xs.size] = x := by
simp only [push, getElem_toList, List.concat_eq_append]
rw [List.getElem_append_right] <;> simp [ getElem_toList, Nat.zero_lt_one]
theorem getElem_push {xs : Array α} {x : α} {i : Nat} (h : i < (xs.push x).size) :
@[grind =] theorem getElem_push {xs : Array α} {x : α} {i : Nat} (h : i < (xs.push x).size) :
(xs.push x)[i] = if h : i < xs.size then xs[i] else x := by
by_cases h' : i < xs.size
· simp [getElem_push_lt, h']
· simp at h
simp [getElem_push_lt, Nat.le_antisymm (Nat.le_of_lt_succ h) (Nat.ge_of_not_lt h')]
theorem getElem?_push {xs : Array α} {x} : (xs.push x)[i]? = if i = xs.size then some x else xs[i]? := by
@[grind =] theorem getElem?_push {xs : Array α} {x} : (xs.push x)[i]? = if i = xs.size then some x else xs[i]? := by
simp [getElem?_def, getElem_push]
(repeat' split) <;> first | rfl | omega
@[simp] theorem getElem?_push_size {xs : Array α} {x} : (xs.push x)[xs.size]? = some x := by
simp [getElem?_push]
theorem getElem?_push_size {xs : Array α} {x} : (xs.push x)[xs.size]? = some x := by
simp
@[simp] theorem getElem_singleton {a : α} {i : Nat} (h : i < 1) : #[a][i] = a :=
match i, h with
| 0, _ => rfl
theorem getElem_singleton {a : α} {i : Nat} (h : i < 1) : #[a][i] = a := by
simp
@[grind]
theorem getElem?_singleton {a : α} {i : Nat} : #[a][i]? = if i = 0 then some a else none := by
@@ -244,6 +237,8 @@ theorem back?_pop {xs : Array α} :
/-! ### push -/
@[simp] theorem push_empty : #[].push x = #[x] := rfl
@[simp] theorem push_ne_empty {a : α} {xs : Array α} : xs.push a #[] := by
cases xs
simp
@@ -423,8 +418,7 @@ theorem eq_empty_iff_forall_not_mem {xs : Array α} : xs = #[] ↔ ∀ a, a ∉
theorem eq_of_mem_singleton (h : a #[b]) : a = b := by
simpa using h
@[simp] theorem mem_singleton {a b : α} : a #[b] a = b :=
eq_of_mem_singleton, (by simp [·])
theorem mem_singleton {a b : α} : a #[b] a = b := by simp
theorem forall_mem_push {p : α Prop} {xs : Array α} {a : α} :
( x, x xs.push a p x) p a x, x xs p x := by
@@ -609,13 +603,13 @@ theorem anyM_loop_cons [Monad m] {p : α → m Bool} {a : α} {as : List α} {st
-- Auxiliary for `any_iff_exists`.
theorem anyM_loop_iff_exists {p : α Bool} {as : Array α} {start stop} (h : stop as.size) :
anyM.loop (m := Id) p as stop h start = true
(anyM.loop (m := Id) (pure <| p ·) as stop h start).run = true
(i : Nat) (_ : i < as.size), start i i < stop p as[i] = true := by
unfold anyM.loop
split <;> rename_i h₁
· dsimp
split <;> rename_i h₂
· simp only [true_iff]
· simp only [true_iff, Id.run_pure]
refine start, by omega, by omega, by omega, h₂
· rw [anyM_loop_iff_exists]
constructor
@@ -632,9 +626,9 @@ termination_by stop - start
-- This could also be proved from `SatisfiesM_anyM_iff_exists` in `Batteries.Data.Array.Init.Monadic`
theorem any_iff_exists {p : α Bool} {as : Array α} {start stop} :
as.any p start stop (i : Nat) (_ : i < as.size), start i i < stop p as[i] := by
dsimp [any, anyM, Id.run]
dsimp [any, anyM]
split
· rw [anyM_loop_iff_exists]
· rw [anyM_loop_iff_exists (p := p)]
· rw [anyM_loop_iff_exists]
constructor
· rintro i, hi, ge, _, h
@@ -768,6 +762,7 @@ theorem all_eq_false' {p : α → Bool} {as : Array α} :
rw [Bool.eq_false_iff, Ne, all_eq_true']
simp
@[grind =]
theorem any_eq {xs : Array α} {p : α Bool} : xs.any p = decide ( i : Nat, h, p (xs[i]'h)) := by
by_cases h : xs.any p
· simp_all [any_eq_true]
@@ -782,6 +777,7 @@ theorem any_eq' {xs : Array α} {p : α → Bool} : xs.any p = decide (∃ x, x
simp only [any_eq_false'] at h
simpa using h
@[grind =]
theorem all_eq {xs : Array α} {p : α Bool} : xs.all p = decide ( i, (_ : i < xs.size) p xs[i]) := by
by_cases h : xs.all p
· simp_all [all_eq_true]
@@ -851,7 +847,7 @@ abbrev elem_eq_true_of_mem := @contains_eq_true_of_mem
elem a xs = xs.contains a := by
simp [elem]
@[grind] theorem contains_empty [BEq α] : (#[] : Array α).contains a = false := rfl
@[grind] theorem contains_empty [BEq α] : (#[] : Array α).contains a = false := by simp
theorem elem_iff [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
elem a xs = true a xs := mem_of_contains_eq_true, contains_eq_true_of_mem
@@ -868,8 +864,8 @@ theorem elem_eq_mem [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
@[simp, grind] theorem contains_eq_mem [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
xs.contains a = decide (a xs) := by rw [ elem_eq_contains, elem_eq_mem]
@[simp, grind] theorem any_empty [BEq α] {p : α Bool} : (#[] : Array α).any p = false := rfl
@[simp, grind] theorem all_empty [BEq α] {p : α Bool} : (#[] : Array α).all p = true := rfl
@[grind] theorem any_empty [BEq α] {p : α Bool} : (#[] : Array α).any p = false := by simp
@[grind] theorem all_empty [BEq α] {p : α Bool} : (#[] : Array α).all p = true := by simp
/-- Variant of `any_push` with a side condition on `stop`. -/
@[simp, grind] theorem any_push' [BEq α] {xs : Array α} {a : α} {p : α Bool} (h : stop = xs.size + 1) :
@@ -957,6 +953,13 @@ theorem set_push {xs : Array α} {x y : α} {h} :
· simp at h
omega
@[grind _=_]
theorem set_pop {xs : Array α} {x : α} {i : Nat} (h : i < xs.pop.size) :
xs.pop.set i x h = (xs.set i x (by simp at h; omega)).pop := by
ext i h₁ h₂
· simp
· simp [getElem_set]
@[simp] theorem set_eq_empty_iff {xs : Array α} {i : Nat} {a : α} {h : i < xs.size} :
xs.set i a = #[] xs = #[] := by
cases xs <;> cases i <;> simp [set]
@@ -989,7 +992,11 @@ theorem mem_or_eq_of_mem_set
@[simp, grind] theorem setIfInBounds_empty {i : Nat} {a : α} :
#[].setIfInBounds i a = #[] := rfl
@[simp] theorem set!_eq_setIfInBounds : @set! = @setIfInBounds := rfl
@[simp, grind =] theorem set!_eq_setIfInBounds : set! xs i v = setIfInBounds xs i v := rfl
@[grind]
theorem setIfInBounds_def (xs : Array α) (i : Nat) (a : α) :
xs.setIfInBounds i a = if h : i < xs.size then xs.set i a else xs := rfl
@[deprecated set!_eq_setIfInBounds (since := "2024-12-12")]
abbrev set!_is_setIfInBounds := @set!_eq_setIfInBounds
@@ -1081,7 +1088,7 @@ theorem mem_or_eq_of_mem_setIfInBounds
by_cases h : i < xs.size <;>
simp [setIfInBounds, Nat.not_lt_of_le, h, getD_getElem?]
@[simp] theorem toList_setIfInBounds {xs : Array α} {i : Nat} {x : α} :
@[simp, grind =] theorem toList_setIfInBounds {xs : Array α} {i : Nat} {x : α} :
(xs.setIfInBounds i x).toList = xs.toList.set i x := by
simp only [setIfInBounds]
split <;> rename_i h
@@ -1228,7 +1235,7 @@ where
@[simp] theorem mapM_empty [Monad m] (f : α m β) : mapM f #[] = pure #[] := by
rw [mapM, mapM.map]; rfl
@[simp, grind] theorem map_empty {f : α β} : map f #[] = #[] := mapM_empty f
@[grind] theorem map_empty {f : α β} : map f #[] = #[] := by simp
@[simp, grind] theorem map_push {f : α β} {as : Array α} {x : α} :
(as.push x).map f = (as.map f).push (f x) := by
@@ -1263,7 +1270,8 @@ theorem map_singleton {f : α → β} {a : α} : map f #[a] = #[f a] := by simp
-- We use a lower priority here as there are more specific lemmas in downstream libraries
-- which should be able to fire first.
@[simp 500] theorem mem_map {f : α β} {xs : Array α} : b xs.map f a, a xs f a = b := by
@[simp 500, grind =] theorem mem_map {f : α β} {xs : Array α} :
b xs.map f a, a xs f a = b := by
simp only [mem_def, toList_map, List.mem_map]
theorem exists_of_mem_map (h : b map f l) : a, a l f a = b := mem_map.1 h
@@ -1366,17 +1374,17 @@ theorem mapM_eq_mapM_toList [Monad m] [LawfulMonad m] {f : α → m β} {xs : Ar
@[deprecated "Use `mapM_eq_foldlM` instead" (since := "2025-01-08")]
theorem mapM_map_eq_foldl {as : Array α} {f : α β} {i : Nat} :
mapM.map (m := Id) f as i b = as.foldl (start := i) (fun acc a => acc.push (f a)) b := by
mapM.map (m := Id) (pure <| f ·) as i b = pure (as.foldl (start := i) (fun acc a => acc.push (f a)) b) := by
unfold mapM.map
split <;> rename_i h
· simp only [Id.bind_eq]
dsimp [foldl, Id.run, foldlM]
· ext : 1
dsimp [foldl, foldlM]
rw [mapM_map_eq_foldl, dif_pos (by omega), foldlM.loop, dif_pos h]
-- Calling `split` here gives a bad goal.
have : size as - i = Nat.succ (size as - i - 1) := by omega
rw [this]
simp [foldl, foldlM, Id.run, Nat.sub_add_eq]
· dsimp [foldl, Id.run, foldlM]
simp [foldl, foldlM, Nat.sub_add_eq]
· dsimp [foldl, foldlM]
rw [dif_pos (by omega), foldlM.loop, dif_neg h]
rfl
termination_by as.size - i
@@ -1598,8 +1606,8 @@ theorem filterMap_congr {as bs : Array α} (h : as = bs)
as.toList ++ List.filterMap f xs := ?_
exact this #[]
induction xs
· simp_all [Id.run]
· simp_all [Id.run, List.filterMap_cons]
· simp_all
· simp_all [List.filterMap_cons]
split <;> simp_all
@[grind] theorem toList_filterMap {f : α Option β} {xs : Array α} :
@@ -1813,17 +1821,18 @@ theorem toArray_append {xs : List α} {ys : Array α} :
theorem singleton_eq_toArray_singleton {a : α} : #[a] = [a].toArray := rfl
@[simp] theorem empty_append_fun : ((#[] : Array α) ++ ·) = id := by
@[deprecated empty_append (since := "2025-05-26")]
theorem empty_append_fun : ((#[] : Array α) ++ ·) = id := by
funext l
simp
@[simp, grind] theorem mem_append {a : α} {xs ys : Array α} : a xs ++ ys a xs a ys := by
simp only [mem_def, toList_append, List.mem_append]
@[grind] theorem mem_append_left {a : α} {xs : Array α} (ys : Array α) (h : a xs) : a xs ++ ys :=
theorem mem_append_left {a : α} {xs : Array α} (ys : Array α) (h : a xs) : a xs ++ ys :=
mem_append.2 (Or.inl h)
@[grind] theorem mem_append_right {a : α} (xs : Array α) {ys : Array α} (h : a ys) : a xs ++ ys :=
theorem mem_append_right {a : α} (xs : Array α) {ys : Array α} (h : a ys) : a xs ++ ys :=
mem_append.2 (Or.inr h)
theorem not_mem_append {a : α} {xs ys : Array α} (h₁ : a xs) (h₂ : a ys) : a xs ++ ys :=
@@ -1863,7 +1872,7 @@ theorem getElem_append_right {xs ys : Array α} {h : i < (xs ++ ys).size} (hle :
(xs ++ ys)[i] = ys[i - xs.size]'(Nat.sub_lt_left_of_lt_add hle (size_append .. h)) := by
simp only [ getElem_toList]
have h' : i < (xs.toList ++ ys.toList).length := by rwa [ length_toList, toList_append] at h
conv => rhs; rw [ List.getElem_append_right (h₁ := hle) (h₂ := h')]
conv => rhs; unfold Array.size; rw [ List.getElem_append_right (h₁ := hle) (h₂ := h')]
apply List.get_of_eq; rw [toList_append]
theorem getElem?_append_left {xs ys : Array α} {i : Nat} (hn : i < xs.size) :
@@ -1964,8 +1973,8 @@ theorem append_left_inj {xs₁ xs₂ : Array α} (ys) : xs₁ ++ ys = xs₂ ++ y
theorem eq_empty_of_append_eq_empty {xs ys : Array α} (h : xs ++ ys = #[]) : xs = #[] ys = #[] :=
append_eq_empty_iff.mp h
@[simp] theorem empty_eq_append_iff {xs ys : Array α} : #[] = xs ++ ys xs = #[] ys = #[] := by
rw [eq_comm, append_eq_empty_iff]
theorem empty_eq_append_iff {xs ys : Array α} : #[] = xs ++ ys xs = #[] ys = #[] := by
simp
theorem append_ne_empty_of_left_ne_empty {xs ys : Array α} (h : xs #[]) : xs ++ ys #[] := by
simp_all
@@ -2030,10 +2039,10 @@ theorem append_eq_append_iff {ws xs ys zs : Array α} :
xs ++ ys.set (i - xs.size) x (by simp at h; omega) := by
rcases xs with s
rcases ys with t
simp only [List.append_toArray, List.set_toArray, List.set_append]
simp only [List.append_toArray, List.set_toArray, List.set_append, Array.size]
split <;> simp
@[simp] theorem set_append_left {xs ys : Array α} {i : Nat} {x : α} (h : i < xs.size) :
@[simp] theorem set_append_left {xs ys : Array α} {i : Nat} {x : α} (h : i < xs.size) :
(xs ++ ys).set i x (by simp; omega) = xs.set i x ++ ys := by
simp [set_append, h]
@@ -2050,7 +2059,7 @@ theorem append_eq_append_iff {ws xs ys zs : Array α} :
xs ++ ys.setIfInBounds (i - xs.size) x := by
rcases xs with s
rcases ys with t
simp only [List.append_toArray, List.setIfInBounds_toArray, List.set_append]
simp only [List.append_toArray, List.setIfInBounds_toArray, List.set_append, Array.size]
split <;> simp
@[simp] theorem setIfInBounds_append_left {xs ys : Array α} {i : Nat} {x : α} (h : i < xs.size) :
@@ -2108,14 +2117,13 @@ theorem append_eq_map_iff {f : α → β} :
| nil => simp
| cons as => induction as.toList <;> simp [*]
@[simp] theorem flatten_map_toArray {L : List (List α)} :
(L.toArray.map List.toArray).flatten = L.flatten.toArray := by
@[simp] theorem flatten_toArray_map {L : List (List α)} :
(L.map List.toArray).toArray.flatten = L.flatten.toArray := by
apply ext'
simp [Function.comp_def]
@[simp] theorem flatten_toArray_map {L : List (List α)} :
(L.map List.toArray).toArray.flatten = L.flatten.toArray := by
rw [ flatten_map_toArray]
theorem flatten_map_toArray {L : List (List α)} :
(L.toArray.map List.toArray).flatten = L.flatten.toArray := by
simp
-- We set this to lower priority so that `flatten_toArray_map` is applied first when relevant.
@@ -2143,8 +2151,8 @@ theorem mem_flatten : ∀ {xss : Array (Array α)}, a ∈ xss.flatten ↔ ∃ xs
induction xss using array₂_induction
simp
@[simp] theorem empty_eq_flatten_iff {xss : Array (Array α)} : #[] = xss.flatten xs xss, xs = #[] := by
rw [eq_comm, flatten_eq_empty_iff]
theorem empty_eq_flatten_iff {xss : Array (Array α)} : #[] = xss.flatten xs xss, xs = #[] := by
simp
theorem flatten_ne_empty_iff {xss : Array (Array α)} : xss.flatten #[] xs, xs xss xs #[] := by
simp
@@ -2284,15 +2292,9 @@ theorem eq_iff_flatten_eq {xss₁ xss₂ : Array (Array α)} :
rw [List.map_inj_right]
simp +contextual
@[simp] theorem flatten_toArray_map_toArray {xss : List (List α)} :
theorem flatten_toArray_map_toArray {xss : List (List α)} :
(xss.map List.toArray).toArray.flatten = xss.flatten.toArray := by
simp [flatten]
suffices as, List.foldl (fun acc bs => acc ++ bs) as (List.map List.toArray xss) = as ++ xss.flatten.toArray by
simpa using this #[]
intro as
induction xss generalizing as with
| nil => simp
| cons xs xss ih => simp [ih]
simp
/-! ### flatMap -/
@@ -2322,13 +2324,9 @@ theorem flatMap_toArray_cons {β} {f : α → Array β} {a : α} {as : List α}
intro cs
induction as generalizing cs <;> simp_all
@[simp, grind =] theorem flatMap_toArray {β} {f : α Array β} {as : List α} :
theorem flatMap_toArray {β} {f : α Array β} {as : List α} :
as.toArray.flatMap f = (as.flatMap (fun a => (f a).toList)).toArray := by
induction as with
| nil => simp
| cons a as ih =>
apply ext'
simp [ih, flatMap_toArray_cons]
simp
@[simp] theorem flatMap_id {xss : Array (Array α)} : xss.flatMap id = xss.flatten := by simp [flatMap_def]
@@ -2794,7 +2792,7 @@ theorem reverse_eq_iff {xs ys : Array α} : xs.reverse = ys ↔ xs = ys.reverse
cases xs
simp
@[grind _=_]theorem filterMap_reverse {f : α Option β} {xs : Array α} : (xs.reverse.filterMap f) = (xs.filterMap f).reverse := by
@[grind _=_] theorem filterMap_reverse {f : α Option β} {xs : Array α} : (xs.reverse.filterMap f) = (xs.filterMap f).reverse := by
cases xs
simp
@@ -2883,7 +2881,7 @@ theorem size_extract_loop {xs ys : Array α} {size start : Nat} :
have h := Nat.le_of_not_gt h
rw [extract_loop_of_ge (h:=h), Nat.sub_eq_zero_of_le h, Nat.min_zero, Nat.add_zero]
@[simp, grind] theorem size_extract {xs : Array α} {start stop : Nat} :
@[simp, grind =] theorem size_extract {xs : Array α} {start stop : Nat} :
(xs.extract start stop).size = min stop xs.size - start := by
simp only [extract, Nat.sub_eq, emptyWithCapacity_eq]
rw [size_extract_loop, size_empty, Nat.zero_add, Nat.sub_min_sub_right, Nat.min_assoc,
@@ -2949,7 +2947,7 @@ theorem getElem_extract_aux {xs : Array α} {start stop : Nat} (h : i < (xs.extr
rw [size_extract] at h; apply Nat.add_lt_of_lt_sub'; apply Nat.lt_of_lt_of_le h
apply Nat.sub_le_sub_right; apply Nat.min_le_right
@[simp] theorem getElem_extract {xs : Array α} {start stop : Nat}
@[simp, grind =] theorem getElem_extract {xs : Array α} {start stop : Nat}
(h : i < (xs.extract start stop).size) :
(xs.extract start stop)[i] = xs[start + i]'(getElem_extract_aux h) :=
show (extract.loop xs (min stop xs.size - start) start #[])[i]
@@ -3009,6 +3007,10 @@ theorem extract_empty_of_size_le_start {xs : Array α} {start stop : Nat} (h : x
apply ext'
simp
theorem _root_.List.toArray_drop {l : List α} {k : Nat} :
(l.drop k).toArray = l.toArray.extract k := by
rw [List.drop_eq_extract, List.extract_toArray, List.size_toArray]
@[deprecated extract_size (since := "2025-02-27")]
theorem take_size {xs : Array α} : xs.take xs.size = xs := by
cases xs
@@ -3030,19 +3032,21 @@ theorem take_size {xs : Array α} : xs.take xs.size = xs := by
| succ n ih =>
simp [shrink.loop, ih]
@[simp] theorem size_shrink {xs : Array α} {i : Nat} : (xs.shrink i).size = min i xs.size := by
-- This doesn't need to be a simp lemma, as shortly we will simplify `shrink` to `take`.
theorem size_shrink {xs : Array α} {i : Nat} : (xs.shrink i).size = min i xs.size := by
simp [shrink]
omega
@[simp] theorem getElem_shrink {xs : Array α} {i j : Nat} (h : j < (xs.shrink i).size) :
(xs.shrink i)[j] = xs[j]'(by simp at h; omega) := by
-- This doesn't need to be a simp lemma, as shortly we will simplify `shrink` to `take`.
theorem getElem_shrink {xs : Array α} {i j : Nat} (h : j < (xs.shrink i).size) :
(xs.shrink i)[j] = xs[j]'(by simp [size_shrink] at h; omega) := by
simp [shrink]
@[simp] theorem toList_shrink {xs : Array α} {i : Nat} : (xs.shrink i).toList = xs.toList.take i := by
apply List.ext_getElem <;> simp
@[simp] theorem shrink_eq_take {xs : Array α} {i : Nat} : xs.shrink i = xs.take i := by
ext <;> simp
ext <;> simp [size_shrink, getElem_shrink]
theorem toList_shrink {xs : Array α} {i : Nat} : (xs.shrink i).toList = xs.toList.take i := by
simp
/-! ### foldlM and foldrM -/
@@ -3211,18 +3215,16 @@ theorem foldlM_push [Monad m] [LawfulMonad m] {xs : Array α} {a : α} {f : β
rw [foldr, foldrM_start_stop, foldrM_toList, List.foldrM_pure, foldr_toList, foldr, foldrM_start_stop]
theorem foldl_eq_foldlM {f : β α β} {b} {xs : Array α} {start stop : Nat} :
xs.foldl f b start stop = xs.foldlM (m := Id) f b start stop := by
simp [foldl, Id.run]
xs.foldl f b start stop = (xs.foldlM (m := Id) (pure <| f · ·) b start stop).run := rfl
theorem foldr_eq_foldrM {f : α β β} {b} {xs : Array α} {start stop : Nat} :
xs.foldr f b start stop = xs.foldrM (m := Id) f b start stop := by
simp [foldr, Id.run]
xs.foldr f b start stop = (xs.foldrM (m := Id) (pure <| f · ·) b start stop).run := rfl
@[simp] theorem id_run_foldlM {f : β α Id β} {b} {xs : Array α} {start stop : Nat} :
Id.run (xs.foldlM f b start stop) = xs.foldl f b start stop := foldl_eq_foldlM.symm
Id.run (xs.foldlM f b start stop) = xs.foldl (f · · |>.run) b start stop := rfl
@[simp] theorem id_run_foldrM {f : α β Id β} {b} {xs : Array α} {start stop : Nat} :
Id.run (xs.foldrM f b start stop) = xs.foldr f b start stop := foldr_eq_foldrM.symm
Id.run (xs.foldrM f b start stop) = xs.foldr (f · · |>.run) b start stop := rfl
/-- Variant of `foldlM_reverse` with a side condition for the `stop` argument. -/
@[simp] theorem foldlM_reverse' [Monad m] {xs : Array α} {f : β α m β} {b} {stop : Nat}
@@ -3251,7 +3253,7 @@ theorem foldrM_reverse [Monad m] {xs : Array α} {f : α → β → m β} {b} :
theorem foldrM_push [Monad m] {f : α β m β} {init : β} {xs : Array α} {a : α} :
(xs.push a).foldrM f init = f a init >>= xs.foldrM f := by
simp only [foldrM_eq_reverse_foldlM_toList, push_toList, List.reverse_append, List.reverse_cons,
simp only [foldrM_eq_reverse_foldlM_toList, toList_push, List.reverse_append, List.reverse_cons,
List.reverse_nil, List.nil_append, List.singleton_append, List.foldlM_cons, List.foldlM_reverse]
/--
@@ -3263,6 +3265,22 @@ rather than `(arr.push a).size` as the argument.
(xs.push a).foldrM f init start = f a init >>= xs.foldrM f := by
simp [ foldrM_push, h]
@[simp, grind] theorem _root_.List.foldrM_push_eq_append [Monad m] [LawfulMonad m] {l : List α} {f : α m β} {xs : Array β} :
l.foldrM (fun x xs => xs.push <$> f x) xs = do return xs ++ ( l.reverse.mapM f).toArray := by
induction l with
| nil => simp
| cons a l ih =>
simp [ih]
congr 1
funext l'
congr 1
funext x
simp
@[simp, grind] theorem _root_.List.foldlM_push_eq_append [Monad m] [LawfulMonad m] {l : List α} {f : α m β} {xs : Array β} :
l.foldlM (fun xs x => xs.push <$> f x) xs = do return xs ++ ( l.mapM f).toArray := by
induction l generalizing xs <;> simp [*]
/-! ### foldl / foldr -/
@[grind] theorem foldl_empty {f : β α β} {init : β} : (#[].foldl f init) = init := rfl
@@ -3359,6 +3377,32 @@ rather than `(arr.push a).size` as the argument.
rcases as with as
simp
@[simp, grind] theorem _root_.List.foldr_push_eq_append {l : List α} {f : α β} {xs : Array β} :
l.foldr (fun x xs => xs.push (f x)) xs = xs ++ (l.reverse.map f).toArray := by
induction l <;> simp [*]
/-- Variant of `List.foldr_push_eq_append` specialized to `f = id`. -/
@[simp, grind] theorem _root_.List.foldr_push_eq_append' {l : List α} {xs : Array α} :
l.foldr (fun x xs => xs.push x) xs = xs ++ l.reverse.toArray := by
induction l <;> simp [*]
@[simp, grind] theorem _root_.List.foldl_push_eq_append {l : List α} {f : α β} {xs : Array β} :
l.foldl (fun xs x => xs.push (f x)) xs = xs ++ (l.map f).toArray := by
induction l generalizing xs <;> simp [*]
/-- Variant of `List.foldl_push_eq_append` specialized to `f = id`. -/
@[simp, grind] theorem _root_.List.foldl_push_eq_append' {l : List α} {xs : Array α} :
l.foldl (fun xs x => xs.push x) xs = xs ++ l.toArray := by
simpa using List.foldl_push_eq_append (f := id)
@[deprecated _root_.List.foldl_push_eq_append' (since := "2025-05-18")]
theorem _root_.List.foldl_push {l : List α} {as : Array α} : l.foldl Array.push as = as ++ l.toArray := by
induction l generalizing as <;> simp [*]
@[deprecated _root_.List.foldr_push_eq_append' (since := "2025-05-18")]
theorem _root_.List.foldr_push {l : List α} {as : Array α} : l.foldr (fun a bs => push bs a) as = as ++ l.reverse.toArray := by
rw [List.foldr_eq_foldl_reverse, List.foldl_push_eq_append']
@[simp, grind] theorem foldr_append_eq_append {xs : Array α} {f : α Array β} {ys : Array β} :
xs.foldr (f · ++ ·) ys = (xs.map f).flatten ++ ys := by
rcases xs with xs
@@ -3480,17 +3524,16 @@ theorem foldrM_append [Monad m] [LawfulMonad m] {f : α → β → m β} {b} {xs
@[simp] theorem foldr_append' {f : α β β} {b} {xs ys : Array α} {start : Nat}
(w : start = xs.size + ys.size) :
(xs ++ ys).foldr f b start 0 = xs.foldr f (ys.foldr f b) := by
subst w
simp [foldr_eq_foldrM]
(xs ++ ys).foldr f b start 0 = xs.foldr f (ys.foldr f b) :=
foldrM_append' w
@[grind _=_]theorem foldl_append {β : Type _} {f : β α β} {b} {xs ys : Array α} :
(xs ++ ys).foldl f b = ys.foldl f (xs.foldl f b) := by
simp [foldl_eq_foldlM]
@[grind _=_] theorem foldl_append {β : Type _} {f : β α β} {b} {xs ys : Array α} :
(xs ++ ys).foldl f b = ys.foldl f (xs.foldl f b) :=
foldlM_append
@[grind _=_] theorem foldr_append {f : α β β} {b} {xs ys : Array α} :
(xs ++ ys).foldr f b = xs.foldr f (ys.foldr f b) := by
simp [foldr_eq_foldrM]
(xs ++ ys).foldr f b = xs.foldr f (ys.foldr f b) :=
foldrM_append
@[simp] theorem foldl_flatten' {f : β α β} {b} {xss : Array (Array α)} {stop : Nat}
(w : stop = xss.flatten.size) :
@@ -3519,21 +3562,22 @@ theorem foldrM_append [Monad m] [LawfulMonad m] {f : α → β → m β} {b} {xs
/-- Variant of `foldl_reverse` with a side condition for the `stop` argument. -/
@[simp] theorem foldl_reverse' {xs : Array α} {f : β α β} {b} {stop : Nat}
(w : stop = xs.size) :
xs.reverse.foldl f b 0 stop = xs.foldr (fun x y => f y x) b := by
simp [w, foldl_eq_foldlM, foldr_eq_foldrM]
xs.reverse.foldl f b 0 stop = xs.foldr (fun x y => f y x) b :=
foldlM_reverse' w
/-- Variant of `foldr_reverse` with a side condition for the `start` argument. -/
@[simp] theorem foldr_reverse' {xs : Array α} {f : α β β} {b} {start : Nat}
(w : start = xs.size) :
xs.reverse.foldr f b start 0 = xs.foldl (fun x y => f y x) b := by
simp [w, foldl_eq_foldlM, foldr_eq_foldrM]
xs.reverse.foldr f b start 0 = xs.foldl (fun x y => f y x) b :=
foldrM_reverse' w
@[grind] theorem foldl_reverse {xs : Array α} {f : β α β} {b} :
xs.reverse.foldl f b = xs.foldr (fun x y => f y x) b := by simp [foldl_eq_foldlM, foldr_eq_foldrM]
xs.reverse.foldl f b = xs.foldr (fun x y => f y x) b :=
foldlM_reverse
@[grind] theorem foldr_reverse {xs : Array α} {f : α β β} {b} :
xs.reverse.foldr f b = xs.foldl (fun x y => f y x) b :=
(foldl_reverse ..).symm.trans <| by simp
foldrM_reverse
theorem foldl_eq_foldr_reverse {xs : Array α} {f : β α β} {b} :
xs.foldl f b = xs.reverse.foldr (fun x y => f y x) b := by simp
@@ -3614,7 +3658,7 @@ theorem foldr_rel {xs : Array α} {f g : α → β → β} {a b : β} {r : β
theorem back?_eq_some_iff {xs : Array α} {a : α} :
xs.back? = some a ys : Array α, xs = ys.push a := by
rcases xs with xs
simp only [List.back?_toArray, List.getLast?_eq_some_iff, toArray_eq, push_toList]
simp only [List.back?_toArray, List.getLast?_eq_some_iff, toArray_eq, toList_push]
constructor
· rintro ys, rfl
exact ys.toArray, by simp
@@ -3704,7 +3748,7 @@ theorem back?_replicate {a : α} {n : Nat} :
@[deprecated back?_replicate (since := "2025-03-18")]
abbrev back?_mkArray := @back?_replicate
@[simp] theorem back_replicate (w : 0 < n) : (replicate n a).back (by simpa using w) = a := by
@[simp] theorem back_replicate {xs : Array α} (w : 0 < n) : (replicate n xs).back (by simpa using w) = xs := by
simp [back_eq_getElem]
@[deprecated back_replicate (since := "2025-03-18")]
@@ -3739,6 +3783,7 @@ theorem contains_iff_exists_mem_beq [BEq α] {xs : Array α} {a : α} :
rcases xs with xs
simp [List.contains_iff_exists_mem_beq]
@[grind _=_]
theorem contains_iff_mem [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
xs.contains a a xs := by
simp
@@ -4046,18 +4091,19 @@ abbrev all_mkArray := @all_replicate
/-! ### modify -/
@[simp] theorem size_modify {xs : Array α} {i : Nat} {f : α α} : (xs.modify i f).size = xs.size := by
unfold modify modifyM Id.run
@[simp, grind =] theorem size_modify {xs : Array α} {i : Nat} {f : α α} : (xs.modify i f).size = xs.size := by
unfold modify modifyM
split <;> simp
theorem getElem_modify {xs : Array α} {j i} (h : i < (xs.modify j f).size) :
@[grind =] theorem getElem_modify {xs : Array α} {j i} (h : i < (xs.modify j f).size) :
(xs.modify j f)[i] = if j = i then f (xs[i]'(by simpa using h)) else xs[i]'(by simpa using h) := by
simp only [modify, modifyM, Id.run, Id.pure_eq]
simp only [modify, modifyM]
split
· simp only [Id.bind_eq, getElem_set]; split <;> simp [*]
· rw [if_neg (mt (by rintro rfl; exact h) (by simp_all))]
· simp only [getElem_set, Id.run_pure, Id.run_bind]; split <;> simp [*]
· simp only [Id.run_pure]
rw [if_neg (mt (by rintro rfl; exact h) (by simp_all))]
@[simp] theorem toList_modify {xs : Array α} {f : α α} {i : Nat} :
@[simp, grind =] theorem toList_modify {xs : Array α} {f : α α} {i : Nat} :
(xs.modify i f).toList = xs.toList.modify i f := by
apply List.ext_getElem
· simp
@@ -4072,7 +4118,7 @@ theorem getElem_modify_of_ne {xs : Array α} {i : Nat} (h : i ≠ j)
(xs.modify i f)[j] = xs[j]'(by simpa using hj) := by
simp [getElem_modify hj, h]
theorem getElem?_modify {xs : Array α} {i : Nat} {f : α α} {j : Nat} :
@[grind =] theorem getElem?_modify {xs : Array α} {i : Nat} {f : α α} {j : Nat} :
(xs.modify i f)[j]? = if i = j then xs[j]?.map f else xs[j]? := by
simp only [getElem?_def, size_modify, getElem_modify, Option.map_dif]
split <;> split <;> rfl
@@ -4121,20 +4167,18 @@ theorem swap_comm {xs : Array α} {i j : Nat} (hi hj) : xs.swap i j hi hj = xs.s
· split <;> simp_all
· split <;> simp_all
@[simp] theorem size_swapIfInBounds {xs : Array α} {i j : Nat} :
@[simp, grind =] theorem size_swapIfInBounds {xs : Array α} {i j : Nat} :
(xs.swapIfInBounds i j).size = xs.size := by unfold swapIfInBounds; split <;> (try split) <;> simp [size_swap]
@[deprecated size_swapIfInBounds (since := "2024-11-24")] abbrev size_swap! := @size_swapIfInBounds
/-! ### swapAt -/
@[simp] theorem swapAt_def {xs : Array α} {i : Nat} {v : α} (hi) :
@[simp, grind =] theorem swapAt_def {xs : Array α} {i : Nat} {v : α} (hi) :
xs.swapAt i v hi = (xs[i], xs.set i v) := rfl
theorem size_swapAt {xs : Array α} {i : Nat} {v : α} (hi) :
(xs.swapAt i v hi).2.size = xs.size := by simp
@[simp]
@[simp, grind =]
theorem swapAt!_def {xs : Array α} {i : Nat} {v : α} (h : i < xs.size) :
xs.swapAt! i v = (xs[i], xs.set i v) := by simp [swapAt!, h]
@@ -4150,7 +4194,7 @@ theorem swapAt!_def {xs : Array α} {i : Nat} {v : α} (h : i < xs.size) :
section replace
variable [BEq α]
@[simp, grind] theorem replace_empty : (#[] : Array α).replace a b = #[] := by rfl
@[simp, grind] theorem replace_empty : (#[] : Array α).replace a b = #[] := by simp [replace]
@[simp, grind] theorem replace_singleton {a b c : α} : #[a].replace b c = #[if a == b then c else a] := by
simp only [replace, List.finIdxOf?_toArray, List.finIdxOf?]
@@ -4257,42 +4301,44 @@ Examples:
/-! ### Preliminaries about `ofFn` -/
@[simp] theorem size_ofFn_go {n} {f : Fin n α} {i acc} :
(ofFn.go f i acc).size = acc.size + (n - i) := by
if hin : i < n then
unfold ofFn.go
have : 1 + (n - (i + 1)) = n - i :=
Nat.sub_sub .. Nat.add_sub_cancel' (Nat.le_sub_of_add_le (Nat.add_comm .. hin))
rw [dif_pos hin, size_ofFn_go, size_push, Nat.add_assoc, this]
else
have : n - i = 0 := Nat.sub_eq_zero_of_le (Nat.le_of_not_lt hin)
unfold ofFn.go
simp [hin, this]
termination_by n - i
@[simp] theorem size_ofFn_go {n} {f : Fin n α} {i acc h} :
(ofFn.go f acc i h).size = acc.size + i := by
induction i generalizing acc with
| zero => simp [ofFn.go]
| succ i ih =>
simpa [ofFn.go, ih] using Nat.succ_add_eq_add_succ acc.size i
@[simp] theorem size_ofFn {n : Nat} {f : Fin n α} : (ofFn f).size = n := by simp [ofFn]
theorem getElem_ofFn_go {f : Fin n α} {i} {acc k}
(hki : k < n) (hin : i n) (hi : i = acc.size)
(hacc : j, hj : j < acc.size, acc[j] = f j, Nat.lt_of_lt_of_le hj (hi hin)) :
haveI : acc.size + (n - acc.size) = n := Nat.add_sub_cancel' (hi hin)
(ofFn.go f i acc)[k]'(by simp [*]) = f k, hki := by
unfold ofFn.go
if hin : i < n then
have : 1 + (n - (i + 1)) = n - i :=
Nat.sub_sub .. Nat.add_sub_cancel' (Nat.le_sub_of_add_le (Nat.add_comm .. hin))
simp only [dif_pos hin]
rw [getElem_ofFn_go _ hin (by simp [*]) (fun j hj => ?hacc)]
cases (Nat.lt_or_eq_of_le <| Nat.le_of_lt_succ (by simpa using hj)) with
| inl hj => simp [getElem_push, hj, hacc j hj]
| inr hj => simp [getElem_push, *]
else
simp [hin, hacc k (Nat.lt_of_lt_of_le hki (Nat.le_of_not_lt (hi hin)))]
termination_by n - i
-- Recall `ofFn.go f acc i h = acc ++ #[f (n - i), ..., f(n - 1)]`
theorem getElem_ofFn_go {f : Fin n α} {acc i k} (h : i n) (w₁ : k < acc.size + i) :
(ofFn.go f acc i h)[k]'(by simpa using w₁) =
if w₂ : k < acc.size then acc[k] else f n - i + k - acc.size, by omega := by
induction i generalizing acc k with
| zero =>
simp at w₁
simp_all [ofFn.go]
| succ i ih =>
unfold ofFn.go
rw [ih]
· simp only [size_push]
split <;> rename_i h'
· rw [Array.getElem_push]
split
· rfl
· congr 2
omega
· split
· omega
· congr 2
omega
· simp
omega
@[simp] theorem getElem_ofFn {f : Fin n α} {i : Nat} (h : i < (ofFn f).size) :
(ofFn f)[i] = f i, size_ofFn (f := f) h :=
getElem_ofFn_go _ (by simp) (by simp) nofun
(ofFn f)[i] = f i, size_ofFn (f := f) h := by
unfold ofFn
rw [getElem_ofFn_go] <;> simp_all
theorem getElem?_ofFn {f : Fin n α} {i : Nat} :
(ofFn f)[i]? = if h : i < n then some (f i, h) else none := by
@@ -4300,42 +4346,44 @@ theorem getElem?_ofFn {f : Fin n → α} {i : Nat} :
/-! ### Preliminaries about `range` and `range'` -/
@[simp] theorem size_range' {start size step} : (range' start size step).size = size := by
@[simp, grind =] theorem size_range' {start size step} : (range' start size step).size = size := by
simp [range']
@[simp] theorem toList_range' {start size step} :
@[simp, grind =] theorem toList_range' {start size step} :
(range' start size step).toList = List.range' start size step := by
apply List.ext_getElem <;> simp [range']
@[simp]
@[simp, grind =]
theorem getElem_range' {start size step : Nat} {i : Nat}
(h : i < (Array.range' start size step).size) :
(Array.range' start size step)[i] = start + step * i := by
simp [ getElem_toList]
@[grind =]
theorem getElem?_range' {start size step : Nat} {i : Nat} :
(Array.range' start size step)[i]? = if i < size then some (start + step * i) else none := by
simp [getElem?_def, getElem_range']
@[simp] theorem _root_.List.toArray_range' {start size step : Nat} :
@[simp, grind =] theorem _root_.List.toArray_range' {start size step : Nat} :
(List.range' start size step).toArray = Array.range' start size step := by
apply ext'
simp
@[simp] theorem size_range {n : Nat} : (range n).size = n := by
@[simp, grind =] theorem size_range {n : Nat} : (range n).size = n := by
simp [range]
@[simp] theorem toList_range {n : Nat} : (range n).toList = List.range n := by
@[simp, grind =] theorem toList_range {n : Nat} : (range n).toList = List.range n := by
apply List.ext_getElem <;> simp [range]
@[simp]
@[simp, grind =]
theorem getElem_range {n : Nat} {i : Nat} (h : i < (Array.range n).size) : (Array.range n)[i] = i := by
simp [ getElem_toList]
@[grind =]
theorem getElem?_range {n : Nat} {i : Nat} : (Array.range n)[i]? = if i < n then some i else none := by
simp [getElem?_def, getElem_range]
@[simp] theorem _root_.List.toArray_range {n : Nat} : (List.range n).toArray = Array.range n := by
@[simp, grind =] theorem _root_.List.toArray_range {n : Nat} : (List.range n).toArray = Array.range n := by
apply ext'
simp
@@ -4383,7 +4431,8 @@ theorem getElem!_eq_getD [Inhabited α] {xs : Array α} {i} : xs[i]! = xs.getD i
/-! # mem -/
@[simp, grind =] theorem mem_toList {a : α} {xs : Array α} : a xs.toList a xs := mem_def.symm
@[deprecated mem_toList_iff (since := "2025-05-26")]
theorem mem_toList {a : α} {xs : Array α} : a xs.toList a xs := mem_def.symm
@[deprecated not_mem_empty (since := "2025-03-25")]
theorem not_mem_nil (a : α) : ¬ a #[] := nofun
@@ -4403,7 +4452,7 @@ theorem getElem?_size_le {xs : Array α} {i : Nat} (h : xs.size ≤ i) : xs[i]?
simp [getElem?_neg, h]
theorem getElem_mem_toList {xs : Array α} {i : Nat} (h : i < xs.size) : xs[i] xs.toList := by
simp only [ getElem_toList, List.getElem_mem]
simp only [ getElem_toList, List.getElem_mem, ugetElem_eq_getElem]
theorem back!_eq_back? [Inhabited α] {xs : Array α} : xs.back! = xs.back?.getD default := by
simp [back!, back?, getElem!_def, Option.getD]; rfl
@@ -4432,7 +4481,7 @@ theorem getElem?_push_eq {xs : Array α} {x : α} : (xs.push x)[xs.size]? = some
simp
@[simp, grind =] theorem forIn'_toList [Monad m] {xs : Array α} {b : β} {f : (a : α) a xs.toList β m (ForInStep β)} :
forIn' xs.toList b f = forIn' xs b (fun a m b => f a (mem_toList.mpr m) b) := by
forIn' xs.toList b f = forIn' xs b (fun a m b => f a (mem_toList_iff.mpr m) b) := by
cases xs
simp
@@ -4471,6 +4520,7 @@ abbrev contains_def [DecidableEq α] {a : α} {xs : Array α} : xs.contains a
@[simp] theorem size_zipWith {xs : Array α} {ys : Array β} {f : α β γ} :
(zipWith f xs ys).size = min xs.size ys.size := by
rw [size_eq_length_toList, toList_zipWith, List.length_zipWith]
simp only [Array.size]
@[simp] theorem size_zip {xs : Array α} {ys : Array β} :
(zip xs ys).size = min xs.size ys.size :=
@@ -4529,8 +4579,8 @@ Our goal is to have `simp` "pull `List.toArray` outwards" as much as possible.
theorem toListRev_toArray {l : List α} : l.toArray.toListRev = l.reverse := by simp
@[simp, grind =] theorem take_toArray {l : List α} {i : Nat} : l.toArray.take i = (l.take i).toArray := by
apply Array.ext <;> simp
@[grind =] theorem take_toArray {l : List α} {i : Nat} : l.toArray.take i = (l.take i).toArray := by
simp
@[simp, grind =] theorem mapM_toArray [Monad m] [LawfulMonad m] {f : α m β} {l : List α} :
l.toArray.mapM f = List.toArray <$> l.mapM f := by
@@ -4543,7 +4593,7 @@ theorem toListRev_toArray {l : List α} : l.toArray.toListRev = l.reverse := by
| nil => simp
| cons a l ih =>
simp only [foldlM_toArray] at ih
rw [size_toArray, mapM'_cons, foldlM_toArray]
rw [size_toArray, mapM'_cons]
simp [ih]
theorem uset_toArray {l : List α} {i : USize} {a : α} {h : i.toNat < l.toArray.size} :
@@ -4596,12 +4646,12 @@ namespace Array
@[simp] theorem findSomeRev?_eq_findSome?_reverse {f : α Option β} {xs : Array α} :
xs.findSomeRev? f = xs.reverse.findSome? f := by
cases xs
simp [findSomeRev?, Id.run]
simp [findSomeRev?]
@[simp] theorem findRev?_eq_find?_reverse {f : α Bool} {xs : Array α} :
xs.findRev? f = xs.reverse.find? f := by
cases xs
simp [findRev?, Id.run]
simp [findRev?]
/-! ### unzip -/
@@ -4657,13 +4707,6 @@ namespace List
end List
/-! ### Deprecations -/
namespace List
@[deprecated setIfInBounds_toArray (since := "2024-11-24")] abbrev setD_toArray := @setIfInBounds_toArray
end List
namespace Array
@[deprecated size_toArray (since := "2024-12-11")]
@@ -4703,11 +4746,6 @@ theorem get!_eq_getD_getElem? [Inhabited α] (xs : Array α) (i : Nat) :
set_option linter.deprecated false in
@[deprecated get!_eq_getD_getElem? (since := "2025-02-12")] abbrev get!_eq_getElem? := @get!_eq_getD_getElem?
@[deprecated mem_of_back? (since := "2024-10-21")] abbrev mem_of_back?_eq_some := @mem_of_back?
@[deprecated getElem?_size_le (since := "2024-10-21")] abbrev get?_len_le := @getElem?_size_le
set_option linter.deprecated false in
@[deprecated "`Array.get?` is deprecated, use `a[i]?` instead." (since := "2025-02-12")]
theorem get?_eq_get?_toList (xs : Array α) (i : Nat) : xs.get? i = xs.toList.get? i := by
@@ -4716,56 +4754,11 @@ theorem get?_eq_get?_toList (xs : Array α) (i : Nat) : xs.get? i = xs.toList.ge
set_option linter.deprecated false in
@[deprecated get!_eq_getD_getElem? (since := "2025-02-12")] abbrev get!_eq_get? := @get!_eq_getD_getElem?
@[deprecated getElem?_push_lt (since := "2024-10-21")] abbrev get?_push_lt := @getElem?_push_lt
@[deprecated getElem?_push_eq (since := "2024-10-21")] abbrev get?_push_eq := @getElem?_push_eq
@[deprecated getElem?_push (since := "2024-10-21")] abbrev get?_push := @getElem?_push
@[deprecated getElem?_size (since := "2024-10-21")] abbrev get?_size := @getElem?_size
@[deprecated getElem_set_self (since := "2025-01-17")]
theorem get_set_eq (xs : Array α) (i : Nat) (v : α) (h : i < xs.size) :
(xs.set i v h)[i]'(by simp [h]) = v := by
simp only [set, getElem_toList, List.getElem_set_self]
@[deprecated foldl_toList_eq_flatMap (since := "2024-10-16")]
abbrev foldl_toList_eq_bind := @foldl_toList_eq_flatMap
@[deprecated foldl_toList_eq_flatMap (since := "2024-10-16")]
abbrev foldl_data_eq_bind := @foldl_toList_eq_flatMap
@[deprecated getElem_mem (since := "2024-10-17")]
abbrev getElem?_mem := @getElem_mem
@[deprecated getElem_fin_eq_getElem_toList (since := "2024-10-17")]
abbrev getElem_fin_eq_toList_get := @getElem_fin_eq_getElem_toList
@[deprecated "Use reverse direction of `getElem?_toList`" (since := "2024-10-17")]
abbrev getElem?_eq_toList_getElem? := @getElem?_toList
@[deprecated getElem?_swap (since := "2024-10-17")] abbrev get?_swap := @getElem?_swap
@[deprecated getElem_push (since := "2024-10-21")] abbrev get_push := @getElem_push
@[deprecated getElem_push_lt (since := "2024-10-21")] abbrev get_push_lt := @getElem_push_lt
@[deprecated getElem_push_eq (since := "2024-10-21")] abbrev get_push_eq := @getElem_push_eq
@[deprecated back!_eq_back? (since := "2024-10-31")] abbrev back_eq_back? := @back!_eq_back?
@[deprecated back!_push (since := "2024-10-31")] abbrev back_push := @back!_push
@[deprecated eq_push_pop_back!_of_size_ne_zero (since := "2024-10-31")]
abbrev eq_push_pop_back_of_size_ne_zero := @eq_push_pop_back!_of_size_ne_zero
@[deprecated set!_is_setIfInBounds (since := "2024-11-24")] abbrev set_is_setIfInBounds := @set!_eq_setIfInBounds
@[deprecated size_setIfInBounds (since := "2024-11-24")] abbrev size_setD := @size_setIfInBounds
@[deprecated getElem_setIfInBounds_eq (since := "2024-11-24")] abbrev getElem_setD_eq := @getElem_setIfInBounds_self
@[deprecated getElem?_setIfInBounds_eq (since := "2024-11-24")] abbrev get?_setD_eq := @getElem?_setIfInBounds_self
@[deprecated getD_getElem?_setIfInBounds (since := "2025-04-04")] abbrev getD_get?_setIfInBounds := @getD_getElem?_setIfInBounds
@[deprecated getD_getElem?_setIfInBounds (since := "2024-11-24")] abbrev getD_setD := @getD_getElem?_setIfInBounds
@[deprecated getElem_setIfInBounds (since := "2024-11-24")] abbrev getElem_setD := @getElem_setIfInBounds
@[deprecated List.getElem_toArray (since := "2024-11-29")]
theorem getElem_mk {xs : List α} {i : Nat} (h : i < xs.length) : (Array.mk xs)[i] = xs[i] := rfl
@[deprecated Array.getElem_toList (since := "2024-12-08")]
theorem getElem_eq_getElem_toList {xs : Array α} (h : i < xs.size) : xs[i] = xs.toList[i] := rfl

View File

@@ -6,6 +6,7 @@ Author: Kim Morrison
module
prelude
import all Init.Data.Array.Lex.Basic
import Init.Data.Array.Lemmas
import Init.Data.List.Lex
@@ -22,22 +23,18 @@ namespace Array
@[simp, grind =] theorem lt_toList [LT α] {xs ys : Array α} : xs.toList < ys.toList xs < ys := Iff.rfl
@[simp, grind =] theorem le_toList [LT α] {xs ys : Array α} : xs.toList ys.toList xs ys := Iff.rfl
protected theorem not_lt_iff_ge [LT α] {l₁ l₂ : List α} : ¬ l₁ < l₂ l₂ l₁ := Iff.rfl
protected theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] {l₁ l₂ : List α} :
¬ l₁ l₂ l₂ < l₁ :=
protected theorem not_lt_iff_ge [LT α] {xs ys : Array α} : ¬ xs < ys ys xs := Iff.rfl
protected theorem not_le_iff_gt [DecidableEq α] [LT α] [DecidableLT α] {xs ys : Array α} :
¬ xs ys ys < xs :=
Decidable.not_not
@[simp] theorem lex_empty [BEq α] {lt : α α Bool} {xs : Array α} : xs.lex #[] lt = false := by
simp [lex, Id.run]
@[simp] theorem singleton_lex_singleton [BEq α] {lt : α α Bool} : #[a].lex #[b] lt = lt a b := by
simp only [lex, List.getElem_toArray, List.getElem_singleton]
cases lt a b <;> cases a != b <;> simp [Id.run]
simp [lex]
private theorem cons_lex_cons [BEq α] {lt : α α Bool} {a b : α} {xs ys : Array α} :
(#[a] ++ xs).lex (#[b] ++ ys) lt =
(lt a b || a == b && xs.lex ys lt) := by
simp only [lex, Id.run]
simp only [lex]
simp only [Std.Range.forIn'_eq_forIn'_range', size_append, List.size_toArray, List.length_singleton,
Nat.add_comm 1]
simp [Nat.add_min_add_right, List.range'_succ, getElem_append_left, List.range'_succ_left,
@@ -50,13 +47,16 @@ private theorem cons_lex_cons [BEq α] {lt : αα → Bool} {a b : α} {xs
@[simp, grind =] theorem _root_.List.lex_toArray [BEq α] {lt : α α Bool} {l₁ l₂ : List α} :
l₁.toArray.lex l₂.toArray lt = l₁.lex l₂ lt := by
induction l₁ generalizing l₂ with
| nil => cases l₂ <;> simp [lex, Id.run]
| nil => cases l₂ <;> simp [lex]
| cons x l₁ ih =>
cases l₂ with
| nil => simp [lex, Id.run]
| nil => simp [lex]
| cons y l₂ =>
rw [List.toArray_cons, List.toArray_cons y, cons_lex_cons, List.lex, ih]
theorem singleton_lex_singleton [BEq α] {lt : α α Bool} : #[a].lex #[b] lt = lt a b := by
simp
@[simp, grind =] theorem lex_toList [BEq α] {lt : α α Bool} {xs ys : Array α} :
xs.toList.lex ys.toList lt = xs.lex ys lt := by
cases xs <;> cases ys <;> simp

View File

@@ -6,10 +6,11 @@ Authors: Mario Carneiro, Kim Morrison
module
prelude
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.Array.Attach
import Init.Data.Array.OfFn
import Init.Data.List.MapIdx
import all Init.Data.List.MapIdx
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
@@ -26,7 +27,7 @@ theorem mapFinIdx_induction (xs : Array α) (f : (i : Nat) → α → (h : i < x
motive xs.size eq : (Array.mapFinIdx xs f).size = xs.size,
i h, p i ((Array.mapFinIdx xs f)[i]) h := by
let rec go {bs i j h} (h₁ : j = bs.size) (h₂ : i h h', p i bs[i] h) (hm : motive j) :
let as : Array β := Array.mapFinIdxM.map (m := Id) xs f i j h bs
let as : Array β := Id.run <| Array.mapFinIdxM.map xs (pure <| f · · ·) i j h bs
motive xs.size eq : as.size = xs.size, i h, p i as[i] h := by
induction i generalizing j bs with simp [mapFinIdxM.map]
| zero =>
@@ -191,7 +192,8 @@ theorem mapFinIdx_empty {f : (i : Nat) → α → (h : i < 0) → β} : mapFinId
theorem mapFinIdx_eq_ofFn {xs : Array α} {f : (i : Nat) α (h : i < xs.size) β} :
xs.mapFinIdx f = Array.ofFn fun i : Fin xs.size => f i xs[i] i.2 := by
cases xs
simp [List.mapFinIdx_eq_ofFn]
simp only [List.mapFinIdx_toArray, List.mapFinIdx_eq_ofFn, Fin.getElem_fin, List.getElem_toArray]
simp [Array.size]
theorem mapFinIdx_append {xs ys : Array α} {f : (i : Nat) α (h : i < (xs ++ ys).size) β} :
(xs ++ ys).mapFinIdx f =
@@ -199,7 +201,7 @@ theorem mapFinIdx_append {xs ys : Array α} {f : (i : Nat) → α → (h : i < (
ys.mapFinIdx (fun i a h => f (i + xs.size) a (by simp; omega)) := by
cases xs
cases ys
simp [List.mapFinIdx_append]
simp [List.mapFinIdx_append, Array.size]
@[simp]
theorem mapFinIdx_push {xs : Array α} {a : α} {f : (i : Nat) α (h : i < (xs.push a).size) β} :
@@ -263,12 +265,12 @@ theorem mapFinIdx_eq_append_iff {xs : Array α} {f : (i : Nat) → α → (h : i
toArray_eq_append_iff]
constructor
· rintro l₁, l₂, rfl, rfl, rfl
refine l₁.toArray, l₂.toArray, by simp_all
refine l₁.toArray, l₂.toArray, by simp_all [Array.size]
· rintro l₁, l₂, rfl, h₁, h₂
simp [ toList_inj] at h₁ h₂
obtain rfl := h₁
obtain rfl := h₂
refine l₁, l₂, by simp_all
refine l₁, l₂, by simp_all [Array.size]
theorem mapFinIdx_eq_push_iff {xs : Array α} {b : β} {f : (i : Nat) α (h : i < xs.size) β} :
xs.mapFinIdx f = ys.push b
@@ -306,7 +308,7 @@ abbrev mapFinIdx_eq_mkArray_iff := @mapFinIdx_eq_replicate_iff
@[simp] theorem mapFinIdx_reverse {xs : Array α} {f : (i : Nat) α (h : i < xs.reverse.size) β} :
xs.reverse.mapFinIdx f = (xs.mapFinIdx (fun i a h => f (xs.size - 1 - i) a (by simp; omega))).reverse := by
rcases xs with l
simp [List.mapFinIdx_reverse]
simp [List.mapFinIdx_reverse, Array.size]
/-! ### mapIdx -/
@@ -412,7 +414,7 @@ theorem mapIdx_eq_mapIdx_iff {xs : Array α} :
rcases xs with xs
simp [List.mapIdx_eq_mapIdx_iff]
@[simp] theorem mapIdx_set {xs : Array α} {i : Nat} {h : i < xs.size} {a : α} :
@[simp] theorem mapIdx_set {f : Nat α β} {xs : Array α} {i : Nat} {h : i < xs.size} {a : α} :
(xs.set i a).mapIdx f = (xs.mapIdx f).set i (f i a) (by simpa) := by
rcases xs with xs
simp [List.mapIdx_set]
@@ -485,7 +487,7 @@ namespace List
| x :: xs => simp only [mapFinIdxM.go, mapIdxM.go, go]
unfold Array.mapIdxM
rw [mapFinIdxM_toArray]
simp only [mapFinIdxM, mapIdxM]
simp only [mapFinIdxM, mapIdxM, Array.size]
rw [go]
end List

View File

@@ -6,6 +6,8 @@ Authors: Kim Morrison
module
prelude
import all Init.Data.List.Control
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.Array.Attach
import Init.Data.List.Monadic
@@ -23,15 +25,29 @@ open Nat
/-! ## Monadic operations -/
theorem map_toList_inj [Monad m] [LawfulMonad m]
{xs : m (Array α)} {ys : m (Array α)} :
toList <$> xs = toList <$> ys xs = ys := by
simp
/-! ### mapM -/
@[simp] theorem mapM_pure [Monad m] [LawfulMonad m] {xs : Array α} {f : α β} :
xs.mapM (m := m) (pure <| f ·) = pure (xs.map f) := by
induction xs; simp_all
@[simp] theorem mapM_id {xs : Array α} {f : α Id β} : xs.mapM f = xs.map f :=
@[simp] theorem idRun_mapM {xs : Array α} {f : α Id β} : (xs.mapM f).run = xs.map (f · |>.run) :=
mapM_pure
@[deprecated idRun_mapM (since := "2025-05-21")]
theorem mapM_id {xs : Array α} {f : α Id β} : xs.mapM f = xs.map f :=
mapM_pure
@[simp] theorem mapM_map [Monad m] [LawfulMonad m] {f : α β} {g : β m γ} {xs : Array α} :
(xs.map f).mapM g = xs.mapM (g f) := by
rcases xs with xs
simp
@[simp] theorem mapM_append [Monad m] [LawfulMonad m] {f : α m β} {xs ys : Array α} :
(xs ++ ys).mapM f = (return ( xs.mapM f) ++ ( ys.mapM f)) := by
rcases xs with xs
@@ -179,12 +195,18 @@ theorem forIn'_eq_foldlM [Monad m] [LawfulMonad m]
rcases xs with xs
simp [List.forIn'_pure_yield_eq_foldl, List.foldl_map]
@[simp] theorem forIn'_yield_eq_foldl
theorem idRun_forIn'_yield_eq_foldl
{xs : Array α} (f : (a : α) a xs β Id β) (init : β) :
(forIn' xs init (fun a m b => .yield <$> f a m b)).run =
xs.attach.foldl (fun b a, h => f a h b |>.run) init := by
simp
@[deprecated idRun_forIn'_yield_eq_foldl (since := "2025-05-21")]
theorem forIn'_yield_eq_foldl
{xs : Array α} (f : (a : α) a xs β β) (init : β) :
forIn' (m := Id) xs init (fun a m b => .yield (f a m b)) =
xs.attach.foldl (fun b a, h => f a h b) init := by
rcases xs with xs
simp [List.foldl_map]
xs.attach.foldl (fun b a, h => f a h b) init :=
forIn'_pure_yield_eq_foldl _ _
@[simp] theorem forIn'_map [Monad m] [LawfulMonad m]
{xs : Array α} (g : α β) (f : (b : β) b xs.map g γ m (ForInStep γ)) :
@@ -221,12 +243,18 @@ theorem forIn_eq_foldlM [Monad m] [LawfulMonad m]
rcases xs with xs
simp [List.forIn_pure_yield_eq_foldl, List.foldl_map]
@[simp] theorem forIn_yield_eq_foldl
theorem idRun_forIn_yield_eq_foldl
{xs : Array α} (f : α β Id β) (init : β) :
(forIn xs init (fun a b => .yield <$> f a b)).run =
xs.foldl (fun b a => f a b |>.run) init := by
simp
@[deprecated idRun_forIn_yield_eq_foldl (since := "2025-05-21")]
theorem forIn_yield_eq_foldl
{xs : Array α} (f : α β β) (init : β) :
forIn (m := Id) xs init (fun a b => .yield (f a b)) =
xs.foldl (fun b a => f a b) init := by
rcases xs with xs
simp [List.foldl_map]
xs.foldl (fun b a => f a b) init :=
forIn_pure_yield_eq_foldl _ _
@[simp] theorem forIn_map [Monad m] [LawfulMonad m]
{xs : Array α} {g : α β} {f : β γ m (ForInStep γ)} :
@@ -282,7 +310,7 @@ namespace List
@[simp] theorem filterM_toArray' [Monad m] [LawfulMonad m] {l : List α} {p : α m Bool} (w : stop = l.length) :
l.toArray.filterM p 0 stop = toArray <$> l.filterM p := by
subst w
rw [filterM_toArray]
simp [ filterM_toArray]
@[grind =] theorem filterRevM_toArray [Monad m] [LawfulMonad m] {l : List α} {p : α m Bool} :
l.toArray.filterRevM p = toArray <$> l.filterRevM p := by
@@ -294,7 +322,7 @@ namespace List
@[simp] theorem filterRevM_toArray' [Monad m] [LawfulMonad m] {l : List α} {p : α m Bool} (w : start = l.length) :
l.toArray.filterRevM p start 0 = toArray <$> l.filterRevM p := by
subst w
rw [filterRevM_toArray]
simp [ filterRevM_toArray]
@[grind =] theorem filterMapM_toArray [Monad m] [LawfulMonad m] {l : List α} {f : α m (Option β)} :
l.toArray.filterMapM f = toArray <$> l.filterMapM f := by
@@ -312,7 +340,7 @@ namespace List
@[simp] theorem filterMapM_toArray' [Monad m] [LawfulMonad m] {l : List α} {f : α m (Option β)} (w : stop = l.length) :
l.toArray.filterMapM f 0 stop = toArray <$> l.filterMapM f := by
subst w
rw [filterMapM_toArray]
simp [ filterMapM_toArray]
@[simp, grind =] theorem flatMapM_toArray [Monad m] [LawfulMonad m] {l : List α} {f : α m (Array β)} :
l.toArray.flatMapM f = toArray <$> l.flatMapM (fun a => Array.toList <$> f a) := by

View File

@@ -6,8 +6,11 @@ Authors: Kim Morrison
module
prelude
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
import Init.Data.Array.Monadic
import Init.Data.List.OfFn
import Init.Data.List.FinRange
/-!
# Theorems about `Array.ofFn`
@@ -18,6 +21,8 @@ set_option linter.indexVariables true -- Enforce naming conventions for index va
namespace Array
/-! ### ofFn -/
@[simp] theorem ofFn_zero {f : Fin 0 α} : ofFn f = #[] := by
simp [ofFn, ofFn.go]
@@ -25,12 +30,17 @@ theorem ofFn_succ {f : Fin (n+1) → α} :
ofFn f = (ofFn (fun (i : Fin n) => f i.castSucc)).push (f n, by omega) := by
ext i h₁ h₂
· simp
· simp [getElem_push]
split <;> rename_i h₃
· rfl
· congr
simp at h₁ h₂
omega
· simp only [getElem_ofFn, getElem_push, size_ofFn, Fin.castSucc_mk, left_eq_dite_iff,
Nat.not_lt]
simp only [size_ofFn] at h₁
intro h₃
simp only [show i = n by omega]
theorem ofFn_add {n m} {f : Fin (n + m) α} :
ofFn f = (ofFn (fun i => f (i.castLE (Nat.le_add_right n m)))) ++ (ofFn (fun i => f (i.natAdd n))) := by
induction m with
| zero => simp
| succ m ih => simp [ofFn_succ, ih]
@[simp] theorem _root_.List.toArray_ofFn {f : Fin n α} : (List.ofFn f).toArray = Array.ofFn f := by
ext <;> simp
@@ -38,6 +48,11 @@ theorem ofFn_succ {f : Fin (n+1) → α} :
@[simp] theorem toList_ofFn {f : Fin n α} : (Array.ofFn f).toList = List.ofFn f := by
apply List.ext_getElem <;> simp
theorem ofFn_succ' {f : Fin (n+1) α} :
ofFn f = #[f 0] ++ ofFn (fun i => f i.succ) := by
apply Array.toList_inj.mp
simp [List.ofFn_succ]
@[simp]
theorem ofFn_eq_empty_iff {f : Fin n α} : ofFn f = #[] n = 0 := by
rw [ Array.toList_inj]
@@ -52,4 +67,70 @@ theorem mem_ofFn {n} {f : Fin n → α} {a : α} : a ∈ ofFn f ↔ ∃ i, f i =
· rintro i, rfl
apply mem_of_getElem (i := i) <;> simp
/-! ### ofFnM -/
/-- Construct (in a monadic context) an array by applying a monadic function to each index. -/
def ofFnM {n} [Monad m] (f : Fin n m α) : m (Array α) :=
Fin.foldlM n (fun xs i => xs.push <$> f i) (Array.emptyWithCapacity n)
@[simp]
theorem ofFnM_zero [Monad m] {f : Fin 0 m α} : ofFnM f = pure #[] := by
simp [ofFnM]
theorem ofFnM_succ' {n} [Monad m] [LawfulMonad m] {f : Fin (n + 1) m α} :
ofFnM f = (do
let a f 0
let as ofFnM fun i => f i.succ
pure (#[a] ++ as)) := by
simp [ofFnM, Fin.foldlM_eq_foldlM_finRange, List.foldlM_push_eq_append, List.finRange_succ, Function.comp_def]
theorem ofFnM_succ {n} [Monad m] [LawfulMonad m] {f : Fin (n + 1) m α} :
ofFnM f = (do
let as ofFnM fun i => f i.castSucc
let a f (Fin.last n)
pure (as.push a)) := by
simp [ofFnM, Fin.foldlM_succ_last]
theorem ofFnM_add {n m} [Monad m] [LawfulMonad m] {f : Fin (n + k) m α} :
ofFnM f = (do
let as ofFnM fun i : Fin n => f (i.castLE (Nat.le_add_right n k))
let bs ofFnM fun i : Fin k => f (i.natAdd n)
pure (as ++ bs)) := by
induction k with
| zero => simp
| succ k ih =>
simp only [ofFnM_succ, Nat.add_eq, ih, Fin.castSucc_castLE, Fin.castSucc_natAdd, bind_pure_comp,
bind_assoc, bind_map_left, Fin.natAdd_last, map_bind, Functor.map_map]
congr 1
funext xs
congr 1
funext ys
congr 1
funext x
simp
@[simp] theorem toList_ofFnM [Monad m] [LawfulMonad m] {f : Fin n m α} :
toList <$> ofFnM f = List.ofFnM f := by
induction n with
| zero => simp
| succ n ih => simp [ofFnM_succ, List.ofFnM_succ_last, ih]
@[simp]
theorem ofFnM_pure_comp [Monad m] [LawfulMonad m] {n} {f : Fin n α} :
ofFnM (pure f) = (pure (ofFn f) : m (Array α)) := by
apply Array.map_toList_inj.mp
simp
-- Variant of `ofFnM_pure_comp` using a lambda.
-- This is not marked a `@[simp]` as it would match on every occurrence of `ofFnM`.
theorem ofFnM_pure [Monad m] [LawfulMonad m] {n} {f : Fin n α} :
ofFnM (fun i => pure (f i)) = (pure (ofFn f) : m (Array α)) :=
ofFnM_pure_comp
@[simp, grind =] theorem idRun_ofFnM {f : Fin n Id α} :
Id.run (ofFnM f) = ofFn (fun i => Id.run (f i)) := by
induction n with
| zero => simp
| succ n ih => simp [ofFnM_succ', ofFn_succ', ih]
end Array

View File

@@ -7,6 +7,7 @@ module
prelude
import Init.Data.List.Nat.Perm
import all Init.Data.Array.Basic
import Init.Data.Array.Lemmas
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.

View File

@@ -27,23 +27,27 @@ Internal implementation of `Array.qsort`.
It does so by first swapping the elements at indices `lo`, `mid := (lo + hi) / 2`, and `hi`
if necessary so that the middle (pivot) element is at index `hi`.
We then iterate from `j = lo` to `j = hi`, with a pointer `i` starting at `lo`, and
We then iterate from `k = lo` to `k = hi`, with a pointer `i` starting at `lo`, and
swapping each element which is less than the pivot to position `i`, and then incrementing `i`.
-/
def qpartition {n} (as : Vector α n) (lt : α α Bool) (lo hi : Nat)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) : {m : Nat // lo m m < n} × Vector α n :=
def qpartition {n} (as : Vector α n) (lt : α α Bool) (lo hi : Nat) (w : lo hi := by omega)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) : {m : Nat // lo m m hi} × Vector α n :=
let mid := (lo + hi) / 2
let as := if lt as[mid] as[lo] then as.swap lo mid else as
let as := if lt as[hi] as[lo] then as.swap lo hi else as
let as := if lt as[mid] as[hi] then as.swap mid hi else as
let pivot := as[hi]
let rec loop (as : Vector α n) (i j : Nat)
(ilo : lo i := by omega) (jh : j < n := by omega) (w : i j := by omega) :=
if h : j < hi then
if lt as[j] pivot then
loop (as.swap i j) (i+1) (j+1)
-- During this loop, elements below in `[lo, i)` are less than `pivot`,
-- elements in `[i, k)` are greater than or equal to `pivot`,
-- elements in `[k, hi)` are unexamined,
-- while `as[hi]` is (by definition) the pivot.
let rec loop (as : Vector α n) (i k : Nat)
(ilo : lo i := by omega) (ik : i k := by omega) (w : k hi := by omega) :=
if h : k < hi then
if lt as[k] pivot then
loop (as.swap i k) (i+1) (k+1)
else
loop as i (j+1)
loop as i (k+1)
else
(i, ilo, by omega, as.swap i hi)
loop as lo lo
@@ -51,25 +55,28 @@ def qpartition {n} (as : Vector α n) (lt : αα → Bool) (lo hi : Nat)
/--
In-place quicksort.
`qsort as lt low high` sorts the subarray `as[low:high+1]` in-place using `lt` to compare elements.
`qsort as lt lo hi` sorts the subarray `as[lo:hi+1]` in-place using `lt` to compare elements.
-/
@[inline] def qsort (as : Array α) (lt : α α Bool := by exact (· < ·))
(low := 0) (high := as.size - 1) : Array α :=
let rec @[specialize] sort {n} (as : Vector α n) (lo hi : Nat)
(lo := 0) (hi := as.size - 1) : Array α :=
let rec @[specialize] sort {n} (as : Vector α n) (lo hi : Nat) (w : lo hi := by omega)
(hlo : lo < n := by omega) (hhi : hi < n := by omega) :=
if h₁ : lo < hi then
let mid, hmid, as := qpartition as lt lo hi
if h₂ : mid hi then
-- This only occurs when `hi ≤ lo`,
-- and thus `as[lo:hi+1]` is trivially already sorted.
as
else
-- Otherwise, we recursively sort the two subarrays.
sort (sort as lo mid) (mid+1) hi
else as
if h : as.size = 0 then
as
else
let low := min low (as.size - 1)
let high := min high (as.size - 1)
sort as.toVector low high |>.toArray
let lo := min lo (as.size - 1)
let hi := max lo (min hi (as.size - 1))
sort as.toVector lo hi |>.toArray
set_option linter.unusedVariables.funArgs false in
/--

View File

@@ -7,7 +7,8 @@ module
prelude
import Init.Data.Array.Lemmas
import Init.Data.Array.OfFn
import all Init.Data.Array.Basic
import all Init.Data.Array.OfFn
import Init.Data.Array.MapIdx
import Init.Data.Array.Zip
import Init.Data.List.Nat.Range
@@ -28,6 +29,7 @@ open Nat
/-! ### range' -/
@[grind _=_]
theorem range'_succ {s n step} : range' s (n + 1) step = #[s] ++ range' (s + step) n step := by
rw [ toList_inj]
simp [List.range'_succ]
@@ -38,16 +40,17 @@ theorem range'_succ {s n step} : range' s (n + 1) step = #[s] ++ range' (s + ste
theorem range'_ne_empty_iff : range' s n step #[] n 0 := by
cases n <;> simp
@[simp] theorem range'_zero : range' s 0 step = #[] := by
@[simp, grind =] theorem range'_zero : range' s 0 step = #[] := by
simp
@[simp] theorem range'_one {s step : Nat} : range' s 1 step = #[s] := by
@[simp, grind =] theorem range'_one {s step : Nat} : range' s 1 step = #[s] := by
simp [range', ofFn, ofFn.go]
@[simp] theorem range'_inj : range' s n = range' s' n' n = n' (n = 0 s = s') := by
rw [ toList_inj]
simp [List.range'_inj]
@[grind =]
theorem mem_range' {n} : m range' s n step i < n, m = s + step * i := by
simp [range']
constructor
@@ -56,6 +59,7 @@ theorem mem_range' {n} : m ∈ range' s n step ↔ ∃ i < n, m = s + step * i :
· rintro i, w, h'
exact i, w, by simp_all
@[simp, grind =]
theorem pop_range' : (range' s n step).pop = range' s (n - 1) step := by
ext <;> simp
@@ -65,6 +69,7 @@ theorem map_add_range' {a} (s n step) : map (a + ·) (range' s n step) = range'
theorem range'_succ_left : range' (s + 1) n step = (range' s n step).map (· + 1) := by
ext <;> simp <;> omega
@[grind _=_]
theorem range'_append {s m n step : Nat} :
range' s m step ++ range' (s + step * m) n step = range' s (m + n) step := by
ext i h₁ h₂
@@ -76,16 +81,17 @@ theorem range'_append {s m n step : Nat} :
have : step * m step * i := by exact mul_le_mul_left step h
omega
@[simp] theorem range'_append_1 {s m n : Nat} :
@[simp, grind _=_]
theorem range'_append_1 {s m n : Nat} :
range' s m ++ range' (s + m) n = range' s (m + n) := by simpa using range'_append (step := 1)
theorem range'_concat {s n : Nat} : range' s (n + 1) step = range' s n step ++ #[s + step * n] := by
simpa using range'_append.symm
simp [ range'_append]
theorem range'_1_concat {s n : Nat} : range' s (n + 1) = range' s n ++ #[s + n] := by
simp [range'_concat]
@[simp] theorem mem_range'_1 : m range' s n s m m < s + n := by
@[simp, grind =] theorem mem_range'_1 : m range' s n s m m < s + n := by
simp [mem_range']; exact
fun i, h, e => e Nat.le_add_right .., Nat.add_lt_add_left h _,
fun h₁, h₂ => m - s, Nat.sub_lt_left_of_lt_add h₁ h₂, (Nat.add_sub_cancel' h₁).symm
@@ -115,6 +121,7 @@ theorem range'_eq_append_iff : range' s n = xs ++ ys ↔ ∃ k, k ≤ n ∧ xs =
simp only [List.find?_toArray]
simp
@[grind =]
theorem erase_range' :
(range' s n).erase i =
range' s (min n (i - s)) ++ range' (max s (i + 1)) (min s (i + 1) + n - (i + 1)) := by
@@ -123,6 +130,7 @@ theorem erase_range' :
/-! ### range -/
@[grind _=_]
theorem range_eq_range' {n : Nat} : range n = range' 0 n := by
simp [range, range']
@@ -144,6 +152,7 @@ theorem range'_eq_map_range {s n : Nat} : range' s n = map (s + ·) (range n) :=
theorem range_ne_empty_iff {n : Nat} : range n #[] n 0 := by
cases n <;> simp
@[grind _=_]
theorem range_succ {n : Nat} : range (succ n) = range n ++ #[n] := by
ext i h₁ h₂
· simp
@@ -159,7 +168,7 @@ theorem range_add {n m : Nat} : range (n + m) = range n ++ (range m).map (n + ·
theorem reverse_range' {s n : Nat} : reverse (range' s n) = map (s + n - 1 - ·) (range n) := by
simp [ toList_inj, List.reverse_range']
@[simp]
@[simp, grind =]
theorem mem_range {m n : Nat} : m range n m < n := by
simp only [range_eq_range', mem_range'_1, Nat.zero_le, true_and, Nat.zero_add]
@@ -167,7 +176,7 @@ theorem not_mem_range_self {n : Nat} : n ∉ range n := by simp
theorem self_mem_range_succ {n : Nat} : n range (n + 1) := by simp
@[simp] theorem take_range {i n : Nat} : take (range n) i = range (min i n) := by
@[simp, grind =] theorem take_range {i n : Nat} : take (range n) i = range (min i n) := by
ext <;> simp
@[simp] theorem find?_range_eq_some {n : Nat} {i : Nat} {p : Nat Bool} :
@@ -178,6 +187,7 @@ theorem self_mem_range_succ {n : Nat} : n ∈ range (n + 1) := by simp
(range n).find? p = none i, i < n !p i := by
simp only [ List.toArray_range, List.find?_toArray, List.find?_range_eq_none]
@[grind =]
theorem erase_range : (range n).erase i = range (min n i) ++ range' (i + 1) (n - (i + 1)) := by
simp [range_eq_range', erase_range']

Some files were not shown because too many files have changed in this diff Show More