Compare commits

...

255 Commits

Author SHA1 Message Date
Kim Morrison
2ac6dec352 feat: lemmas about Option/if-then-else 2024-08-21 11:17:15 +10:00
Kim Morrison
4aa74d9c0b feat: List.mergeSort (#5092)
Defines `mergeSort`, a naive stable merge sort algorithm, replaces it
via a `@[csimp]` lemma with something faster at runtime, and proves the
following results:

* `mergeSort_sorted`: `mergeSort` produces a sorted list.
* `mergeSort_perm`: `mergeSort` is a permutation of the input list.
* `mergeSort_of_sorted`: `mergeSort` does not change a sorted list.
* `mergeSort_cons`: proves `mergeSort le (x :: xs) = l₁ ++ x :: l₂` for
some `l₁, l₂`
so that `mergeSort le xs = l₁ ++ l₂`, and no `a ∈ l₁` satisfies `le a
x`.
* `mergeSort_stable`: if `c` is a sorted sublist of `l`, then `c` is
still a sublist of `mergeSort le l`.
2024-08-20 06:32:52 +00:00
Joachim Breitner
efbecf272d feat: explain reduce steps in trace.Debug.Meta.Tactic.simp (#5054) 2024-08-19 15:05:13 +00:00
Joachim Breitner
78146190e5 feat: mutual recursion: allow common prefix up to alpha-equivalence (#5041)
@arthur-adjedj was very confused when a mutually recursive definition
didn't work as expected, and the reason was that he used different names
for the fixed parameters.

It seems plausible to simply allow that and calculate the fixed-prefix
up to alpha renaming.

It does mean, though, that, for example, termination proof goals will
mention the names as used by the first function. But probably better
than simply failing. And we could even fix that later (by passing down
the
actual names, and renmaing the variables in the context of the mvar,
depending on the “current function”) should it bother our users.
2024-08-19 15:00:03 +00:00
Joachim Breitner
b4db495f98 feat: unify equational theorems between wf and structural recursion (#5055)
by removing the `tryRefl` variation between the two.

Part of #3983
2024-08-19 14:59:15 +00:00
Henrik Böving
9f47e08ecc feat: import LeanSAT LRAT (#5074)
This PR imports LeanSAT's LRAT module as step 4/~6 (step 7 could go
after I did some refactorings to import this) of the LeanSAT
upstreaming. It is the last large component, after this only the LRAT
parser and the reflection tactic that hooks everything up to the meta
level remains. In particular it is the last component that contains
notable proofs, yay!

Again a few remarks:
1. Why is this not in `Std`? I'm not quite sure whether it should be
there. At the current level of code/proof quality we can certainly not
import the checker itself into `Std` but maybe having the data type as
well as the trimming algorithm there might be of interested? I'm hoping
that as we refactor the checker in the future its quality will be high
enough to be also put into `Std`. At this point we would have a full AIG
-> CNF -> LRAT verification pipeline in `Std` for everyone to use. One
additional blocker in this is that we cannot provide the parsers for the
format in `Std` as of today because `Parsec` is still in `Lean` so that
would also have to change.
2. There do exist two abstraction levels to make sure we can swap out
the LRAT implementation at any time:
- The public interface is just all files in the top level `LRAT`
directory. It basically only contains the LRAT format itself, the
checker + soundness proof and the trimming algorithm. As long as we
don't need to change their API (which we shouldn't have to I think) we
can always swap out the entire `Internal` directory without breaking
anything else in LeanSAT.
- The `Internal` module itself contains another layer of abstraction in
the form of the `Formula` class. This allows us to swap out the most
complex component in `Internal` as well, without having to touch any of
the infrastructure that is built around it either.
3. I mostly performed stylistic cleanups on the `Internal` module. In my
experience over upgrading to many nightlies during the course of LeanSAT
development, I have gotten these proofs cleaned up to the point, where
they only break if we change the `List` or `Array` proof API
significantly. Given that we are currently in the process of stabilizing
it I'm hoping that these proofs do not have to be touched anymore unless
we do something crazy. All of the custom theory that the LRAT component
developed around various basic data types has been upstreamed into Lean
over the course of various other PRs.
4. If there are some simple tricks that we can pull off to increase the
code / proof quality in `Internal` and in particular `Internal.Formula`
(this module is not for the light-hearted Lean reviewer) I'm all for it.
Otherwise the best course of action to provide LeanSAT to our users soon
would probably be to merge it as is and do a cut + rewrite at one of the
two interface points described above.
2024-08-19 14:31:00 +00:00
Joachim Breitner
728980443f refactor: rename new option to debug.rawDecreasingByGoal (#5066)
as suggested by @semorrison in 

https://leanprover.zulipchat.com/#narrow/stream/116290-rss/topic/cleanDecreasingBy/near/462659021


Follow-up to #5016.
2024-08-19 11:53:54 +00:00
Sebastian Ullrich
ca945be133 fix: disable incrementality in case .. | .. (#5090) 2024-08-19 09:17:03 +00:00
Eric Wieser
f2573dc51e fix: Do not overwrite existing signal handlers (#5062)
Such handlers can come from address sanitizers and similar. When
combined with #4971, this forward-ports
676b9bc477
/ rust-lang/rust#69685

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-08-19 09:11:38 +00:00
Joachim Breitner
51f01d8c8a feat: expose index option to dsimp tactic (#5071)
makes the option introduced in #4202 also available when using `dsimp`
2024-08-19 07:57:16 +00:00
Matthew Toohey
b486c6748b fix: correct typo in invalid reassignment error (#5080)
Corrects a small typo in the error message for when a user attempts to
mutate something which cannot be mutated.
2024-08-18 08:10:07 +00:00
Kim Morrison
38288ae07a feat: upstream List.Perm (#5069) 2024-08-17 04:11:35 +00:00
Kim Morrison
b939fef2cf chore: fix implicitness in refl/rfl lemma binders (#5077) 2024-08-16 22:31:06 +00:00
Arthur Adjedj
eb15c08ea0 fix: instantiate mvars of indices before instantiating fvars (#4717)
When elaborating the headers of mutual indexed inductive types, mvars
have to be synthesized and instantiated before replacing the fvars
present there. Otherwise, some fvars present in uninstantiated mvars may
be missed and lead to an error later.
Closes #3242 (again)
2024-08-16 15:19:48 +00:00
Joachim Breitner
72f2e7aab1 feat: make structure type clickable in “not a field” error (#5072) 2024-08-16 09:06:18 +00:00
Joachim Breitner
cd21687884 feat: simp debug trace tag to use “dpre” in rlfOnly mode (#5073)
to distinguish from `pre`.
2024-08-16 08:56:38 +00:00
Joachim Breitner
a08ef5ffa2 fix: remove partially copied code comment (#5070) 2024-08-16 08:42:30 +00:00
Joachim Breitner
53e6e99a29 refactor: generalize addMatcherInfo (#5068)
works in any `MonadEnv`.
2024-08-16 06:24:32 +00:00
Kim Morrison
59ca274296 chore: minimize some imports (#5067) 2024-08-16 06:18:11 +00:00
Henrik Böving
ac4927de46 feat: List.foldlRecOn (#5039)
As discussed with @semorrison, feel free to do whatever to the branch.

---------

Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
Co-authored-by: Kim Morrison <kim@tqft.net>
2024-08-15 23:26:06 +00:00
Sebastian Ullrich
0ecbcfdcc3 chore: remove stray markPersistent (#5056)
It is conditionally applied a few lines below
2024-08-15 15:41:42 +00:00
Sebastian Ullrich
4d4d485c19 chore: avoid rebuilding leanmanifest in each build (#5057) 2024-08-15 14:55:36 +00:00
Joachim Breitner
d1174e10e6 feat: always run clean_wf, even before decreasing_by (#5016)
Previously, the tactic state shown at `decreasing_by` would leak lots of
details about the translation, and mention `invImage`, `PSigma` etc.
This is not nice.
  
So this introduces `clean_wf`, which is like `simp_wf` but using
`simp`'s `only` mode, and runs this unconditionally. This should clean
up the goal to a reasonable extent.
  
Previously `simp_wf` was an unrestricted `simp […]` call, but we
probably don’t want arbitrary simplification to happen at this point, so
this now became `simp only` call. For backwards compatibility,
`decreasing_with` begins with `try simp`. The `simp_wf` tactic
is still available to not break too much existing code; it’s docstring
suggests to no longer use it.

With `set_option cleanDecreasingByGoal false` one can disable the use of
`clean_wf`. I hope this is only needed for debugging and understanding.
  
Migration advise: If your `decreasing_by` proof begins with `simp_wf`,
either remove that (if the proof still goes through), or replace with
`simp`.
  
I am a bit anxious about running even `simp only` unconditionally here,
as it may do more than some user might want, e.g. because of options
like `zetaDelta := true`. We'll see if we need to reign in this tactic
some more.

I wonder if in corner cases the `simp_wf` tactic might be able to close
the goal, and if that is a problem. If so, we may have to promote simp’s
internal `mayCloseGoal` parameter to a simp configuration option and use
that here.
  
fixes #4928
2024-08-15 14:42:15 +00:00
Sebastian Ullrich
a43356591c chore: CI: fix 32bit stage 0 builds (#5052)
Let's link stage 0 against libuv in both cases, even if for Emscripten
we won't for stage 1
2024-08-15 12:35:25 +00:00
Sebastian Ullrich
082ed944d8 fix: Windows stage 0 2024-08-15 14:50:56 +02:00
Kim Morrison
36d71f8253 feat: more List.find?/findSome?/findIdx? theorems (#5053) 2024-08-15 11:53:35 +00:00
Lean stage0 autoupdater
3c07e48a33 chore: update stage0 2024-08-15 12:12:52 +00:00
Markus Himmel
8a6eec0047 doc: add release note drafts for LibUV and hash map deprecation (#5049) 2024-08-15 09:10:02 +00:00
Kim Morrison
213a7221f6 feat: more List.Sublist theorems (#5048) 2024-08-15 05:38:25 +00:00
Sebastian Ullrich
42fcfcbad6 fix: macOS: install name of libleanshared_1 (#5034) 2024-08-15 05:22:34 +00:00
Kim Morrison
2ba7c995a6 chore: upstream List.Pairwise lemmas (#5047) 2024-08-15 02:59:05 +00:00
Kim Morrison
7e72f9ab85 chore: add libuv to the required packages heading in macos docs (#5045) 2024-08-15 01:33:58 +00:00
Kim Morrison
326dbd1e15 chore: upstream #time command (#5044) 2024-08-15 00:17:48 +00:00
Markus Himmel
6bc98af67b chore: reduce usage of refine' (#5042) 2024-08-14 15:14:44 +00:00
Sebastian Ullrich
f883fc0db6 chore: clean up cmdline snapshots logic (#5043) 2024-08-14 15:10:37 +00:00
Sebastian Ullrich
20a7fe89b5 perf: mark entire reported info tree as persistent (#5040)
As we can definitely not free it until .ilean generation at the very end
2024-08-14 13:39:35 +00:00
Joachim Breitner
ac64cfd70a fix: array_get_dec etc. tactics to solve more cases (#5037)
Using `Nat.lt_trans` is too restrictive, and using `Nat.lt_of_lt_of_le`
should make this tactic prove more goals.

This fixes a regression probably introduced by #3991; at least in some
cases before that `apply sizeOf_get` would have solved the goal here.
And it’s true that this is now subsumed by `simp`, but because of the
order that `macro_rules` are tried, the too restrictive variant with
`Nat.lt_trans` would be tried before `simp`, without backtracking.

Fixes #5027
2024-08-14 12:41:14 +00:00
Henrik Böving
958ad2b54b feat: upstream LeanSAT's bitblaster (#5013)
Step 3/~7 in upstreaming LeanSAT.

A few thoughts:
- Why is this not in `Std.Sat`? LeanSAT's bitblaster operates on a
limited internal language. For example it has no idea that signed
comparision operators even exist. This is because it relies on a
normalization pass before being given the goal. For this reason I would
not classify the bitblaster as an API that we should publicly advertise
at this abstraction level
- Sometimes I slightly rebuild parts of the LawfulOperator
infrastructure for operators that work non-tail-recursively. This is
because they do not return an `Entrypoint` but instead an
`ExtendingEntrypoint` in order to even be defined in the first place
(casting Ref's and all that). Given the fact that this barely happens
and I never actually commit to rebuilding the full API I'm hoping that
this is indeed a fine decision?
- The single explicit `decreasing_by` that has a simp only which
*almost* looks like `simp_wf` is missing a singular lemma from `simp_wf`
because it doesn't terminate otherwise.
- I am not using functional induction because it basically always fails
at some generalization step, that is also the reason that there is lots
of explicit `generalize` and manually recursive proofs.

---------

Co-authored-by: Markus Himmel <markus@lean-fro.org>
Co-authored-by: Tobias Grosser <tobias@grosser.es>
2024-08-14 09:54:10 +00:00
Lean stage0 autoupdater
bd5f8ef242 chore: update stage0 2024-08-14 09:45:24 +00:00
Sebastian Ullrich
337db03717 fix: report info trees on cmdline for .ileans (#5018)
In #4976, I forgot that we do need info trees eventually on the cmdline
for .ilean generation. Unfortunately, not reporting them incrementally
would require an API change, so let's see what the impact of incremental
reporting is
2024-08-14 08:59:29 +00:00
Markus Himmel
3efd0e4e1f chore: fix inconsistent style in internal hash map lemmas (#5033) 2024-08-14 07:49:11 +00:00
Kim Morrison
8c96d213f3 chore: use local instance in Lsp.Diagnostics (#5031)
I'm experimenting with changing the signature of `Ord.arrayOrd`; rather
than make a local synonym here, let's make a local instance so it
doesn't interact with the experiments.
2024-08-14 05:04:32 +00:00
Kim Morrison
154385fdb9 chore: remove dead code in Lake.Util.Compare (#5030)
While exploring refactors of `List.lt` I ran into errors here, in code
that is entirely unused. Propose cleaning up to get things out of my
way!
2024-08-14 04:59:20 +00:00
Kim Morrison
9e39dc8100 feat: new+old lemmas about List.Sublist (#5029)
Some upstreamed from mathlib, some new.
2024-08-14 04:13:57 +00:00
Markus Himmel
dcadfd1c89 chore: remove oldSectionVars from hash map lemmas (#5023) 2024-08-14 03:04:33 +00:00
Tobias Grosser
7c5d8661f4 feat: add BitVec.toNat_[udiv|umod] and [udiv|umod]_eq (#4997)
Co-authored-by: Siddharth <siddu.druid@gmail.com>
Co-authored-by: Atticus Kuhn <52258164+AtticusKuhn@users.noreply.github.com>
2024-08-14 03:03:31 +00:00
Alex Keizer
bff30fe98e feat: express BitVec.extractLsb' in terms of extractLsb (#5007)
Adds a lemma to rewrite `BitVec.extractLsb'` to `extractLsb` plus a
cast.
Note that `extractLsb'` with a length of 0 returns `BitVec 0`, while
`extractLsb` will never return an empty bitvector (because of the `+ 1`
in it `hi - lo + 1`). Hence, this lemma needs a side condition that the
length is non-zero.

Also adds `getLsb_extractLsb'`

---------

Co-authored-by: Tobias Grosser <github@grosser.es>
2024-08-14 03:01:58 +00:00
Jeremy Tan Jie Rui
ac2dabdedf chore: use in Fin.ne_of_val_ne (#5011)
Instead of a `Not (Eq …)` term use the proper `≠` in `Fin.ne_of_val_ne`,
to make it symmetric with `Fin.val_ne_of_ne`, and move the former to the
same place as the latter.

This answers a query of @eric-wieser at

https://github.com/leanprover-community/mathlib4/pull/15762#discussion_r1714990412
2024-08-14 01:34:47 +00:00
Joachim Breitner
7283e2c14e chore: pr-release: pass --retry to curl (#5025)
Since https://github.com/curl/curl/pull/4465 curl adheres to the
`Retry-After` header, so maybe this fixes the issues with
```
jq: error (at <stdin>:5): Cannot index string with string "body"
```
that sometimes make this workflow fail.
2024-08-13 16:19:43 +00:00
Joachim Breitner
f500af99e8 chore: ci.yaml: build MacOS Aarch64 release for PRs by default (#5022)
should make https://github.com/leanprover-community/mathlib4/pull/13301
unnecessary, which has a fair number of bad side-effects
2024-08-13 15:34:44 +00:00
Joachim Breitner
861ef27503 refactor: state WellFoundedRelation Nat using <, not Nat.lt (#5012)
as that’s the simp normal form.
2024-08-13 13:37:42 +00:00
Joachim Breitner
11be29e68c chore: pr-release: adjust lakefile editing sed to new git syntax (#5014) 2024-08-13 12:03:51 +00:00
Henrik Böving
74f9dea701 feat: use save-always in cache action (#5010)
Follows up on
https://github.com/leanprover/lean4/pull/5003#issuecomment-2284813940
2024-08-13 09:27:15 +00:00
Sebastian Ullrich
041b80a4f5 chore: speedcenter: reduce number of runs for "fast" benchmarks from 10 to 3 (#5009) 2024-08-13 09:06:06 +00:00
Sebastian Ullrich
5bc6496a7c chore: more libuv search patterns for the speedcenter (#5008) 2024-08-13 10:40:09 +02:00
Sebastian Ullrich
f3e7b455bb perf: avoid MT marking environment in language processor (#5004)
#4976 moved resolution of a promise to an earlier point, but that led to
object being marked MT earlier, so we need to move the code that
minimizes those objects earlier too to revert the performance
regression.
2024-08-12 19:15:38 +00:00
Kyle Miller
7cd406f335 fix: check is valid structure projection when pretty printing (#4982)
For structure projections, the pretty printer assumed that the
expression was type correct. Now it checks that the object being
projected is of the correct type. Such terms appear in type mismatch
errors.

Also, fixes and improves `#print` for structures. The types of
projections now use MessageData (so are now hoverable), and the type of
`self` is now the correct type.

Closes #4670
2024-08-12 15:52:17 +00:00
Henrik Böving
ecb35795eb chore: upgrade cache action to silence warnings (#5003)
According to the release notes of cache this should not break anything
as they merely upgraded the node version.
2024-08-12 15:46:53 +00:00
Henrik Böving
dc3eccdf26 feat: Std.Sat.AIG (#4953)
Step 2/~7 in upstreaming LeanSAT.

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
Co-authored-by: Siddharth <siddu.druid@gmail.com>
Co-authored-by: Markus Himmel <markus@lean-fro.org>
Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
2024-08-12 14:58:38 +00:00
Markus Himmel
c237c1f9fb feat: link LibUV (#4963) 2024-08-12 12:33:24 +00:00
Sebastian Ullrich
dd4e26f247 feat: output panics into Lean's redirected stderr (#4952)
...unless we are about to kill the process anyway (which is not the
default)

Ensures panics are visible as regular messages in the language server
and properly ordered in relation to other messages on the cmdline
2024-08-12 12:15:15 +00:00
Sebastian Ullrich
c5114c971a fix: Windows needs more LEAN_EXPORTs 2024-08-12 14:14:42 +02:00
Sebastian Ullrich
adc799584c fix: split libleanshared on Windows to avoid symbol limit 2024-08-12 14:14:42 +02:00
Sebastian Ullrich
bbb448cdf6 chore: update stage0 2024-08-12 14:14:42 +02:00
Sebastian Ullrich
9d0302e749 chore: remove LEAN_EXPORT denylist workaround 2024-08-12 14:14:42 +02:00
Sebastian Ullrich
8d12dd87a4 fix: disable incremental body elaboration in presence of where (#5001) 2024-08-12 10:42:19 +00:00
Henrik Böving
da9d44df2d feat: handle \r on all operating systems in IO.FS.lines (#4973)
Closes: #4573
2024-08-12 09:51:50 +00:00
David Thrane Christiansen
5c182bd540 fix: reduce default max depth for ext tactic (#4996)
The prior default of 1000000 could not be achieved in practice, because
the stack would overflow after around 5000 recursive invocations. This
meant that a poorly-chosen @[ext] lemma could crash Lean.

Talking to Mathlib users, it seems that 10 would be a very large number
in practice, so a default limit of 100 should not change successful
uses. But it does make it much easier to diagnose and recover from poor
choices of @[ext] lemmas.
2024-08-12 07:26:32 +00:00
David Thrane Christiansen
ecd3aa4b5d fix: handle SIGBUS when looking for stack overflows (#4971)
Without this change, a stack overflow on Mac OS during tactic execution
can lead to the message:

    terminated by signal SIGBUS (Misaligned address error)

This comes from `lean_alloc_small`. With the change, the process instead
terminates with the more accurate and actionable:

    Stack overflow detected. Aborting.
2024-08-12 07:08:22 +00:00
Kim Morrison
12ca422d86 chore: upstream List.findIdx lemmas (#4995) 2024-08-12 04:11:00 +00:00
Kim Morrison
0a7af630a5 chore: when a linter crashes, prefix its name (#4967)
Helpful for diagnosing which linter is failing, c.f. [recent
problem](https://leanprover.zulipchat.com/#narrow/stream/428973-nightly-testing/topic/quote4/near/457349304)
in quote4.
2024-08-12 02:36:42 +00:00
Kim Morrison
fc5615880e chore: add deprecation for Array.get_modify (#4957) 2024-08-12 02:02:18 +00:00
François G. Dorais
23d898c86b feat: TransGen r is transitive (#4960)
Closes #4959
2024-08-12 02:01:52 +00:00
Tobias Grosser
dfe493d9d8 feat: add BitVec.[sshiftRight/shiftLeft]_*_distrib (#4951)
After having added already `BitVec.ushiftRight_*_distrib`in
https://github.com/leanprover/lean4/pull/4667 for ushiftRight, this PR
now completes the `*_distrib` theorems for shift.
2024-08-12 02:00:58 +00:00
François G. Dorais
759ece7f9e fix: naming convention for UInt lemmas (#4514)
Closes #4513

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
2024-08-12 01:03:21 +00:00
Leonardo de Moura
89c3079072 chore: fix typo in hash code for Expr equality test (#4990)
We observed a small performance improvement at
https://github.com/leanprover/LNSym/blob/proof_size_expt/Proofs/SHA512/Experiments/Sym30.lean
Before: 2.65s
After: 2.60s
2024-08-12 00:47:08 +00:00
Tobias Grosser
37f9063c3e feat: add BitVec.neg_neg (#4977)
.. as well as neg_neq_iff_neq_neg.

---------

Co-authored-by: Henrik Böving <hargonix@gmail.com>
2024-08-12 00:34:46 +00:00
Kim Morrison
8364c3e178 chore: begin development cycle for v4.12.0 (#4986) 2024-08-12 00:33:05 +00:00
Kim Morrison
215b4a6a8d fix: omega regression (#4989)
This is a better fix to the problem reported at
https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/nat.20fighting,
which itself had a problem as reported at
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/omega.20regression/near/456539091.
2024-08-12 00:24:24 +00:00
Leonardo de Moura
7d7447563d fix: panic at reducePow (#4988)
closes #4947
2024-08-12 00:20:29 +00:00
Leonardo de Moura
2436562d57 fix: regular mvar assignments take precedence over delayed ones (#4987)
closes #4920
2024-08-12 00:14:38 +00:00
Tobias Grosser
4236d8a85b feat: add Std.[Associative|Commutative] instances for BitVec.[and|or|xor] (#4981) 2024-08-11 09:40:07 +00:00
Kyle Miller
5f31e938c1 feat: @[app_delab] (#4976)
Adds `@[app_delab ident]` as a macro for `@[delab app.ident]`. Resolves
the identifier when expanding the macro, saving needing to use the fully
qualified identifiers that `@[delab]` requires. Also, unlike `@[delab]`,
throws an error if the identifier cannot be resolved.

Closes #4899
2024-08-10 16:54:39 +00:00
Kyle Miller
95bf6793aa fix: make cdot anonymous function notation handle ambiguous notation (#4833)
Fixes an issue where each alternative in choice nodes would get their
own arguments. Now cdot function expansion is aware of choice nodes.

Also modifies the variable naming so that multi-argument functions like
`(· + ·)` expand as `fun x1 x2 => x1 + x2` rather than `fun x x_1 => x +
x_1`.

Closes #4832
2024-08-09 21:16:51 +00:00
Lean stage0 autoupdater
30cf3bb3bf chore: update stage0 2024-08-09 16:16:48 +00:00
Sebastian Ullrich
238885436b fix: resolve parser promise before starting elaboration (#4974)
A regression introduced in #3106 that meant we could actually not
interrupt elaboration of previous document versions but would always
wait on them
2024-08-09 15:38:13 +00:00
David Thrane Christiansen
30a52b794a feat: add [ext] attribute to Array.ext (#4970)
This fixes a minor papercut.
2024-08-09 12:40:39 +00:00
Sebastian Ullrich
5da9038fb4 chore: adapt stdlib to new variable behavior 2024-08-09 11:50:54 +02:00
Sebastian Ullrich
3a588e7547 chore: update stage0 2024-08-09 11:50:54 +02:00
Sebastian Ullrich
9f76cb9aa5 feat: new variable command 2024-08-09 11:50:54 +02:00
Lean stage0 autoupdater
88a7f5c592 chore: update stage0 2024-08-09 08:46:18 +00:00
Kim Morrison
dd6ed124ba chore: upstream Thunk.ext (#4969) 2024-08-09 05:49:13 +00:00
Alok Singh
6dd502321f chore: add parallelism fallback for macOS on build (#4647)
1 less thing to think about.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-08-08 14:26:06 +00:00
David Thrane Christiansen
bcbd7299e9 fix: export more symbols needed by Verso (#4956)
This enables the Verso LSP server extensions to work.
2024-08-08 13:31:34 +00:00
Markus Himmel
b144107ed5 chore: deprecate Lean.HashMap and Lean.HashSet (#4954)
This restores all of the imports of `Lean.Data.HashMap` and
`Lean.Data.HashSet` so that users actually see the deprecation warnings
instead of a "declaration not found" error.
2024-08-08 12:46:10 +00:00
Sebastian Ullrich
c24d2186fc doc: stderrAsMessages is now the default on the cmdline as well (#4955) 2024-08-08 10:28:22 +00:00
Sebastian Ullrich
7ef2d2fea2 chore: CI: remove rebase command
We'll try using the buttons provided by GitHub for now
2024-08-08 11:17:52 +02:00
Henrik Böving
7776852d22 feat: revamp file IO, this time Windows compatible (#4950)
This implements a naive version of `getline` because Windows does not
have `getline`. Given the fact that `FILE` has buffered IO, calling
`fgetc` in a loop is not as big of a performance hazard as it might seem
at first glance.

The proper solution to this would of course be to have our own buffered
IO so we are fully in charge of the buffer. In this situation we could
check the entire buffer for a newline at once instead of char by char.
However that is not going to happen for the near future so I propose we
stay with this implementation. If reading individual lines of a file
does truly end up being the performance bottle neck we have already
won^^.
2024-08-07 22:33:37 +02:00
Markus Himmel
63c4de5fea chore: update stage0 2024-08-07 18:24:42 +02:00
Markus Himmel
3b14642c42 chore: build Lake again 2024-08-07 18:24:42 +02:00
Markus Himmel
d52da36e68 chore: update stage0 2024-08-07 18:24:42 +02:00
Markus Himmel
bf82965eec chore: avoid builing Lake 2024-08-07 18:24:42 +02:00
Markus Himmel
4bac74c4ac chore: switch to Std.HashMap and Std.HashSet almost everywhere 2024-08-07 18:24:42 +02:00
Henrik Böving
8b9d27de31 chore: Revert "feat: Revamp file reading and writing" (#4948)
Reverts leanprover/lean4#4906
2024-08-07 16:00:45 +00:00
Henrik Böving
d15f0335a9 feat: setup Std.Sat with definitions of SAT and CNF (#4933)
Step 1 out of approximately 7 to upstream LeanSAT.

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
Co-authored-by: Markus Himmel <markus@lean-fro.org>
2024-08-07 15:44:46 +00:00
Sebastian Ullrich
240ebff549 chore: Windows needs more LEAN_EXPORTs (#4941) 2024-08-07 17:13:13 +02:00
Sebastian Ullrich
a29bca7f00 chore: CI: placate linter 2024-08-07 16:52:18 +02:00
Tobias Grosser
313f6b3c74 chore: name variables in Data/BitVec consistently (#4930)
This change canonicalizes the BitVec variable names to `x y z : BitVec`
instead of alternative namings such as `s t : BitVec` or `a b : BitVec`.
Variable names that carry semantic meaning such as `(msbs : BitVec w)
(lsb : Bool)` remain untouched.

This is purely a naming change to make our bitvector proofs more
consistent and polish the (auto-generated) documentation as a very small
step towards polishing the documentation of the BitVec library in Lean.

---------

Co-authored-by: AnotherAlexHere <153999274+AnotherAlexHere@users.noreply.github.com>
2024-08-07 13:43:15 +00:00
Markus Himmel
43fa46412d feat: deprecated variants of hash map query methods (#4943)
#4917 will expose users of the `Lean` API to the renaming of the hash
map query methods. This PR aims to make the transition easier by adding
deprecated functions with the old names.
2024-08-07 13:36:19 +00:00
Henrik Böving
234704e304 feat: upstream utilities around Array, Bool and Prod from LeanSAT (#4945)
Co-authored-by: Kim Morrison <kim@tqft.net>
2024-08-07 12:32:40 +00:00
Sebastian Ullrich
12a714a6f9 chore: CI: fix rebase command 2024-08-07 14:27:53 +02:00
Sebastian Ullrich
cdc7ed0224 chore: CI: fix rebase command 2024-08-07 14:21:43 +02:00
Sebastian Ullrich
217abdf97a chore: CI: fix rebase command 2024-08-07 14:15:18 +02:00
Sebastian Ullrich
490a2b4bf9 chore: CI: fix rebase command 2024-08-07 14:05:00 +02:00
Sebastian Ullrich
84d45deb10 chore: CI: fix rebase 2024-08-07 14:02:57 +02:00
Sebastian Ullrich
f46d216e18 chore: CI: !rebase PR comment command 2024-08-07 13:53:17 +02:00
Tobias Grosser
cc42a17931 feat: add ushiftRight_*_distrib theorems (#4667) 2024-08-07 10:43:54 +00:00
Siddharth
e106be19dd feat: sshiftRight bitblasting (#4889)
We follow the same strategy as
https://github.com/leanprover/lean4/pull/4872,
https://github.com/leanprover/lean4/pull/4571, and implement bitblasting
theorems for `sshiftRight`.

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
2024-08-07 10:33:56 +00:00
Sebastian Ullrich
1efd6657d4 test: unflakify test cases (#4940)
With the recent unification of server and cmdline processing,
`IO.Process` tests that previously broke the server because they
directly wrote to stdout are now flaky on the cmdline because
elaboration and reporting are happening in separate threads. By removing
direct writes to stdout, the race condition is removed and the file can
actually be edited in the language server as well again.
2024-08-07 09:34:29 +00:00
Henrik Böving
473b34561d feat: Revamp file reading and writing (#4906)
This PR:
- changes the implementation of `readBinFile` and `readFile` to only
require two system calls (`stat` + `read`) instead of one `read` per
1024 byte chunk.
- fixes a bug where `Handle.getLine` would get tripped up by a NUL
character in the line and cut the string off. This is caused by the fact
that the original implementation uses `strlen` and `lean_mk_string`
which is the backer of `mk_string` does so as well.
- fixes a bug where `Handle.putStr` and thus by extension `writeFile`
would get tripped up by a NUL char in the line and cut the string off.
Cause here is the use of `fputs` when a NUL char is possible.

Closes: #4891 
Closes: #3546
Closes: #3741
2024-08-07 07:39:15 +00:00
Marc Huisinga
574066b30b fix: language server windows issues (#4821)
This PR resolves two language server bugs that especially affect Windows
users:
1. Editing the header could result in the watchdog not correctly
restarting the file worker (#3786, #3787), which would lead to the file
seemingly being processed forever.
- The cause of this issue was a race condition in the watchdog that was
accidentally introduced as far back as #1884: In specific circumstances,
the watchdog will attempt forwarding a message to the file worker after
the process has exited due to a changed header, but before the file
worker exiting has been noticed by the watchdog (which will then restart
the file worker). In this case, the watchdog would mark the file worker
as having crashed and not look at its exit code to restart the file
worker, but instead treat it like a crashed file worker that will only
be restarted when editing the file again. Not inspecting the exit code
of the file worker when it crashed from forwarding a message from the
file worker is necessary since we do not restart the file worker until
another notification from the client arrives, and so we would read the
same crash exit code over and over again in the main loop of the
watchdog if we did not remove it from our list of file workers that we
listen to.
- This PR resolves this issue by distinguishing between "crashes when
forwarding messages to the file worker" and "crashes when forwarding
messages from the file worker". In the former case, we still inspect the
exit code of the file worker and potentially restart it if the imports
changed, whereas in the latter case, we stop inspecting the exit code of
the file worker. This is correct because the latter case is exactly the
one where we need to stop inspecting the exit code but where a crash
cannot occur as a result of a changed header, whereas the former case is
exactly the one where we still need to inspect the exit code after a
crash to ensure that we restart the file worker in case it exited
because the header changed.
- At some point in the future, it would be nice to revamp the
concurrency model of the watchdog entirely now that we have all those
fancy concurrency primitives that were not available four years ago when
the watchdog was first written.

2. On an especially slow Windows machine, we found that starting the
language server would sometimes not succeed at all because reading from
the stdin pipe in the watchdog produced an EINVAL error, which was in
turn caused by an NT "pipe empty" error.
- After lots of debugging, @Kha found that Lake accidentally passes its
stdin to Git because it does not explicitly set the `stdin` field to
`null` when spawning the process.
- Changing this fixes the issue, which suggests that Git may mutate the
pipe we pass to it to be non-blocking, which then causes a "pipe empty"
error in the watchdog when we also attempt to read from that same pipe.
- I'm still very uncertain why we only saw this issue on one
particularly slow machine and not across the whole eco system.

This PR also resolves an issue where we would not correctly emit
messages that we received while the file worker is being restarted to
the corresponding file worker after the restart.

Closes #3786, closes #3787.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2024-08-07 06:19:33 +00:00
Kim Morrison
1e6d617aad chore: minor fixes to release checklist (#4937) 2024-08-07 01:09:35 +00:00
Leonardo de Moura
c17a4ddc94 perf: skip betaReduceLetRecApps if it is not needed (#4936) 2024-08-07 00:57:35 +00:00
Leonardo de Moura
5be4f5e30c perf: skip eraseRecAppSyntaxExpr if it is not needed (#4935) 2024-08-07 00:29:50 +00:00
Leonardo de Moura
3c5ac9496f perf: expr_eq_fn (#4934)
This PR contains a collection of small optimizations that improve the
equality test for `Expr`.
2024-08-07 00:02:14 +00:00
Kyle Miller
6c1f8a8a63 fix: ensure autoparam errors have correct positions (#4926)
Autoparam tactic scripts have no source positions, which until recently
made it so that any errors or messages would be logged at the current
ref, which was the application or structure instance being elaborated.
However, with the new incrementality features the ref is now carefully
managed to avoid leakage of outside data. This inhibits the elaborator's
ref from being used for the tactic's ref, causing messages to be placed
at the beginning of the file rather than on the syntax that triggered
the autoparam.

To fix this, now the elaborators insert the ref's source position
everywhere into the autoparam tactic script.

If in the future messages for synthetic tactics appear at the tops of
files in other contexts, we should consider an approach where
`Lean.Elab.Term.withReuseContext` uses something like `replaceRef` to
set the ref while disabling incrementality when the tactic does not
contain source position information.

Closes #4880
2024-08-06 22:27:51 +00:00
Matthew Robert Ballard
7bea3c1508 chore: wrap diagnostic results in MessageData.traces (#4897)
Currently, the messages in the diagnostic summaries are created by
appending interpolated strings. We wrap these in `.trace`'s, and the
results are better formatted when expanding child nodes in the info
view. Particularly, the latter diagnostic summaries remain on their own
lines flush to the left instead of on the same line directly adjacent to
the last child node.
2024-08-06 19:10:51 +00:00
Leonardo de Moura
a27d4a9519 chore: reduce stack space usage at instantiate_mvars_fn (#4931) 2024-08-06 17:38:59 +00:00
Leonardo de Moura
4a2fb6e922 chore: fix typo (#4929) 2024-08-06 15:27:20 +00:00
Henrik Böving
b7db82894b feat: generalized Parsec (#4774)
For experimentation by @the-sofi-uwu.

I also have an efficient number parser in LeanSAT that I am planning to
upstream after we have sufficiently bikeshed this change.
2024-08-06 15:17:23 +00:00
Sebastian Ullrich
35e1554ef7 chore: ignore stale leanpkg tests (#4925) 2024-08-06 08:19:33 +00:00
Leonardo de Moura
14d59b3599 feat: theorem diagnostics (#4924)
When `set_option diagnostics true`, for each theorem with size >
`diagnostics.threshold.proofSize`, display proof size, and the number of
applications for each constant symbol.
2024-08-06 01:01:28 +00:00
Leonardo de Moura
a8e480cd52 chore: profile instantiateMVars at MutualDef.lean (#4923)
`instantiateMVars` can be a performance bottleneck when assembling the
final proof term.

For example, it takes approx. 1 second at

https://github.com/leanprover/LNSym/blob/proof_size_expt/Proofs/SHA512/Experiments/Sym30.lean
2024-08-05 19:15:56 +00:00
Leonardo de Moura
d07239d1bd perf: use lean_instantiate_expr_mvars at instantiateExprMVars (#4922)
This PR completes #4915
2024-08-05 19:02:54 +00:00
Leonardo de Moura
590de785cc chore: cleanup betaRev (#4921) 2024-08-05 18:02:20 +00:00
Lean stage0 autoupdater
d671d0d61a chore: update stage0 2024-08-05 17:53:40 +00:00
Leonardo de Moura
8e476e9d22 perf: instantiateExprMVars (#4915)
TODO: 
- Support for `zeta := true` at `apply_beta`.
- Investigate test failure. 
- Break PR in pieces because of bootstrapping issues. The current PR
updates a stage0 file to workaround the issue.

Motivation: significant performance improvement at
https://github.com/leanprover/LNSym/blob/proof_size_expt/Proofs/SHA512/Experiments/Sym30.lean

With M1 Pro:
- Before: 4.56 secs
- After: 3.16 secs

Successfully built stage2 using this PR
2024-08-05 17:15:22 +00:00
Sebastian Ullrich
a3d144a362 feat: new snapshot architecture on the cmdline (#3106)
This is #3014 with cad5cce reverted for testing.
2024-08-05 15:57:42 +00:00
Sebastian Ullrich
87d41e6326 chore: missing include on Windows (#4919) 2024-08-05 15:50:43 +00:00
Kim Morrison
d6cb2432c6 chore: remove unnecessary steps from release checklist (#4914) 2024-08-05 01:57:30 +00:00
Kim Morrison
c0ffc85d75 chore: require docs in BitVec (#4913) 2024-08-05 01:12:04 +00:00
Leonardo de Moura
f62359acc7 perf: use lean_instantiate_level_mvars (#4912)
implemented in C/C++.
Next step: same for `instantiateExprMVars`
2024-08-04 23:21:57 +00:00
Shuhao Song
2d09c96caf chore: cli help text: comma-separate alternative option forms (#4911)
The help message of Lean command line contains
```
--o=oname -o create olean file
```
This may lead to misunderstanding that the command needs both argument
`--o=oname` and `-o`, i. e. `lean --o=test.o -o test.lean`. In the help
message of GNU coreutils, such as `ls`, it is `-a, --all ...`, which
might be better.
Some discussion is on Zulip thread
[https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/The.20help.20message.20of.20Lean.20command.20line](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/The.20help.20message.20of.20Lean.20command.20line).
2024-08-04 22:54:34 +00:00
Lean stage0 autoupdater
21b4377d36 chore: update stage0 2024-08-04 19:11:50 +00:00
Leonardo de Moura
1e9d96be22 perf: add lean_instantiate_level_mvars (#4910)
The new code is not active yet because of bootstrapping issues.
It requires an `update_stage0`.
2024-08-04 18:31:44 +00:00
Leonardo de Moura
647a5e9492 perf: use NatPow Int instead of HPow Int Nat Int (#4903)
This modification improves the performance of the example in issue
#4861. It no longer times out but is still expensive.

Here is the analysis of the performance issue: Given `(x : Int)`, to
elaborate `x ^ 1`, a few default instances have to be tried.

First, the homogeneous instance is tried and fails since `Int` does not
implement `Pow Int`. Then, the `NatPow` instance is tried, and it also
fails. The same process is performed for each term of the form `p ^ 1`.
There are seveal of them at #4861. After all of these fail, the lower
priority default instance for numerals is tried, and `x ^ 1` becomes `x
^ (1 : Nat)`. Then, `HPow Int Nat Int` can be applied, and the
elaboration succeeds. However, this process has to be repeated for every
single term of the form `p ^ 1`. The elaborator tries all homogeneous
`HPow` and `NatPow` instances for all `p ^ 1` terms before trying the
lower priority default instance `OfNat`.

This commit ensures `Int` has a `NatPow` instance instead of `HPow Int
Nat Int`. This change shortcuts the process, but it still first tries
the homogeneous `HPow` instance, fails, and then tries `NatPow`. The
elaboration can be made much more efficient by writing `p ^ (1 : Nat)`.
2024-08-03 00:35:04 +00:00
Clement Courbet
9c4028aab4 perf: avoid expr copies in replace_rec_fn::apply (#4702)
Those represent ~13% of the time spent in `save_result`,
even though `r` is a temporary in all cases but one.

See #4698 for details.

---------

Co-authored-by: Leonardo de Moura <leomoura@amazon.com>
2024-08-02 18:32:36 +00:00
Clement Courbet
2c002718e0 perf: fix implementation of move constructors and move assignment ope… (#4700)
…rators

Right now those constructors result in a copy instead of the desired
move. We've measured that expr copying and assignment by itself uses
around 10% of total runtime on our workloads.

See #4698 for details.
2024-08-02 17:55:03 +00:00
Sebastian Ullrich
b07384acbb feat: accept user-defined options on the cmdline (#4741)
Initial options are now re-parsed and validated after importing. Cmdline
option assignments prefixed with `weak.` are silently discarded if the
option name without the prefix does not exist.

Fixes #3403
2024-08-02 12:24:56 +00:00
Sebastian Ullrich
efc99b982e chore: deprecate Nix-based build, remove interactive components (#4895)
Users who prefer the flake build should maintain it externally
2024-08-02 09:57:34 +00:00
Siddharth
ee430b6c80 feat: getLsb_replicate (#4873)
This allows bitblasting `BitVec.replicate`.

I changed the definition of `BitVec.replicate` to use `BitVec.cast` in
order to make the proof smoother, since it's an easier time simplifying
away terms with `BitVec.cast`.

---------

Co-authored-by: Tobias Grosser <tobias@grosser.es>
2024-08-01 23:07:19 +00:00
Alok Singh
a8740f5ed9 doc: add docstring to IO.FS.realpath (#4648)
Based on `IO.FS.rename` template.
2024-08-01 14:00:54 +00:00
Sebastian Ullrich
5e6a3cf5f9 chore: revert "fix: make import resolution case-sensitive on all platforms" (#4896)
Reverts leanprover/lean4#4538 because of unexpected overhead
2024-08-01 13:55:37 +00:00
Sebastian Ullrich
0ed1cf7244 fix: LEAN_EXPORT in sharecommon (#4893) 2024-08-01 13:03:25 +02:00
Lean stage0 autoupdater
e83f78d5af chore: update stage0 2024-08-01 06:54:29 +00:00
David Thrane Christiansen
32b9de8c77 fix: export symbols needed by Verso (#4884)
Verso needed a symbol that was unexported - this exposes it again.
2024-08-01 04:56:27 +00:00
Leonardo de Moura
a856016b9d perf: precise cache for expr_eq_fn (#4890)
This performance issue was exposed by the benchmarks at
https://github.com/leanprover/LNSym/tree/proof_size_expt/Proofs/SHA512/Experiments
2024-08-01 02:56:41 +00:00
Siddharth
c517688f1d feat: ushiftRight bitblasting (#4872)
This adds theorems `ushiftRight_rec_zero`, `ushiftRight_rec_succ`,
`ushiftRight_rec_eq`, and `shiftRight_eq_shiftRight_rec`.

---------

Co-authored-by: Tobias Grosser <github@grosser.es>
2024-07-31 21:44:06 +00:00
Leonardo de Moura
db594425bf refactor: sharecommon (#4887)
This PR also fixes a missing borrow annotation.
2024-07-31 19:13:12 +00:00
Kim Morrison
dcea47db02 chore: shorten suggestion about diagnostics (#4882)
This message is often incorporated into source files via `#guard_msgs`.
This change ensures it won't go over the 100 character ruler, and I
think is equally grammatical. :-)
2024-07-31 17:56:43 +00:00
Siddharth
f869902a4b feat: Nat simprocs for simplifying bit expressions (#4874)
This came up in the context of simplifying proof states for
https://github.com/leanprover/LNSym.
2024-07-31 17:26:05 +00:00
Sebastian Ullrich
d5a8c9647f fix: make import resolution case-sensitive on all platforms (#4538)
Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-07-31 16:48:14 +00:00
Sebastian Ullrich
d19bab0c27 feat: include command (#4883)
To be implemented in #4814
2024-07-31 13:25:54 +00:00
Sebastian Ullrich
6a4159c4a7 refactor: split out Lean.Language.Lean.Types (#4881) 2024-07-31 09:50:12 +00:00
Kim Morrison
8acdafd5b3 chore: correct doc-string for Array.swap! (#4869) 2024-07-31 04:02:30 +00:00
Kim Morrison
688da9d8a7 chore: updates to release_checklist.md (#4876)
Co-authored-by: Kyle Miller <kmill31415@gmail.com>
2024-07-31 04:02:19 +00:00
Kyle Miller
d5e7dbad80 fix: make "use `set_option diagnostics true" message conditional on current setting (#4781)
It is confusing that the message suggesting to use the `diagnostics`
option is given even when the option is already set. This PR makes use
of lazy message data to make the message contingent on the option being
false.

It also tones down the promise that there is any diagonostic information
available, since sometimes there is nothing to report.

Suggested by Johan Commelin.
2024-07-31 03:53:09 +00:00
Kyle Miller
82f48740dc chore: copy release notes from releases/v4.10.0 (#4864)
This also updates the 4.9.0 release notes with backported changes.
2024-07-31 03:30:13 +00:00
Kyle Miller
a827759f1d fix: mistake in statement of List.take_takeWhile (#4875)
This theorem is meant to say that `List.take` and `List.takeWhile`
commute.
2024-07-31 03:29:34 +00:00
Kim Morrison
a4015ca36c chore: rename PSigma.exists (#4878) 2024-07-31 03:26:17 +00:00
Kim Morrison
81719f94c9 chore: fix binder explicitness in List.map_subset (#4877) 2024-07-31 03:03:52 +00:00
Leonardo de Moura
afe0b5a013 perf: precise cache for foldConsts (#4871)
It addresses a performance issue at
https://github.com/leanprover/LNSym/blob/proof_size_expt/Proofs/SHA512/Experiments/Sym20.lean
2024-07-30 18:35:45 +00:00
Kim Morrison
90dab5e267 chore: fix naming of List.Subset lemmas (#4868) 2024-07-30 09:21:23 +00:00
Kim Morrison
6a904f2c85 chore: upstream List.eraseIdx lemmas (#4865) 2024-07-30 06:59:45 +00:00
Kim Morrison
2c396d6424 chore: upstream List.pairwise_iff_getElem (#4866) 2024-07-30 06:55:29 +00:00
Kim Morrison
69f86d6478 chore: split Init.Data.List.Lemmas (#4863)
Init.Data.List.Lemmas had reached 5000 lines: splitting into
function-specific files.
2024-07-30 03:17:34 +00:00
Joachim Breitner
4ea55687a5 refactor: deriving DecidableEq to use termination_by structural (#4826)
now that we support structural mutual recursion, I expect that every
`DecidableEq` instance be implemented using structural recursion, so
let's be explicit about it.
2024-07-29 21:24:05 +00:00
Kyle Miller
69c71f6476 fix: make elabAsElim aware of explicit motive arguments (#4817)
Some eliminators (such as `False.rec`) have an explicit motive argument.
The `elabAsElim` elaborator assumed that all motives are implicit.

If the explicit motive argument is `_`, then it uses the elab-as-elim
procedure, and otherwise it falls back to the standard app elaborator.

Furthermore, if an explicit elaborator is not provided, it falls back to
treating the elaborator as being implicit, which is convenient for
writing `h.rec` rather than `h.rec _`. Rationale: for `False.rec`, this
simulates it having an implicit motive, and also motives are generally
not going to be available in the expected type.

Closes #4347
2024-07-29 19:18:47 +00:00
Kyle Miller
7f128b39e7 feat: more than one optional argument can be omitted while pretty printing (#4854)
Before, the delaborator was conservative about omitting optional
arguments, only omitting the very last one. Now it can omit arbitrarily
long sequences of optional arguments from the end.

For simplicity of implementation, every optional argument is delaborated
and then potentially discarded. It could save state and lazily
delaborate, but we're running under the hypothesis that most optional
arguments are for very simple values (like `true`, `false`, or a numeric
literal), so it is unlikely that efficiency gains, if any, are worth it.
In particular, in the future structure constructors will have optional
arguments, but `unexpandStructureInstance` assumes none of the optional
fields are omitted.

Closes #4812
2024-07-29 19:02:39 +00:00
Kim Morrison
a845a007ac chore: fix universe in PSigma.exists (#4862) 2024-07-29 12:30:31 +00:00
Sebastian Ullrich
abf4206e9c chore: CI: fix msys2 2024-07-29 10:25:59 +02:00
Kim Morrison
83ad82162f feat: upstream more List lemmas (#4856) 2024-07-28 23:23:59 +00:00
arthur-adjedj
93ac635a89 chore: fix parenthesizing in test 2024-07-29 08:58:49 +10:00
Kim Morrison
642c28cdbb chore: update stage0 2024-07-29 08:58:49 +10:00
arthur-adjedj
a04f3cab5a fix: reduction behaviour of derived BEq instances
fix: forgot an assignation
2024-07-29 08:58:49 +10:00
arthur-adjedj
86af04cc08 fix: handle dependent fields when deriving BEq 2024-07-29 08:58:49 +10:00
arthur-adjedj
7253ef8751 chore: inverse rhs construction order in BEq's handler 2024-07-29 08:58:49 +10:00
Joachim Breitner
f830fc9f4d refactor: IndPredBelow: use apply_assumption (#4841)
when transforming the `match` statements in `IndPredBelow`, given a
local variable `x : T`, we need to search for `hx : T.below x`.
Previously this was done using the custom `backwardsChaining` method,
although my hypothesis is that we don’t need to chain anything here, and
can use `apply_assumption`.
2024-07-28 17:22:54 +00:00
Joachim Breitner
671ce7afd3 fix: IndPred: track function's motive in a let binding, use withoutProofIrrelevance, no chaining (#4839)
this improves support for structural recursion over inductive
*predicates* when there are reflexive arguments.

Consider
```lean
inductive F: Prop where
  | base
  | step (fn: Nat → F)

-- set_option trace.Meta.IndPredBelow.search true
set_option pp.proofs true

def F.asdf1 : (f : F) → True
  | base => trivial
  | step f => F.asdf1 (f 0)
termination_by structural f => f`
```

Previously the search for the right induction hypothesis would fail with
```
could not solve using backwards chaining x✝¹ : F
x✝ : x✝¹.below
f : Nat → F
a✝¹ : ∀ (a : Nat), (f a).below
a✝ : Nat → True
⊢ True
```

The backchaining process will try to use `a✝ : Nat → True`, but then has
no idea what to use for `Nat`.

There are three steps here to fix this.

1. We let-bind the function's type before the whole process. Now the
   goal is

   ```
   funType : F → Prop := fun x => True
   x✝ : x✝¹.below
   f : Nat → F
   a✝¹ : ∀ (a : Nat), (f a).below
   a✝ : ∀ (a : Nat), funType (f a)
   ⊢ funType (f 0)
   ```
2. Instead of using the general purpose backchaining proof search, which
is more
powerful than we need here (we need on recursive search and no
backtracking),
   we have a custom search that looks for local assumptions that 
   provide evidence of `funType`, and extracts the arguments from that
   “type” application to construct the recursive call.

   Above, it will thus unify `f a =?= f 0`.

3. In order to make progress here, we also turn on use
`withoutProofIrrelevance`,
because else `isDefEq` is happy to say “they are equal” without actually
looking
   at the terms and thus assigning `?a := 0`.

This idea of let-binding the function's motive may also be useful for
the other recursion compilers, as it may simplify the FunInd
construction. This is to be investigated.

fixes #4751
2024-07-28 17:22:27 +00:00
Kim Morrison
87c92a3f87 feat: upstream more List operations (#4855)
Upstreams `tail`, `findIdx`, `indexOf`, `countP`, `count`, `range'` from
Batteries.
2024-07-28 04:52:21 +00:00
Ikko Eltociear Ashimine
15bf41cd67 chore: update Topological.lean (#4853)
minor fix
2024-07-27 18:14:42 +00:00
Kyle Miller
906bc583c5 fix: handle unimported builtin names for location links (#4780)
The function `locationLinksFromDecl` could throw an error if the name it
is provided doesn't exist in the environment, which is possible if for
example an elaborator is a builtin.

Closes #3789
2024-07-27 17:39:39 +00:00
Kim Morrison
ea43ebd54a chore: cleanups for Mathlib.Init (#4852)
It is convenient to adjust some attributes here, to allow easier cleanup
in `Mathlib.Init`.
2024-07-27 07:37:17 +00:00
Siddharth
bb9c9bd99f feat: bitVec shiftLeft recurrences for bitblasting (#4571)
```lean 
@[simp]
theorem shiftLeftRec_zero (x : BitVec w₁) (y : BitVec w₂) :
    shiftLeftRec x y 0 = x <<< (y &&& twoPow w₂ 0)  := by
  simp [shiftLeftRec]

@[simp]
theorem shiftLeftRec_succ (x : BitVec w₁) (y : BitVec w₂) :
    shiftLeftRec x y (n + 1) =
      (shiftLeftRec x y n) <<< (y &&& twoPow w₂ (n + 1)) := by
  simp [shiftLeftRec]

theorem shiftLeftRec_eq (x : BitVec w₁) (y : BitVec w₂) (n : Nat) (hn : n + 1 ≤ w₂) :
  shiftLeftRec x y n = x <<< (y.truncate (n + 1)).zeroExtend w₂ := by
```

These theorems are used for bitblasting shiftLeft in LeanSAT.

---------

Co-authored-by: Alex Keizer <alex@keizer.dev>
Co-authored-by: Kim Morrison <scott@tqft.net>
Co-authored-by: Tobias Grosser <github@grosser.es>
Co-authored-by: Tobias Grosser <tobias@grosser.es>
2024-07-27 06:36:52 +00:00
Mac Malone
fe5894f2f6 feat: lake: CLI options to control output & failure log levels (#4847)
Adds the `--log-level=<lv>` CLI option for controlling the minimum log
level Lake should output. For instance, `--log-level=error` will only
print errors (not warnings or info).

Also, adds the parallel `--fail-level` CLI option to control what the
minimum log level of build failures is. The existing `--iofail` and
`--wfail` options are equivalent to `--fail-level=info` and
`--fail-level=warning` , respectively.

Closes #4805,
2024-07-27 06:16:59 +00:00
Mac Malone
3ecbf4ae2d doc: lake: require @ git in README (#4849)
Demonstrates `require @ git` in Lake's README and tweaks related
documentation.
2024-07-27 02:44:50 +00:00
Sebastian Ullrich
9b342efb84 fix: calling programs with spaces on Windows (#4515)
This entire API has been carefully optimized for maximum pain output

---------

Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-07-26 17:35:05 +00:00
Sebastian Ullrich
c02aa98c6a doc: triage 2024-07-26 18:24:06 +02:00
Kim Morrison
18ba5f24e1 chore: correct List.Subset lemma names (#4843) 2024-07-26 11:36:28 +00:00
Kim Morrison
cbe39dc4bb chore: fix List deprecations (#4842) 2024-07-26 11:32:18 +00:00
Joachim Breitner
a5b8d5b486 chore: upon nightly release, trigger nightly_bump_toolchain on mathlib4 (#4838)
as discussed at
https://leanprover.zulipchat.com/#narrow/stream/428973-nightly-testing/topic/Bumping.20more.20often/near/453976634
2024-07-26 06:51:19 +00:00
Kim Morrison
895391b73f feat: List.IsPrefix/IsSuffix is decidable (#4837) 2024-07-26 05:17:41 +00:00
Kim Morrison
e280de00b6 feat: gaps/cleanup in List lemmas (#4835) 2024-07-26 05:00:50 +00:00
Kim Morrison
8c87a90cea chore: upstream IsPrefix/IsSuffix/IsInfix (#4836)
Further lemmas to follow; this is the basic material from Batteries.
2024-07-26 04:35:36 +00:00
Joachim Breitner
54c22efca1 fix: structural recursion: do not check for brecOn too early (#4831)
Due to nested recursion, we do two passes of `getRecArgInfo`: One on
each argument in isolation, to see which inductive types are around
(e.g. `Tree` and `List`), and
then we later refine/replace this result with the data for the nested
type former (the implicit `ListTree`).

If we have nested recursion through a non-recursive data type like
`Array` or `Prod` then arguemnts of these types should survive the first
phase, so that we can still use them when looking for, say, `Array
Tree`.

This was helpfully reported by @arthur-adjedj.
2024-07-25 15:25:34 +00:00
Joachim Breitner
d4f2db9559 chore: report github actions failure on zulip (#4830)
only the master branch
2024-07-25 11:15:33 +00:00
Joachim Breitner
39e0b41fe1 test: make #1697 test case Linux-Debug safe (#4829) 2024-07-25 10:26:01 +00:00
Marc Huisinga
84f8871c3f fix: filter duplicate subexpressions (#4786)
For every parenthesized expression `(foo)`, the InfoView produces an
interactive component both for `(foo)` itself and its subexpression
`foo` because the corresponding `TaggedText` in the language server is
duplicated as well. Both of these subexpressions have the same
subexpression position and so they are identical w.r.t. interactive
features.

Removing this duplication would help reduce the size of the DOM of the
InfoView and ensure that the UI for InfoView features is consistent for
`(foo)` and `foo` (e.g. hovers would always highlight `(foo)`, not
either `(foo)` or `foo` depending on whether the mouse cursor is on the
bracket or not). It would also help resolve a bug where selecting a
subexpression will yield selection highlighting both for `(foo)` and
`foo`, as we use the subexpression position to identify which terms to
highlight.

This PR adjusts the parenthesizer to move the corresponding info instead
of duplicating it.
2024-07-25 08:58:49 +00:00
Austin Letson
93fa9c8837 feat: create ci workflow on lake new/init (#4608)
Draft of adding ci workflow using lean-action on `lake new/init`

This PR is currently missing lake options for the user to control this
feature.

Closes #4606

---------

Co-authored-by: Mac Malone <tydeu@hatpress.net>
2024-07-25 01:23:54 +00:00
Sebastian Ullrich
0768ad4eb9 chore: CI: Jira sync 2024-07-24 19:52:55 +02:00
Kyle Miller
c545e7b0c9 fix: make sure anonymous dot notation works with pi-type-valued type synonyms (#4818)
When resolving anonymous dot notation (`.ident x y z`), it would reduce
the expected type to whnf. Now, it unfolds definitions step-by-step,
even if the type synonym is for a pi type like so
```lean
def Foo : Prop := ∀ a : Nat, a = a
protected theorem Foo.intro : Foo := sorry
example : Foo := .intro
```

Closes #4761
2024-07-24 17:09:42 +00:00
Sebastian Ullrich
7b3c64fc85 feat: trailing whitespace changes should not invalidate imports (#4580)
Thus, starting to type the first declaration after the imports should
not make them reload
2024-07-24 13:08:01 +00:00
Sebastian Ullrich
af0b563099 feat: respond to info view requests as soon as relevant tactic has finished execution (#4727)
After each tactic step, we save the info tree created by it together
with an appropriate info tree context that makes it stand-alone (which
we already did before to some degree, see `Info.updateContext?`). Then,
in the adjusted request handlers, we first search for a snapshot task
containing the required position, if so wait on it, and if it yielded an
info tree, use it to answer the request, or else continue searching and
waiting, falling back to the full info tree, which should be unchanged
by this PR.

The definition header does *not* report info trees early as in general
it is not stand-alone in the tactic sense but may contain e.g.
metavariables solved by the body in which case we do want to show the
ultimate state as before. This could be refined in the future in case
there are no unsolved mvars.

The adjusted request handlers are exactly the ones waited on together by
the info view, so they all have to be adjusted to have any effect on the
UX. Further request handlers may be adjusted in the future.

No new tests as "replies early" is not something we can test with our
current framework but the existing test suite did help in uncovering
functional regressions.
2024-07-24 13:02:13 +00:00
Sebastian Ullrich
af40e61811 chore: typo 2024-07-24 15:11:54 +02:00
Kim Morrison
1758b37a71 chore: List.filterMapM runs and returns left-to-right (#4820)
Closes #4676. Previously `List.filterMapM` was returning results
left-to-right, but evaluating right-to-left.
2024-07-24 09:00:10 +00:00
Joachim Breitner
3701bee777 test: test case for #4751 (#4819)
and tracing for `IndPredBelow.backwardsChaining`.
2024-07-24 08:14:25 +00:00
Sebastian Ullrich
6d971827e2 chore: CI: add back dropped check-stage3 2024-07-24 09:03:11 +02:00
Joachim Breitner
871c9b4164 test: update test output following stage0 update (#4815)
this is a consequenc of #4807 that only shows up once that change made
it to stage0, it seem.
2024-07-23 21:43:38 +00:00
Lean stage0 autoupdater
ee6737ab4d chore: update stage0 2024-07-23 16:14:57 +00:00
Joachim Breitner
7d60d8b563 feat: safer #eval, and #eval! (#4810)
previously, `#eval` would happily evaluate expressions that contain
`sorry`, either explicitly or because of failing tactics. In conjunction
with operations like array access this can lead to the lean process
crashing, which isn't particularly great.

So how `#eval` will refuse to run code that (transitively) depends on
the `sorry` axiom (using the same code as `#print axioms`).

If the user really wants to run it, they can use `#eval!`.

Closes #1697
2024-07-23 15:26:56 +00:00
Joachim Breitner
a4673e20a5 chore: release notes for mutual structural induction (#4808) 2024-07-23 07:40:29 +00:00
Marc Huisinga
b2ee8c240d doc: update quickstart guide (#4806)
This PR updates the screenshots and instructions in the quickstart guide
for the most recent Lean 4 VS Code extension version and makes a small
stylistic change suggested by @semorrison.
2024-07-23 07:31:21 +00:00
Markus Himmel
5d632a97b8 feat: more hash map lemmas (#4803) 2024-07-23 06:57:44 +00:00
Kyle Miller
5938dbbd14 fix: make elab_as_elim eagerly elaborate arguments for parameters appearing in the types of targets (#4800)
The `elab_as_elim` elaborator eagerly elaborates arguments that can help
with elaborating the motive, however it does not include the transitive
closure of parameters appearing in types of parameters appearing in ...
types of targets.

This leads to counter-intuitive behavior where arguments supplied to the
eliminator may unexpectedly have postponed elaboration, causing motives
to be type incorrect for under-applied eliminators such as the
following:

```lean
class IsEmpty (α : Sort u) : Prop where
  protected false : α → False

@[elab_as_elim]
def isEmptyElim [IsEmpty α] {p : α → Sort _} (a : α) : p a :=
  (IsEmpty.false a).elim

example {α : Type _} [IsEmpty α] :
  id (α → False) := isEmptyElim (α := α)
```

The issue is that when `isEmptyElim (α := α)` is computing its motive,
the value of the postponed argument `α` is still an unassignable
metavariable. With this PR, this argument is now among those that are
eagerly elaborated since it appears as the type of the target `a`.

This PR also contains some other fixes:
* When underapplied, does unification when instantiating foralls in the
expected type.
* When overapplied, type checks the generalized-and-reverted expected
type.
* When collecting targets, collects them in the correct order.

Adds trace class `trace.Elab.app.elab_as_elim`.

This is a followup to #4722, which added motive type checking but
exposed the eagerness issue.
2024-07-22 23:23:28 +00:00
grunweg
852add3e55 doc: document Command.Scope (#4748)
Also extends existing definition for `getScope`/`getScopes` and
clarifies that the `end` command is optional at the end of a file.

---------

Co-authored-by: Kyle Miller <kmill31415@gmail.com>
2024-07-22 21:55:37 +00:00
Joachim Breitner
20c857147c feat: unnecessary termination_by clauses cause warnings, not errors (#4809)
fixes #4804
2024-07-22 20:52:14 +00:00
Joachim Breitner
9f1eb479b0 feat: functional induction for mutual structural recursion (#4772) 2024-07-22 15:10:11 +00:00
Markus Himmel
92cca5ed1b chore: remove bif from hash map lemmas (#4791)
The original idea was to use `bif` in computation contexts and `if` in
propositional contexts, but this turned out to be really inconvenient in
practice.
2024-07-22 14:39:00 +00:00
Joachim Breitner
3a4d2cded3 refactor: Introduce PProdN module (#4807)
code to create nested `PProd`s, and project out, and related functions
were scattered in variuos places. This unifies them in
`Lean.Meta.PProdN`.

It also consistently avoids the terminal `True` or `PUnit`, for slightly
easier to read constructions.
2024-07-22 11:56:50 +00:00
Joachim Breitner
22ae04f3e7 refactor: FunInd overhaul (#4789)
This refactoring PR changes the structure of the `FunInd` module, with
the main purpose to make it easier to support mutual structural
recursion.

In particular the recursive calls are now longer recognized by their
terms (simple for well-founded recursion, `.app oldIH [arg, proof]`, but
tedious for structural recursion and even more so for mutual structural
recursion), but the type after replacing `oldIH` with `newIH`, where the
type will be simply and plainly `mkAppN motive args`).

We also no longer try to guess whether we deal with well-founded or
structural recursion but instead rely on the `EqnInfo` environment
extensions. The previous code tried to handle both variants, but they
differ too much, so having separate top-level functions is easier.

This also fuses the `foldCalls` and `collectIHs` traversals and
introduces a suitable monad for collecting the inductive hypotheses.
2024-07-21 14:46:52 +00:00
François G. Dorais
99f362979b feat: use usize for array types (#4802)
This is part 2 of 2 of #4801 (which closes #4654). That PR was split in
two to allow a stage0 update between declaring the `usize` functions and
using them where they are needed.
2024-07-21 12:26:04 +00:00
Lean stage0 autoupdater
3a309f7691 chore: update stage0 2024-07-21 11:05:36 +00:00
François G. Dorais
8f0631ab1f feat: usize for array types (#4801)
Add efficient `usize` functions for `Array`, `ByteArray`, `FloatArray`.

This is part 1 of 2 since there is a need to update stage0 between the
two parts. (See discussion below.)

Closes #4654
2024-07-21 10:23:49 +00:00
Markus Himmel
08acf5a136 fix: remove typeclass assumptions for Nodup.eraseP (#4790)
The `α` in the typeclass assumptions wasn't even the element type of the
list.
2024-07-21 07:51:42 +00:00
Markus Himmel
bdfaa00b1e doc: remove reference to HashMap.find? from Option docstring (#4782) 2024-07-21 07:51:06 +00:00
Leonardo de Moura
8ceb24a5e6 perf: Expr.replace (#4799)
use the kernel implementation.
2024-07-20 04:53:43 +00:00
Lean stage0 autoupdater
201749ccac chore: update stage0 2024-07-20 04:31:11 +00:00
Leonardo de Moura
d0bc4e4245 fix: replace_fn.cpp (#4798) 2024-07-19 21:20:43 -07:00
Lean stage0 autoupdater
c2117d75a6 chore: update stage0 2024-07-20 03:58:16 +00:00
Leonardo de Moura
3477b0e7f6 fix: for_each_fn.cpp (#4797) 2024-07-20 03:22:56 +00:00
Lean stage0 autoupdater
696f70bb4e chore: update stage0 2024-07-20 02:35:13 +00:00
Leonardo de Moura
726e162527 perf: kernel replace with precise cache (#4796)
Changes:

- We avoid the thread local storage.
- We use a hash map to ensure that cached values are not lost.
- We remove `check_system`. If this becomes an issue in the future we
should precompute the remaining amount of stack space, and use a cheaper
check.
- We add a `Expr.replaceImpl`, and will use it to implement
`Expr.replace` after update-stage0
2024-07-20 02:00:29 +00:00
Leonardo de Moura
de5e07c4d2 perf: find? and findExt? (#4795)
use the kernel implementation.
2024-07-20 01:13:54 +00:00
Lean stage0 autoupdater
327986e6fb chore: update stage0 2024-07-20 00:51:23 +00:00
Leonardo de Moura
6c33b9c57f perf: for_each with precise cache (#4794)
This commit also adds support for `find?` and `findExt?` using kernel
`for_each`.
We need to perform `update-stage0`.
2024-07-20 00:18:55 +00:00
Henrik Böving
d907771fdd feat: theory from LeanSAT (#4742)
Co-authored-by: Kim Morrison <scott.morrison@gmail.com>
2024-07-19 16:21:03 +00:00
Leonardo de Moura
5c3360200e fix: add term elaborator for Lean.Parser.Term.namedPattern (#4792)
closes #4662
2024-07-19 16:14:32 +00:00
Joachim Breitner
204d4839fa refactor: add numFixed to Structural.EqnInfo (#4788) 2024-07-19 10:21:43 +00:00
Joachim Breitner
e32f3e8140 refactor: IndGroupInst.brecOn (#4787)
this logic fits nicely within `IndGroupInst`.

Also makes `isAuxRecursorWithSuffix` recognize `brecOn_<n>`.
2024-07-19 10:20:50 +00:00
Sebastian Ullrich
7d2155943c doc: fix integer division example
Fixes #4785
2024-07-19 10:36:43 +02:00
Lean stage0 autoupdater
78c4d6daff chore: update stage0 2024-07-18 20:38:21 +00:00
Leonardo de Moura
5526ff6320 chore: Simp.Config.implicitDefEqProofs := true by default (#4784)
Motivation: unblock PR #4595
`Simp.Config.implicitDefEqProofs := false` is currently creating too
many issues in Mathlib.
2024-07-18 19:10:18 +00:00
Leonardo de Moura
bfca7ec72a fix: .eq_def theorem generation with messy universes (#4712)
closes #4673
2024-07-18 17:34:23 +00:00
Leonardo de Moura
9208b3585f chore: document replaceUnsafeM issue (#4783) 2024-07-18 16:26:20 +00:00
Leonardo de Moura
a94805ff71 perf: ensure Expr.replaceExpr preserve DAG structure in Exprs (#4779) 2024-07-18 02:24:15 +00:00
1351 changed files with 33590 additions and 7747 deletions

View File

@@ -204,7 +204,7 @@ jobs:
"os": "macos-14",
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
"release": true,
"check-level": 1,
"check-level": 0,
"shell": "bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
@@ -226,21 +226,19 @@ jobs:
},
{
"name": "Linux aarch64",
"os": "ubuntu-latest",
"os": "nscloud-ubuntu-22.04-arm64-4x8",
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-linux_aarch64",
"release": true,
"check-level": 2,
"cross": true,
"cross_target": "aarch64-unknown-linux-gnu",
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*"
},
{
"name": "Linux 32bit",
"os": "ubuntu-latest",
// Use 32bit on stage0 and stage1 to keep oleans compatible
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86",
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86 -DCMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/",
"cmultilib": true,
"release": true,
"check-level": 2,
@@ -251,7 +249,7 @@ jobs:
"name": "Web Assembly",
"os": "ubuntu-latest",
// Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32",
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32 -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/",
"wasm": true,
"cmultilib": true,
"release": true,
@@ -259,7 +257,7 @@ jobs:
"cross": true,
"shell": "bash -euxo pipefail {0}",
// Just a few selected tests because wasm is slow
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean\""
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean|leanruntest_libuv\\.lean\""
}
];
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`)
@@ -298,12 +296,12 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: clang64
# `:p` means prefix with appropriate msystem prefix
pacboy: "make python cmake:p clang:p ccache:p gmp:p git zip unzip diffutils binutils tree zstd:p tar"
# `:` means do not prefix with msystem
pacboy: "make: python: cmake clang ccache gmp libuv git: zip: unzip: diffutils: binutils: tree: zstd tar:"
if: runner.os == 'Windows'
- name: Install Brew Packages
run: |
brew install ccache tree zstd coreutils gmp
brew install ccache tree zstd coreutils gmp libuv
if: runner.os == 'macOS'
- name: Checkout
uses: actions/checkout@v4
@@ -327,17 +325,19 @@ jobs:
if: matrix.wasm
- name: Install 32bit c libs
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y gcc-multilib g++-multilib ccache
sudo apt-get install -y gcc-multilib g++-multilib ccache libuv1-dev:i386
if: matrix.cmultilib
- name: Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .ccache
key: ${{ matrix.name }}-build-v3-${{ github.event.pull_request.head.sha }}
# fall back to (latest) previous cache
restore-keys: |
${{ matrix.name }}-build-v3
save-always: true
# open nix-shell once for initial setup
- name: Setup
run: |
@@ -382,6 +382,12 @@ jobs:
make -C build install
- name: Check Binaries
run: ${{ matrix.binary-check }} lean-*/bin/* || true
- name: Count binary symbols
run: |
for f in lean-*/bin/*; do
echo "$f: $(nm $f | grep " T " | wc -l) exported symbols"
done
if: matrix.name == 'Windows'
- name: List Install Tree
run: |
# omit contents of Init/, ...
@@ -426,7 +432,7 @@ jobs:
if: matrix.test-speedcenter
- name: Check Stage 3
run: |
make -C build -j$NPROC stage3
make -C build -j$NPROC check-stage3
if: matrix.test-speedcenter
- name: Test Speedcenter Benchmarks
run: |
@@ -455,12 +461,24 @@ jobs:
# mark as merely cancelled not failed if builds are cancelled
if: ${{ !cancelled() }}
steps:
- if: ${{ contains(needs.*.result, 'failure') && github.repository == 'leanprover/lean4' && github.ref_name == 'master' }}
uses: zulip/github-actions-zulip/send-message@v1
with:
api-key: ${{ secrets.ZULIP_BOT_KEY }}
email: "github-actions-bot@lean-fro.zulipchat.com"
organization-url: "https://lean-fro.zulipchat.com"
to: "infrastructure"
topic: "Github actions"
type: "stream"
content: |
A build of `${{ github.ref_name }}`, triggered by event `${{ github.event_name }}`, [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).
- if: contains(needs.*.result, 'failure')
uses: actions/github-script@v7
with:
script: |
core.setFailed('Some jobs failed')
# This job creates releases from tags
# (whether they are "unofficial" releases for experiments, or official releases when the tag is "v" followed by a semver string.)
# We do not attempt to automatically construct a changelog here:
@@ -533,3 +551,8 @@ jobs:
gh workflow -R leanprover/release-index run update-index.yml
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
- name: Update toolchain on mathlib4's nightly-testing branch
run: |
gh workflow -R leanprover-community/mathlib4 run nightly_bump_toolchain.yml
env:
GITHUB_TOKEN: ${{ secrets.MATHLIB4_BOT }}

34
.github/workflows/jira.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Jira sync
on:
issues:
types: [closed]
jobs:
jira-sync:
runs-on: ubuntu-latest
steps:
- name: Move Jira issue to Done
env:
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }}
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
run: |
issue_number=${{ github.event.issue.number }}
jira_issue_key=$(curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
-X GET -H "Content-Type: application/json" \
"${JIRA_BASE_URL}/rest/api/2/search?jql=summary~\"${issue_number}\"" | \
jq -r '.issues[0].key')
if [ -z "$jira_issue_key" ]; then
exit
fi
curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
-X POST -H "Content-Type: application/json" \
--data "{\"transition\": {\"id\": \"41\"}}" \
"${JIRA_BASE_URL}/rest/api/2/issue/${jira_issue_key}/transitions"
echo "Moved Jira issue ${jira_issue_key} to Done"

View File

@@ -55,13 +55,14 @@ jobs:
# 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@v3
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: |
@@ -78,13 +79,14 @@ jobs:
sudo mkdir -m0770 -p /nix/var/cache/ccache
sudo chown -R $USER /nix/var/cache/ccache
- name: Setup CCache Cache
uses: actions/cache@v3
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
@@ -103,7 +105,7 @@ jobs:
continue-on-error: true
- name: Build manual
run: |
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,test,inked} -o push-doc
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,inked} -o push-doc
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc
# https://github.com/netlify/cli/issues/1809
cp -r --dereference ./result ./dist
@@ -146,5 +148,3 @@ jobs:
- name: Fixup CCache Cache
run: |
sudo chown -R $USER /nix/var/cache
- name: CCache stats
run: CCACHE_DIR=/nix/var/cache/ccache nix run .#nixpkgs.ccache -- -s

View File

@@ -163,7 +163,8 @@ jobs:
# so keep in sync
# Use GitHub API to check if a comment already exists
existing_comment="$(curl -L -s -H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
existing_comment="$(curl --retry 3 --location --silent \
-H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "leanprover-community-mathlib4-bot"))')"
@@ -328,7 +329,7 @@ jobs:
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
git add lean-toolchain
sed -i 's,require "leanprover-community" / "batteries" @ ".\+",require "leanprover-community" / "batteries" @ "git#nightly-testing-'"${MOST_RECENT_NIGHTLY}"'",' lakefile.lean
sed -i 's,require "leanprover-community" / "batteries" @ git ".\+",require "leanprover-community" / "batteries" @ git "nightly-testing-'"${MOST_RECENT_NIGHTLY}"'",' lakefile.lean
lake update batteries
git add lakefile.lean lake-manifest.json
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"

View File

@@ -47,7 +47,7 @@ jobs:
# uses: DeterminateSystems/magic-nix-cache-action@v2
- if: env.should_update_stage0 == 'yes'
name: Restore Build Cache
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: nix-store-cache
key: Nix Linux-nix-store-cache-${{ github.sha }}

View File

@@ -63,6 +63,20 @@ Because the change will be squashed, there is no need to polish the commit messa
Reviews and Feedback:
----
The lean4 repo is managed by the Lean FRO's *triage team* that aims to provide initial feedback on new bug reports, PRs, and RFCs weekly.
This feedback generally consists of prioritizing the ticket using one of the following categories:
* label `P-high`: We will work on this issue
* label `P-medium`: We may work on this issue if we find the time
* label `P-low`: We are not planning to work on this issue
* *closed*: This issue is already fixed, it is not an issue, or is not sufficiently compatible with our roadmap for the project and we will not work on it nor accept external contributions on it
For *bug reports*, the listed priority reflects our commitment to fixing the issue.
It is generally indicative but not necessarily identical to the priority an external contribution addressing this bug would receive.
For *PRs* and *RFCs*, the priority reflects our commitment to reviewing them and getting them to an acceptable state.
Accepted RFCs are marked with the label `RFC accepted` and afterwards assigned a new "implementation" priority as with bug reports.
General guidelines for interacting with reviews and feedback:
**Be Patient**: Given the limited number of full-time maintainers and the volume of PRs, reviews may take some time.
**Engage Constructively**: Always approach feedback positively and constructively. Remember, reviews are about ensuring the best quality for the project, not personal criticism.

View File

@@ -8,16 +8,299 @@ This file contains work-in-progress notes for the upcoming release, as well as p
Please check the [releases](https://github.com/leanprover/lean4/releases) page for the current status
of each version.
v4.11.0
v4.12.0
----------
Development in progress.
v4.11.0
----------
Release candidate, release notes will be copied from the branch `releases/v4.11.0` once completed.
v4.10.0
----------
Release candidate, release notes will be copied from branch `releases/v4.10.0` once completed.
### Language features, tactics, and metaprograms
* `split` tactic:
* [#4401](https://github.com/leanprover/lean4/pull/4401) improves the strategy `split` uses to generalize discriminants of matches and adds `trace.split.failure` trace class for diagnosing issues.
* `rw` tactic:
* [#4385](https://github.com/leanprover/lean4/pull/4385) prevents the tactic from claiming pre-existing goals are new subgoals.
* [dac1da](https://github.com/leanprover/lean4/commit/dac1dacc5b39911827af68247d575569d9c399b5) adds configuration for ordering new goals, like for `apply`.
* `simp` tactic:
* [#4430](https://github.com/leanprover/lean4/pull/4430) adds `dsimproc`s for `if` expressions (`ite` and `dite`).
* [#4434](https://github.com/leanprover/lean4/pull/4434) improves heuristics for unfolding. Equational lemmas now have priorities where more-specific equationals lemmas are tried first before a possible catch-all.
* [#4481](https://github.com/leanprover/lean4/pull/4481) fixes an issue where function-valued `OfNat` numeric literals would become denormalized.
* [#4467](https://github.com/leanprover/lean4/pull/4467) fixes an issue where dsimp theorems might not apply to literals.
* [#4484](https://github.com/leanprover/lean4/pull/4484) fixes the source position for the warning for deprecated simp arguments.
* [#4258](https://github.com/leanprover/lean4/pull/4258) adds docstrings for `dsimp` configuration.
* [#4567](https://github.com/leanprover/lean4/pull/4567) improves the accuracy of used simp lemmas reported by `simp?`.
* [fb9727](https://github.com/leanprover/lean4/commit/fb97275dcbb683efe6da87ed10a3f0cd064b88fd) adds (but does not implement) the simp configuration option `implicitDefEqProofs`, which will enable including `rfl`-theorems in proof terms.
* `omega` tactic:
* [#4360](https://github.com/leanprover/lean4/pull/4360) makes the tactic generate error messages lazily, improving its performance when used in tactic combinators.
* `bv_omega` tactic:
* [#4579](https://github.com/leanprover/lean4/pull/4579) works around changes to the definition of `Fin.sub` in this release.
* [#4490](https://github.com/leanprover/lean4/pull/4490) sets up groundwork for a tactic index in generated documentation, as there was in Lean 3. See PR description for details.
* **Commands**
* [#4370](https://github.com/leanprover/lean4/pull/4370) makes the `variable` command fully elaborate binders during validation, fixing an issue where some errors would be reported only at the next declaration.
* [#4408](https://github.com/leanprover/lean4/pull/4408) fixes a discrepency in universe parameter order between `theorem` and `def` declarations.
* [#4493](https://github.com/leanprover/lean4/pull/4493) and
[#4482](https://github.com/leanprover/lean4/pull/4482) fix a discrepancy in the elaborators for `theorem`, `def`, and `example`,
making `Prop`-valued `example`s and other definition commands elaborate like `theorem`s.
* [8f023b](https://github.com/leanprover/lean4/commit/8f023b85c554186ae562774b8122322d856c674e), [3c4d6b](https://github.com/leanprover/lean4/commit/3c4d6ba8648eb04d90371eb3fdbd114d16949501) and [0783d0](https://github.com/leanprover/lean4/commit/0783d0fcbe31b626fbd3ed2f29d838e717f09101) change the `#reduce` command to be able to control what gets reduced.
For example, `#reduce (proofs := true) (types := false) e` reduces both proofs and types in the expression `e`.
By default, neither proofs or types are reduced.
* [#4489](https://github.com/leanprover/lean4/pull/4489) fixes an elaboration bug in `#check_tactic`.
* [#4505](https://github.com/leanprover/lean4/pull/4505) adds support for `open _root_.<namespace>`.
* **Options**
* [#4576](https://github.com/leanprover/lean4/pull/4576) adds the `debug.byAsSorry` option. Setting `set_option debug.byAsSorry true` causes all `by ...` terms to elaborate as `sorry`.
* [7b56eb](https://github.com/leanprover/lean4/commit/7b56eb20a03250472f4b145118ae885274d1f8f7) and [d8e719](https://github.com/leanprover/lean4/commit/d8e719f9ab7d049e423473dfc7a32867d32c856f) add the `debug.skipKernelTC` option. Setting `set_option debug.skipKernelTC true` turns off kernel typechecking. This is meant for temporarily working around kernel performance issues, and it compromises soundness since buggy tactics may produce invalid proofs, which will not be caught if this option is set to true.
* [#4301](https://github.com/leanprover/lean4/pull/4301)
adds a linter to flag situations where a local variable's name is one of
the argumentless constructors of its type. This can arise when a user either
doesn't open a namespace or doesn't add a dot or leading qualifier, as
in the following:
```lean
inductive Tree (α : Type) where
| leaf
| branch (left : Tree α) (val : α) (right : Tree α)
def depth : Tree α → Nat
| leaf => 0
```
With this linter, the `leaf` pattern is highlighted as a local
variable whose name overlaps with the constructor `Tree.leaf`.
The linter can be disabled with `set_option linter.constructorNameAsVariable false`.
Additionally, the error message that occurs when a name in a pattern that takes arguments isn't valid now suggests similar names that would be valid. This means that the following definition:
```lean
def length (list : List α) : Nat :=
match list with
| nil => 0
| cons x xs => length xs + 1
```
now results in the following warning:
```
warning: Local variable 'nil' resembles constructor 'List.nil' - write '.nil' (with a dot) or 'List.nil' to use the constructor.
note: this linter can be disabled with `set_option linter.constructorNameAsVariable false`
```
and error:
```
invalid pattern, constructor or constant marked with '[match_pattern]' expected
Suggestion: 'List.cons' is similar
```
* **Metaprogramming**
* [#4454](https://github.com/leanprover/lean4/pull/4454) adds public `Name.isInternalDetail` function for filtering declarations using naming conventions for internal names.
* **Other fixes or improvements**
* [#4416](https://github.com/leanprover/lean4/pull/4416) sorts the ouput of `#print axioms` for determinism.
* [#4528](https://github.com/leanprover/lean4/pull/4528) fixes error message range for the cdot focusing tactic.
### Language server, widgets, and IDE extensions
* [#4443](https://github.com/leanprover/lean4/pull/4443) makes the watchdog be more resilient against badly behaving clients.
### Pretty printing
* [#4433](https://github.com/leanprover/lean4/pull/4433) restores fallback pretty printers when context is not available, and documents `addMessageContext`.
* [#4556](https://github.com/leanprover/lean4/pull/4556) introduces `pp.maxSteps` option and sets the default value of `pp.deepTerms` to `false`. Together, these keep excessively large or deep terms from overwhelming the Infoview.
### Library
* [#4560](https://github.com/leanprover/lean4/pull/4560) splits `GetElem` class into `GetElem` and `GetElem?`.
This enables removing `Decidable` instance arguments from `GetElem.getElem?` and `GetElem.getElem!`, improving their rewritability.
See the docstrings for these classes for more information.
* `Array`
* [#4389](https://github.com/leanprover/lean4/pull/4389) makes `Array.toArrayAux_eq` be a `simp` lemma.
* [#4399](https://github.com/leanprover/lean4/pull/4399) improves robustness of the proof for `Array.reverse_data`.
* `List`
* [#4469](https://github.com/leanprover/lean4/pull/4469) and [#4475](https://github.com/leanprover/lean4/pull/4475) improve the organization of the `List` API.
* [#4470](https://github.com/leanprover/lean4/pull/4470) improves the `List.set` and `List.concat` API.
* [#4472](https://github.com/leanprover/lean4/pull/4472) upstreams lemmas about `List.filter` from Batteries.
* [#4473](https://github.com/leanprover/lean4/pull/4473) adjusts `@[simp]` attributes.
* [#4488](https://github.com/leanprover/lean4/pull/4488) makes `List.getElem?_eq_getElem` be a simp lemma.
* [#4487](https://github.com/leanprover/lean4/pull/4487) adds missing `List.replicate` API.
* [#4521](https://github.com/leanprover/lean4/pull/4521) adds lemmas about `List.map`.
* [#4500](https://github.com/leanprover/lean4/pull/4500) changes `List.length_cons` to use `as.length + 1` instead of `as.length.succ`.
* [#4524](https://github.com/leanprover/lean4/pull/4524) fixes the statement of `List.filter_congr`.
* [#4525](https://github.com/leanprover/lean4/pull/4525) changes binder explicitness in `List.bind_map`.
* [#4550](https://github.com/leanprover/lean4/pull/4550) adds `maximum?_eq_some_iff'` and `minimum?_eq_some_iff?`.
* [#4400](https://github.com/leanprover/lean4/pull/4400) switches the normal forms for indexing `List` and `Array` to `xs[n]` and `xs[n]?`.
* `HashMap`
* [#4372](https://github.com/leanprover/lean4/pull/4372) fixes linearity in `HashMap.insert` and `HashMap.erase`, leading to a 40% speedup in a replace-heavy workload.
* `Option`
* [#4403](https://github.com/leanprover/lean4/pull/4403) generalizes type of `Option.forM` from `Unit` to `PUnit`.
* [#4504](https://github.com/leanprover/lean4/pull/4504) remove simp attribute from `Option.elim` and instead adds it to individal reduction lemmas, making unfolding less aggressive.
* `Nat`
* [#4242](https://github.com/leanprover/lean4/pull/4242) adds missing theorems for `n + 1` and `n - 1` normal forms.
* [#4486](https://github.com/leanprover/lean4/pull/4486) makes `Nat.min_assoc` be a simp lemma.
* [#4522](https://github.com/leanprover/lean4/pull/4522) moves `@[simp]` from `Nat.pred_le` to `Nat.sub_one_le`.
* [#4532](https://github.com/leanprover/lean4/pull/4532) changes various `Nat.succ n` to `n + 1`.
* `Int`
* [#3850](https://github.com/leanprover/lean4/pull/3850) adds complete div/mod simprocs for `Int`.
* `String`/`Char`
* [#4357](https://github.com/leanprover/lean4/pull/4357) make the byte size interface be `Nat`-valued with functions `Char.utf8Size` and `String.utf8ByteSize`.
* [#4438](https://github.com/leanprover/lean4/pull/4438) upstreams `Char.ext` from Batteries and adds some `Char` documentation to the manual.
* `Fin`
* [#4421](https://github.com/leanprover/lean4/pull/4421) adjusts `Fin.sub` to be more performant in definitional equality checks.
* `Prod`
* [#4526](https://github.com/leanprover/lean4/pull/4526) adds missing `Prod.map` lemmas.
* [#4533](https://github.com/leanprover/lean4/pull/4533) fixes binder explicitness in lemmas.
* `BitVec`
* [#4428](https://github.com/leanprover/lean4/pull/4428) adds missing `simproc` for `BitVec` equality.
* [#4417](https://github.com/leanprover/lean4/pull/4417) adds `BitVec.twoPow` and lemmas, toward bitblasting multiplication for LeanSAT.
* `Std` library
* [#4499](https://github.com/leanprover/lean4/pull/4499) introduces `Std`, a library situated between `Init` and `Lean`, providing functionality not in the prelude both to Lean's implementation and to external users.
* **Other fixes or improvements**
* [#3056](https://github.com/leanprover/lean4/pull/3056) standardizes on using `(· == a)` over `(a == ·)`.
* [#4502](https://github.com/leanprover/lean4/pull/4502) fixes errors reported by running the library through the the Batteries linters.
### Lean internals
* [#4391](https://github.com/leanprover/lean4/pull/4391) makes `getBitVecValue?` recognize `BitVec.ofNatLt`.
* [#4410](https://github.com/leanprover/lean4/pull/4410) adjusts `instantiateMVars` algorithm to zeta reduce `let` expressions while beta reducing instantiated metavariables.
* [#4420](https://github.com/leanprover/lean4/pull/4420) fixes occurs check for metavariable assignments to also take metavariable types into account.
* [#4425](https://github.com/leanprover/lean4/pull/4425) fixes `forEachModuleInDir` to iterate over each Lean file exactly once.
* [#3886](https://github.com/leanprover/lean4/pull/3886) adds support to build Lean core oleans using Lake.
* **Defeq and WHNF algorithms**
* [#4387](https://github.com/leanprover/lean4/pull/4387) improves performance of `isDefEq` by eta reducing lambda-abstracted terms during metavariable assignments, since these are beta reduced during metavariable instantiation anyway.
* [#4388](https://github.com/leanprover/lean4/pull/4388) removes redundant code in `isDefEqQuickOther`.
* **Typeclass inference**
* [#4530](https://github.com/leanprover/lean4/pull/4530) fixes handling of metavariables when caching results at `synthInstance?`.
* **Elaboration**
* [#4426](https://github.com/leanprover/lean4/pull/4426) makes feature where the "don't know how to synthesize implicit argument" error reports the name of the argument more reliable.
* [#4497](https://github.com/leanprover/lean4/pull/4497) fixes a name resolution bug for generalized field notation (dot notation).
* [#4536](https://github.com/leanprover/lean4/pull/4536) blocks the implicit lambda feature for `(e :)` notation.
* [#4562](https://github.com/leanprover/lean4/pull/4562) makes it be an error for there to be two functions with the same name in a `where`/`let rec` block.
* Recursion principles
* [#4549](https://github.com/leanprover/lean4/pull/4549) refactors `findRecArg`, extracting `withRecArgInfo`.
Errors are now reported in parameter order rather than the order they are tried (non-indices are tried first).
For every argument, it will say why it wasn't tried, even if the reason is obvious (e.g. a fixed prefix or is `Prop`-typed, etc.).
* Porting core C++ to Lean
* [#4474](https://github.com/leanprover/lean4/pull/4474) takes a step to refactor `constructions` toward a future port to Lean.
* [#4498](https://github.com/leanprover/lean4/pull/4498) ports `mk_definition_inferring_unsafe` to Lean.
* [#4516](https://github.com/leanprover/lean4/pull/4516) ports `recOn` construction to Lean.
* [#4517](https://github.com/leanprover/lean4/pull/4517), [#4653](https://github.com/leanprover/lean4/pull/4653), and [#4651](https://github.com/leanprover/lean4/pull/4651) port `below` and `brecOn` construction to Lean.
* Documentation
* [#4501](https://github.com/leanprover/lean4/pull/4501) adds a more-detailed docstring for `PersistentEnvExtension`.
* **Other fixes or improvements**
* [#4382](https://github.com/leanprover/lean4/pull/4382) removes `@[inline]` attribute from `NameMap.find?`, which caused respecialization at each call site.
* [5f9ded](https://github.com/leanprover/lean4/commit/5f9dedfe5ee9972acdebd669f228f487844a6156) improves output of `trace.Elab.snapshotTree`.
* [#4424](https://github.com/leanprover/lean4/pull/4424) removes "you might need to open '{dir}' in your editor" message that is now handled by Lake and the VS Code extension.
* [#4451](https://github.com/leanprover/lean4/pull/4451) improves the performance of `CollectMVars` and `FindMVar`.
* [#4479](https://github.com/leanprover/lean4/pull/4479) adds missing `DecidableEq` and `Repr` instances for intermediate structures used by the `BitVec` and `Fin` simprocs.
* [#4492](https://github.com/leanprover/lean4/pull/4492) adds tests for a previous `isDefEq` issue.
* [9096d6](https://github.com/leanprover/lean4/commit/9096d6fc7180fe533c504f662bcb61550e4a2492) removes `PersistentHashMap.size`.
* [#4508](https://github.com/leanprover/lean4/pull/4508) fixes `@[implemented_by]` for functions defined by well-founded recursion.
* [#4509](https://github.com/leanprover/lean4/pull/4509) adds additional tests for `apply?` tactic.
* [d6eab3](https://github.com/leanprover/lean4/commit/d6eab393f4df9d473b5736d636b178eb26d197e6) fixes a benchmark.
* [#4563](https://github.com/leanprover/lean4/pull/4563) adds a workaround for a bug in `IndPredBelow.mkBelowMatcher`.
* **Cleanup:** [#4380](https://github.com/leanprover/lean4/pull/4380), [#4431](https://github.com/leanprover/lean4/pull/4431), [#4494](https://github.com/leanprover/lean4/pull/4494), [e8f768](https://github.com/leanprover/lean4/commit/e8f768f9fd8cefc758533bc76e3a12b398ed4a39), [de2690](https://github.com/leanprover/lean4/commit/de269060d17a581ed87f40378dbec74032633b27), [d3a756](https://github.com/leanprover/lean4/commit/d3a7569c97123d022828106468d54e9224ed8207), [#4404](https://github.com/leanprover/lean4/pull/4404), [#4537](https://github.com/leanprover/lean4/pull/4537).
### Compiler, runtime, and FFI
* [d85d3d](https://github.com/leanprover/lean4/commit/d85d3d5f3a09ff95b2ee47c6f89ef50b7e339126) fixes criterion for tail-calls in ownership calculation.
* [#3963](https://github.com/leanprover/lean4/pull/3963) adds validation of UTF-8 at the C++-to-Lean boundary in the runtime.
* [#4512](https://github.com/leanprover/lean4/pull/4512) fixes missing unboxing in interpreter when loading initialized value.
* [#4477](https://github.com/leanprover/lean4/pull/4477) exposes the compiler flags for the bundled C compiler (clang).
### Lake
* [#4384](https://github.com/leanprover/lean4/pull/4384) deprecates `inputFile` and replaces it with `inputBinFile` and `inputTextFile`. Unlike `inputBinFile` (and `inputFile`), `inputTextFile` normalizes line endings, which helps ensure text file traces are platform-independent.
* [#4371](https://github.com/leanprover/lean4/pull/4371) simplifies dependency resolution code.
* [#4439](https://github.com/leanprover/lean4/pull/4439) touches up the Lake configuration DSL and makes other improvements:
string literals can now be used instead of identifiers for names,
avoids using French quotes in `lake new` and `lake init` templates,
changes the `exe` template to use `Main` for the main module,
improves the `math` template error if `lean-toolchain` fails to download,
and downgrades unknown configuration fields from an error to a warning to improve cross-version compatibility.
* [#4496](https://github.com/leanprover/lean4/pull/4496) tweaks `require` syntax and updates docs. Now `require` in TOML for a package name such as `doc-gen4` does not need French quotes.
* [#4485](https://github.com/leanprover/lean4/pull/4485) fixes a bug where package versions in indirect dependencies would take precedence over direct dependencies.
* [#4478](https://github.com/leanprover/lean4/pull/4478) fixes a bug where Lake incorrectly included the module dynamic library in a platform-independent trace.
* [#4529](https://github.com/leanprover/lean4/pull/4529) fixes some issues with bad import errors.
A bad import in an executable no longer prevents the executable's root
module from being built. This also fixes a problem where the location
of a transitive bad import would not been shown.
The root module of the executable now respects `nativeFacets`.
* [#4564](https://github.com/leanprover/lean4/pull/4564) fixes a bug where non-identifier script names could not be entered on the CLI without French quotes.
* [#4566](https://github.com/leanprover/lean4/pull/4566) addresses a few issues with precompiled libraries.
* Fixes a bug where Lake would always precompile the package of a module.
* If a module is precompiled, it now precompiles its imports. Previously, it would only do this if imported.
* [#4495](https://github.com/leanprover/lean4/pull/4495), [#4692](https://github.com/leanprover/lean4/pull/4692), [#4849](https://github.com/leanprover/lean4/pull/4849)
add a new type of `require` that fetches package metadata from a
registry API endpoint (e.g. Reservoir) and then clones a Git package
using the information provided. To require such a dependency, the new
syntax is:
```lean
require <scope> / <pkg-name> [@ git <rev>]
-- Examples:
require "leanprover" / "doc-gen4"
require "leanprover-community" / "proofwidgets" @ git "v0.0.39"
```
Or in TOML:
```toml
[[require]]
name = "<pkg-name>"
scope = "<scope>"
rev = "<rev>"
```
Unlike with Git dependencies, Lake can make use of the richer
information provided by the registry to determine the default branch of
the package. This means for repositories of packages like `doc-gen4`
which have a default branch that is not `master`, Lake will now use said
default branch (e.g., in `doc-gen4`'s case, `main`).
Lake also supports configuring the registry endpoint via an environment
variable: `RESERVIOR_API_URL`. Thus, any server providing a similar
interface to Reservoir can be used as the registry. Further
configuration options paralleling those of Cargo's [Alternative Registries](https://doc.rust-lang.org/cargo/reference/registries.html)
and [Source Replacement](https://doc.rust-lang.org/cargo/reference/source-replacement.html)
will come in the future.
### DevOps/CI
* [#4427](https://github.com/leanprover/lean4/pull/4427) uses Namespace runners for CI for `leanprover/lean4`.
* [#4440](https://github.com/leanprover/lean4/pull/4440) fixes speedcenter tests in CI.
* [#4441](https://github.com/leanprover/lean4/pull/4441) fixes that workflow change would break CI for unrebased PRs.
* [#4442](https://github.com/leanprover/lean4/pull/4442) fixes Wasm release-ci.
* [6d265b](https://github.com/leanprover/lean4/commit/6d265b42b117eef78089f479790587a399da7690) fixes for `github.event.pull_request.merge_commit_sha` sometimes not being available.
* [16cad2](https://github.com/leanprover/lean4/commit/16cad2b45c6a77efe4dce850dcdbaafaa7c91fc3) adds optimization for CI to not fetch complete history.
* [#4544](https://github.com/leanprover/lean4/pull/4544) causes releases to be marked as prerelease on GitHub.
* [#4446](https://github.com/leanprover/lean4/pull/4446) switches Lake to using `src/lake/lakefile.toml` to avoid needing to load a version of Lake to build Lake.
* Nix
* [5eb5fa](https://github.com/leanprover/lean4/commit/5eb5fa49cf9862e99a5bccff8d4ca1a062f81900) fixes `update-stage0-commit` for Nix.
* [#4476](https://github.com/leanprover/lean4/pull/4476) adds gdb to Nix shell.
* [e665a0](https://github.com/leanprover/lean4/commit/e665a0d716dc42ba79b339b95e01eb99fe932cb3) fixes `update-stage0` for Nix.
* [4808eb](https://github.com/leanprover/lean4/commit/4808eb7c4bfb98f212b865f06a97d46c44978a61) fixes `cacheRoots` for Nix.
* [#3811](https://github.com/leanprover/lean4/pull/3811) adds platform-dependent flag to lib target.
* [#4587](https://github.com/leanprover/lean4/pull/4587) adds linking of `-lStd` back into nix build flags on darwin.
### Breaking changes
* `Char.csize` is replaced by `Char.utf8Size` ([#4357](https://github.com/leanprover/lean4/pull/4357)).
* Library lemmas now are in terms of `(· == a)` over `(a == ·)` ([#3056](https://github.com/leanprover/lean4/pull/3056)).
* Now the normal forms for indexing into `List` and `Array` is `xs[n]` and `xs[n]?` rather than using functions like `List.get` ([#4400](https://github.com/leanprover/lean4/pull/4400)).
* Sometimes terms created via a sequence of unifications will be more eta reduced than before and proofs will require adaptation ([#4387](https://github.com/leanprover/lean4/pull/4387)).
* The `GetElem` class has been split into two; see the docstrings for `GetElem` and `GetElem?` for more information ([#4560](https://github.com/leanprover/lean4/pull/4560)).
v4.9.0
----------
----------
### Language features, tactics, and metaprograms
@@ -40,6 +323,8 @@ v4.9.0
* [#4395](https://github.com/leanprover/lean4/pull/4395) adds conservative fix for whitespace handling to avoid incremental reuse leading to goals in front of the text cursor being shown.
* [#4407](https://github.com/leanprover/lean4/pull/4407) fixes non-incremental commands in macros blocking further incremental reporting.
* [#4436](https://github.com/leanprover/lean4/pull/4436) fixes incremental reporting when there are nested tactics in terms.
* [#4459](https://github.com/leanprover/lean4/pull/4459) adds incrementality support for `next` and `if` tactics.
* [#4554](https://github.com/leanprover/lean4/pull/4554) disables incrementality for tactics in terms in tactics.
* **Functional induction**
* [#4135](https://github.com/leanprover/lean4/pull/4135) ensures that the names used for functional induction are reserved.
* [#4327](https://github.com/leanprover/lean4/pull/4327) adds support for structural recursion on reflexive types.
@@ -85,7 +370,7 @@ v4.9.0
When `index := false`, only the head function is taken into account, like in Lean 3.
This feature can help users diagnose tricky simp failures or issues in code from libraries
developed using Lean 3 and then ported to Lean 4.
In the following example, it will report that `foo` is a problematic theorem.
```lean
opaque f : Nat → Nat → Nat
@@ -105,7 +390,7 @@ v4.9.0
opaque f : Nat → Nat → Nat
@[simp] theorem foo : f x (no_index (x, y).2) = y := by sorry
example : f a b ≤ b := by
simp -- `foo` is still applied with `index := true`
```
@@ -123,6 +408,8 @@ v4.9.0
* [#4267](https://github.com/leanprover/lean4/pull/4267) cases signature elaboration errors to show even if there are parse errors in the body.
* [#4368](https://github.com/leanprover/lean4/pull/4368) improves error messages when numeric literals fail to synthesize an `OfNat` instance,
including special messages warning when the expected type of the numeral can be a proposition.
* [#4643](https://github.com/leanprover/lean4/pull/4643) fixes issue leading to nested error messages and info trees vanishing, where snapshot subtrees were not restored on reuse.
* [#4657](https://github.com/leanprover/lean4/pull/4657) calculates error suppression per snapshot, letting elaboration errors appear even when there are later parse errors ([RFC #3556](https://github.com/leanprover/lean4/issues/3556)).
* **Metaprogramming**
* [#4167](https://github.com/leanprover/lean4/pull/4167) adds `Lean.MVarId.revertAll` to revert all free variables.
* [#4169](https://github.com/leanprover/lean4/pull/4169) adds `Lean.MVarId.ensureNoMVar` to ensure the goal's target contains no expression metavariables.
@@ -239,6 +526,8 @@ v4.9.0
* [#4192](https://github.com/leanprover/lean4/pull/4192) fixes restoration of infotrees when auto-bound implicit feature is activated,
fixing a pretty printing error in hovers and strengthening the unused variable linter.
* [dfb496](https://github.com/leanprover/lean4/commit/dfb496a27123c3864571aec72f6278e2dad1cecf) fixes `declareBuiltin` to allow it to be called multiple times per declaration.
* [#4569](https://github.com/leanprover/lean4/pull/4569) fixes an issue introduced in a merge conflict, where the interrupt exception was swallowed by some `tryCatchRuntimeEx` uses.
* [b056a0](https://github.com/leanprover/lean4/commit/b056a0b395bb728512a3f3e83bf9a093059d4301) adapts kernel interruption to the new cancellation system.
* Cleanup: [#4112](https://github.com/leanprover/lean4/pull/4112), [#4126](https://github.com/leanprover/lean4/pull/4126), [#4091](https://github.com/leanprover/lean4/pull/4091), [#4139](https://github.com/leanprover/lean4/pull/4139), [#4153](https://github.com/leanprover/lean4/pull/4153).
* Tests: [030406](https://github.com/leanprover/lean4/commit/03040618b8f9b35b7b757858483e57340900cdc4), [#4133](https://github.com/leanprover/lean4/pull/4133).
@@ -249,6 +538,7 @@ v4.9.0
* [#3915](https://github.com/leanprover/lean4/pull/3915) documents the runtime memory layout for inductive types.
### Lake
* [#4518](https://github.com/leanprover/lean4/pull/4518) makes trace reading more robust. Lake now rebuilds if trace files are invalid or unreadable and is backwards compatible with previous pure numeric traces.
* [#4057](https://github.com/leanprover/lean4/pull/4057) adds support for docstrings on `require` commands.
* [#4088](https://github.com/leanprover/lean4/pull/4088) improves hovers for `family_def` and `library_data` commands.
* [#4147](https://github.com/leanprover/lean4/pull/4147) adds default `README.md` to package templates
@@ -298,12 +588,14 @@ v4.9.0
* [#4333](https://github.com/leanprover/lean4/pull/4333) adjusts workflow to update Batteries in manifest when creating `lean-pr-testing-NNNN` Mathlib branches.
* [#4355](https://github.com/leanprover/lean4/pull/4355) simplifies `lean4checker` step of release checklist.
* [#4361](https://github.com/leanprover/lean4/pull/4361) adds installing elan to `pr-release` CI step.
* [#4628](https://github.com/leanprover/lean4/pull/4628) fixes the Windows build, which was missing an exported symbol.
### Breaking changes
While most changes could be considered to be a breaking change, this section makes special note of API changes.
* `Nat.zero_or` and `Nat.or_zero` have been swapped ([#4094](https://github.com/leanprover/lean4/pull/4094)).
* `IsLawfulSingleton` is now `LawfulSingleton` ([#4350](https://github.com/leanprover/lean4/pull/4350)).
* The `BitVec` literal notation is now `<num>#<term>` rather than `<term>#<term>`, and it is global rather than scoped. Use `BitVec.ofNat w x` rather than `x#w` when `x` is a not a numeric literal ([0d3051](https://github.com/leanprover/lean4/commit/0d30517dca094a07bcb462252f718e713b93ffba)).
* `BitVec.rotateLeft` and `BitVec.rotateRight` now take the shift modulo the bitwidth ([#4229](https://github.com/leanprover/lean4/pull/4229)).
* These are no longer simp lemmas:
`List.length_pos` ([#4172](https://github.com/leanprover/lean4/pull/4172)),

View File

@@ -5,7 +5,7 @@ Some notes on how to debug Lean, which may also be applicable to debugging Lean
## Tracing
In `CoreM` and derived monads, we use `trace![traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
In `CoreM` and derived monads, we use `trace[traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
New trace classes have to be registered using `registerTraceClass` first.
Notable trace classes:
@@ -22,7 +22,9 @@ Notable trace classes:
In pure contexts or when execution is aborted before the messages are finally printed, one can instead use the term `dbg_trace "msg with {interpolations}"; val` (`;` can also be replaced by a newline), which will print the message to stderr before evaluating `val`. `dbgTraceVal val` can be used as a shorthand for `dbg_trace "{val}"; val`.
Note that if the return value is not actually used, the trace code is silently dropped as well.
In the language server, stderr output is buffered and shown as messages after a command has been elaborated, unless the option `server.stderrAsMessages` is deactivated.
By default, such stderr output is buffered and shown as messages after a command has been elaborated, which is necessary to ensure deterministic ordering of messages under parallelism.
If Lean aborts the process before it can finish the command or takes too long to do that, using `-DstderrAsMessages=false` avoids this buffering and shows `dbg_trace` output (but not `trace`s or other diagnostics) immediately.
## Debuggers

View File

@@ -5,8 +5,11 @@ See below for the checklist for release candidates.
We'll use `v4.6.0` as the intended release version as a running example.
- One week before the planned release, ensure that (1) someone has written the release notes and (2) someone has written the first draft of the release blog post.
If there is any material in `./releases_drafts/`, then the release notes are not done. (See the section "Writing the release notes".)
- One week before the planned release, ensure that
(1) someone has written the release notes and
(2) someone has written the first draft of the release blog post.
If there is any material in `./releases_drafts/` on the `releases/v4.6.0` branch, then the release notes are not done.
(See the section "Writing the release notes".)
- `git checkout releases/v4.6.0`
(This branch should already exist, from the release candidates.)
- `git pull`
@@ -144,33 +147,31 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`, looking for the `v4.7.0-rc1` tag.
- This step can take up to an hour.
- (GitHub release notes) Once the release appears at https://github.com/leanprover/lean4/releases/
- Edit the release notes on Github to select the "Set as a pre-release box".
- If release notes have been written already, copy the section of `RELEASES.md` for this version into the Github release notes
and use the title "Changes since v4.6.0 (from RELEASES.md)".
- Otherwise, in the "previous tag" dropdown, select `v4.6.0`, and click "Generate release notes".
- Verify that the release is marked as a prerelease (this should have been done automatically by the CI release job).
- In the "previous tag" dropdown, select `v4.6.0`, and click "Generate release notes".
This will add a list of all the commits since the last stable version.
- Delete anything already mentioned in the hand-written release notes above.
- Delete "update stage0" commits, and anything with a completely inscrutable commit message.
- Briefly rearrange the remaining items by category (e.g. `simp`, `lake`, `bug fixes`),
but for minor items don't put any work in expanding on commit messages.
- (How we want to release notes to look is evolving: please update this section if it looks wrong!)
- Next, we will move a curated list of downstream repos to the release candidate.
- This assumes that there is already a *reviewed* branch `bump/v4.7.0` on each repository
containing the required adaptations (or no adaptations are required).
The preparation of this branch is beyond the scope of this document.
- This assumes that for each repository either:
* There is already a *reviewed* branch `bump/v4.7.0` containing the required adaptations.
The preparation of this branch is beyond the scope of this document.
* The repository does not need any changes to move to the new version.
- For each of the target repositories:
- Checkout the `bump/v4.7.0` branch.
- Verify that the `lean-toolchain` is set to the nightly from which the release candidate was created.
- `git merge origin/master`
- Change the `lean-toolchain` to `leanprover/lean4:v4.7.0-rc1`
- In `lakefile.lean`, change any dependencies which were using `nightly-testing` or `bump/v4.7.0` branches
back to `master` or `main`, and run `lake update` for those dependencies.
- Run `lake build` to ensure that dependencies are found (but it's okay to stop it after a moment).
- `git commit`
- `git push`
- Open a PR from `bump/v4.7.0` to `master`, and either merge it yourself after CI, if appropriate,
or notify the maintainers that it is ready to go.
- Once this PR has been merged, tag `master` with `v4.7.0-rc1` and push this tag.
- If the repository does not need any changes (i.e. `bump/v4.7.0` does not exist) then create
a new PR updating `lean-toolchain` to `leanprover/lean4:v4.7.0-rc1` and running `lake update`.
- Otherwise:
- Checkout the `bump/v4.7.0` branch.
- Verify that the `lean-toolchain` is set to the nightly from which the release candidate was created.
- `git merge origin/master`
- Change the `lean-toolchain` to `leanprover/lean4:v4.7.0-rc1`
- In `lakefile.lean`, change any dependencies which were using `nightly-testing` or `bump/v4.7.0` branches
back to `master` or `main`, and run `lake update` for those dependencies.
- Run `lake build` to ensure that dependencies are found (but it's okay to stop it after a moment).
- `git commit`
- `git push`
- Open a PR from `bump/v4.7.0` to `master`, and either merge it yourself after CI, if appropriate,
or notify the maintainers that it is ready to go.
- Once the PR has been merged, tag `master` with `v4.7.0-rc1` and push this tag.
- We do this for the same list of repositories as for stable releases, see above.
As above, there are dependencies between these, and so the process above is iterative.
It greatly helps if you can merge the `bump/v4.7.0` PRs yourself!
@@ -187,9 +188,11 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
Please also make sure that whoever is handling social media knows the release is out.
- Begin the next development cycle (i.e. for `v4.8.0`) on the Lean repository, by making a PR that:
- Updates `src/CMakeLists.txt` to say `set(LEAN_VERSION_MINOR 8)`
- Replaces the "release notes will be copied" text in the `v4.6.0` section of `RELEASES.md` with the
finalized release notes from the `releases/v4.6.0` branch.
- Replaces the "development in progress" in the `v4.7.0` section of `RELEASES.md` with
```
Release candidate, release notes will be copied from `branch releases/v4.7.0` once completed.
Release candidate, release notes will be copied from the branch `releases/v4.7.0` once completed.
```
and inserts the following section before that section:
```
@@ -198,6 +201,8 @@ We'll use `v4.7.0-rc1` as the intended release version in this example.
Development in progress.
```
- Removes all the entries from the `./releases_drafts/` folder.
- Titled "chore: begin development cycle for v4.8.0"
## Time estimates:
Slightly longer than the corresponding steps for a stable release.
@@ -222,12 +227,15 @@ Please read https://leanprover-community.github.io/contribute/tags_and_branches.
* This can either be done by the person managing this process directly,
or by soliciting assistance from authors of files, or generally helpful people on Zulip!
* Each repo has a `bump/v4.7.0` which accumulates reviewed changes adapting to new versions.
* Once `nightly-testing` is working on a given nightly, say `nightly-2024-02-15`, we:
* Once `nightly-testing` is working on a given nightly, say `nightly-2024-02-15`, we will create a PR to `bump/v4.7.0`.
* For Mathlib, there is a script in `scripts/create-adaptation-pr.sh` that automates this process.
* For Batteries and Aesop it is currently manual.
* For all of these repositories, the process is the same:
* Make sure `bump/v4.7.0` is up to date with `master` (by merging `master`, no PR necessary)
* Create from `bump/v4.7.0` a `bump/nightly-2024-02-15` branch.
* In that branch, `git merge --squash nightly-testing` to bring across changes from `nightly-testing`.
* In that branch, `git merge nightly-testing` to bring across changes from `nightly-testing`.
* Sanity check changes, commit, and make a PR to `bump/v4.7.0` from the `bump/nightly-2024-02-15` branch.
* Solicit review, merge the PR into `bump/v4,7,0`.
* Solicit review, merge the PR into `bump/v4.7.0`.
* It is always okay to merge in the following directions:
`master` -> `bump/v4.7.0` -> `bump/nightly-2024-02-15` -> `nightly-testing`.
Please remember to push any merges you make to intermediate steps!
@@ -239,7 +247,7 @@ The exact steps are a work in progress.
Here is the general idea:
* The work is done right on the `releases/v4.6.0` branch sometime after it is created but before the stable release is made.
The release notes for `v4.6.0` will be copied to `master`.
The release notes for `v4.6.0` will later be copied to `master` when we begin a new development cycle.
* There can be material for release notes entries in commit messages.
* There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
See `./releases_drafts/README.md` for more information.

View File

@@ -149,4 +149,4 @@ def fact : Expr ctx (Ty.fn Ty.int Ty.int) :=
(op (·*·) (delay fun _ => app fact (op (·-·) (var stop) (val 1))) (var stop)))
decreasing_by sorry
#eval fact.interp Env.nil 10
#eval! fact.interp Env.nil 10

138
doc/flake.lock generated
View File

@@ -18,12 +18,15 @@
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1656928814,
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -35,13 +38,12 @@
"lean": {
"inputs": {
"flake-utils": "flake-utils",
"lean4-mode": "lean4-mode",
"nix": "nix",
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs",
"nixpkgs-old": "nixpkgs-old"
},
"locked": {
"lastModified": 0,
"narHash": "sha256-YnYbmG0oou1Q/GE4JbMNb8/yqUVXBPIvcdQQJHBqtPk=",
"narHash": "sha256-saRAtQ6VautVXKDw1XH35qwP0KEBKTKZbg/TRa4N9Vw=",
"path": "../.",
"type": "path"
},
@@ -50,22 +52,6 @@
"type": "path"
}
},
"lean4-mode": {
"flake": false,
"locked": {
"lastModified": 1659020985,
"narHash": "sha256-+dRaXB7uvN/weSZiKcfSKWhcdJVNg9Vg8k0pJkDNjpc=",
"owner": "leanprover",
"repo": "lean4-mode",
"rev": "37d5c99b7b29c80ab78321edd6773200deb0bca6",
"type": "github"
},
"original": {
"owner": "leanprover",
"repo": "lean4-mode",
"type": "github"
}
},
"leanInk": {
"flake": false,
"locked": {
@@ -83,22 +69,6 @@
"type": "github"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"mdBook": {
"flake": false,
"locked": {
@@ -115,65 +85,13 @@
"type": "github"
}
},
"nix": {
"inputs": {
"lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1657097207,
"narHash": "sha256-SmeGmjWM3fEed3kQjqIAO8VpGmkC2sL1aPE7kKpK650=",
"owner": "NixOS",
"repo": "nix",
"rev": "f6316b49a0c37172bca87ede6ea8144d7d89832f",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1653988320,
"narHash": "sha256-ZaqFFsSDipZ6KVqriwM34T739+KLYJvNmCWzErjAg7c=",
"lastModified": 1710889954,
"narHash": "sha256-Pr6F5Pmd7JnNEMHHmspZ0qVqIBVxyZ13ik1pJtm2QXk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fa57ed190fd6c7c746319444f34b5917666e5c1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-22.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1657208011,
"narHash": "sha256-BlIFwopAykvdy1DYayEkj6ZZdkn+cVgPNX98QVLc0jM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2770cc0b1e8faa0e20eb2c6aea64c256a706d4f2",
"rev": "7872526e9c5332274ea5932a0c3270d6e4724f3b",
"type": "github"
},
"original": {
@@ -183,6 +101,23 @@
"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",
@@ -194,6 +129,21 @@
"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",

View File

@@ -17,7 +17,7 @@
};
outputs = inputs@{ self, ... }: inputs.flake-utils.lib.eachDefaultSystem (system:
with inputs.lean.packages.${system}; with nixpkgs;
with inputs.lean.packages.${system}.deprecated; with nixpkgs;
let
doc-src = lib.sourceByRegex ../. ["doc.*" "tests(/lean(/beginEndAsMacro.lean)?)?"];
in {
@@ -44,21 +44,6 @@
mdbook build -d $out
'';
};
# We use a separate derivation instead of `checkPhase` so we can push it but not `doc` to the binary cache
test = stdenv.mkDerivation {
name ="lean-doc-test";
src = doc-src;
buildInputs = [ lean-mdbook stage1.Lean.lean-package strace ];
patchPhase = ''
cd doc
patchShebangs test
'';
buildPhase = ''
mdbook test
touch $out
'';
dontInstall = true;
};
leanInk = (buildLeanPackage {
name = "Main";
src = inputs.leanInk;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -13,7 +13,7 @@ Recall that nonnegative numerals are considered to be a `Nat` if there are no ty
The operator `/` for `Int` implements integer division.
```lean
#eval -10 / 4 -- -2
#eval -10 / 4 -- -3
```
Similar to `Nat`, the internal representation of `Int` is optimized. Small integers are

View File

@@ -8,6 +8,7 @@ Requirements
- C++14 compatible compiler
- [CMake](http://www.cmake.org)
- [GMP (GNU multiprecision library)](http://gmplib.org/)
- [LibUV](https://libuv.org/)
Platform-Specific Setup
-----------------------
@@ -27,9 +28,9 @@ Setting up a basic parallelized release build:
git clone https://github.com/leanprover/lean4
cd lean4
cmake --preset release
make -C build/release -j$(nproc) # see below for macOS
make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
```
You can replace `$(nproc)`, which is not available on macOS and some alternative shells, with the desired parallelism amount.
You can replace `$(nproc || sysctl -n hw.logicalcpu)` with the desired parallelism amount.
The above commands will compile the Lean library and binaries into the
`stage1` subfolder; see below for details.

View File

@@ -25,7 +25,7 @@ MSYS2 has a package management system, [pacman][pacman], which is used in Arch L
Here are the commands to install all dependencies needed to compile Lean on your machine.
```bash
pacman -S make python mingw-w64-x86_64-cmake mingw-w64-x86_64-clang mingw-w64-x86_64-ccache git unzip diffutils binutils
pacman -S make python mingw-w64-x86_64-cmake mingw-w64-x86_64-clang mingw-w64-x86_64-ccache mingw-w64-x86_64-libuv mingw-w64-x86_64-gmp git unzip diffutils binutils
```
You should now be able to run these commands:
@@ -64,6 +64,7 @@ they are installed in your MSYS setup:
- libgcc_s_seh-1.dll
- libstdc++-6.dll
- libgmp-10.dll
- libuv-1.dll
- libwinpthread-1.dll
The following linux command will do that:

View File

@@ -32,15 +32,16 @@ following to use `g++`.
cmake -DCMAKE_CXX_COMPILER=g++ ...
```
## Required Packages: CMake, GMP
## Required Packages: CMake, GMP, libuv
```bash
brew install cmake
brew install gmp
brew install libuv
```
## Recommended Packages: CCache
```bash
brew install ccache
```
```

View File

@@ -8,5 +8,5 @@ follow the [generic build instructions](index.md).
## Basic packages
```bash
sudo apt-get install git libgmp-dev cmake ccache clang
sudo apt-get install git libgmp-dev libuv1-dev cmake ccache clang
```

View File

@@ -7,12 +7,17 @@ See [Setup](./setup.md) for supported platforms and other ways to set up Lean 4.
1. Launch VS Code and install the `lean4` extension by clicking on the "Extensions" sidebar entry and searching for "lean4".
![installing the vscode-lean4 extension](images/code-ext.png)
![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`), clicking on the ∀-symbol in the top right and selecting "Documentation… > Setup: Show Setup Guide".
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)
![show setup guide](images/show-setup-guide.png)
1. Follow the Lean 4 setup guide. It will walk you through learning resources for Lean 4, teach you how to set up Lean's dependencies on your platform, install Lean 4 for you at the click of a button and help you set up your first project.
1. Follow the Lean 4 setup guide. It will:
![setup guide](images/setup_guide.png)
- 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)

113
flake.lock generated
View File

@@ -1,21 +1,5 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@@ -34,71 +18,18 @@
"type": "github"
}
},
"lean4-mode": {
"flake": false,
"locked": {
"lastModified": 1709737301,
"narHash": "sha256-uT9JN2kLNKJK9c/S/WxLjiHmwijq49EgLb+gJUSDpz0=",
"owner": "leanprover",
"repo": "lean4-mode",
"rev": "f1f24c15134dee3754b82c9d9924866fe6bc6b9f",
"type": "github"
},
"original": {
"owner": "leanprover",
"repo": "lean4-mode",
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1711102798,
"narHash": "sha256-CXOIJr8byjolqG7eqCLa+Wfi7rah62VmLoqSXENaZnw=",
"owner": "NixOS",
"repo": "nix",
"rev": "a22328066416650471c3545b0b138669ea212ab4",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1709083642,
"narHash": "sha256-7kkJQd4rZ+vFrzWu8sTRtta5D1kBG0LSRYAfhtmMlSo=",
"lastModified": 1710889954,
"narHash": "sha256-Pr6F5Pmd7JnNEMHHmspZ0qVqIBVxyZ13ik1pJtm2QXk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b550fe4b4776908ac2a861124307045f8e717c8e",
"rev": "7872526e9c5332274ea5932a0c3270d6e4724f3b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.11",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
@@ -120,44 +51,10 @@
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1710889954,
"narHash": "sha256-Pr6F5Pmd7JnNEMHHmspZ0qVqIBVxyZ13ik1pJtm2QXk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7872526e9c5332274ea5932a0c3270d6e4724f3b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"lean4-mode": "lean4-mode",
"nix": "nix",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs",
"nixpkgs-old": "nixpkgs-old"
}
},

View File

@@ -1,44 +1,27 @@
{
description = "Lean interactive theorem prover";
description = "Lean development flake. Not intended for end users.";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# old nixpkgs used for portable release with older glibc (2.27)
inputs.nixpkgs-old.url = "github:NixOS/nixpkgs/nixos-19.03";
inputs.nixpkgs-old.flake = false;
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nix.url = "github:NixOS/nix";
inputs.lean4-mode = {
url = "github:leanprover/lean4-mode";
flake = false;
};
# used *only* by `stage0-from-input` below
#inputs.lean-stage0 = {
# url = github:leanprover/lean4;
# inputs.nixpkgs.follows = "nixpkgs";
# inputs.flake-utils.follows = "flake-utils";
# inputs.nix.follows = "nix";
# inputs.lean4-mode.follows = "lean4-mode";
#};
outputs = { self, nixpkgs, nixpkgs-old, flake-utils, nix, lean4-mode, ... }@inputs: flake-utils.lib.eachDefaultSystem (system:
outputs = { self, nixpkgs, nixpkgs-old, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
# for `vscode-with-extensions`
config.allowUnfree = true;
};
pkgs = import nixpkgs { inherit system; };
# An old nixpkgs for creating releases with an old glibc
pkgsDist-old = import nixpkgs-old { inherit system; };
# An old nixpkgs for creating releases with an old glibc
pkgsDist-old-aarch = import nixpkgs-old { localSystem.config = "aarch64-unknown-linux-gnu"; };
lean-packages = pkgs.callPackage (./nix/packages.nix) { src = ./.; inherit nix lean4-mode; };
lean-packages = pkgs.callPackage (./nix/packages.nix) { src = ./.; };
devShellWithDist = pkgsDist: pkgs.mkShell.override {
stdenv = pkgs.overrideCC pkgs.stdenv lean-packages.llvmPackages.clang;
} ({
buildInputs = with pkgs; [
cmake gmp ccache
cmake gmp libuv ccache
lean-packages.llvmPackages.llvm # llvm-symbolizer for asan/lsan
gdb
# TODO: only add when proven to not affect the flakification
@@ -51,6 +34,7 @@
CTEST_OUTPUT_ON_FAILURE = 1;
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
GMP = pkgsDist.gmp.override { withStatic = true; };
LIBUV = pkgsDist.libuv.overrideAttrs (attrs: { configureFlags = ["--enable-static"]; });
GLIBC = pkgsDist.glibc;
GLIBC_DEV = pkgsDist.glibc.dev;
GCC_LIB = pkgsDist.gcc.cc.lib;
@@ -58,41 +42,15 @@
GDB = pkgsDist.gdb;
});
in {
packages = lean-packages // rec {
debug = lean-packages.override { debug = true; };
stage0debug = lean-packages.override { stage0debug = true; };
asan = lean-packages.override { extraCMakeFlags = [ "-DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address" "-DLEANC_EXTRA_FLAGS=-fsanitize=address" "-DSMALL_ALLOCATOR=OFF" "-DSYMBOLIC=OFF" ]; };
asandebug = asan.override { debug = true; };
tsan = lean-packages.override {
extraCMakeFlags = [ "-DLEAN_EXTRA_CXX_FLAGS=-fsanitize=thread" "-DLEANC_EXTRA_FLAGS=-fsanitize=thread" "-DCOMPRESSED_OBJECT_HEADER=OFF" ];
stage0 = (lean-packages.override {
# Compressed headers currently trigger data race reports in tsan.
# Turn them off for stage 0 as well so stage 1 can read its own stdlib.
extraCMakeFlags = [ "-DCOMPRESSED_OBJECT_HEADER=OFF" ];
}).stage1;
};
tsandebug = tsan.override { debug = true; };
stage0-from-input = lean-packages.override {
stage0 = pkgs.writeShellScriptBin "lean" ''
exec ${inputs.lean-stage0.packages.${system}.lean}/bin/lean -Dinterpreter.prefer_native=false "$@"
'';
};
inherit self;
packages = {
# to be removed when Nix CI is not needed anymore
inherit (lean-packages) cacheRoots test update-stage0-commit ciShell;
deprecated = lean-packages;
};
defaultPackage = lean-packages.lean-all;
# The default development shell for working on lean itself
devShells.default = devShellWithDist pkgs;
devShells.oldGlibc = devShellWithDist pkgsDist-old;
devShells.oldGlibcAArch = devShellWithDist pkgsDist-old-aarch;
checks.lean = lean-packages.test;
}) // rec {
templates.pkg = {
path = ./nix/templates/pkg;
description = "A custom Lean package";
};
defaultTemplate = templates.pkg;
};
});
}

View File

@@ -1,13 +1,13 @@
{ src, debug ? false, stage0debug ? false, extraCMakeFlags ? [],
stdenv, lib, cmake, gmp, git, gnumake, bash, buildLeanPackage, writeShellScriptBin, runCommand, symlinkJoin, lndir, perl, gnused, darwin, llvmPackages, linkFarmFromDrvs,
stdenv, lib, cmake, gmp, libuv, git, gnumake, bash, buildLeanPackage, writeShellScriptBin, runCommand, symlinkJoin, lndir, perl, gnused, darwin, llvmPackages, linkFarmFromDrvs,
... } @ args:
with builtins;
rec {
lib.warn "The Nix-based build is deprecated" rec {
inherit stdenv;
sourceByRegex = p: rs: lib.sourceByRegex p (map (r: "(/src/)?${r}") rs);
buildCMake = args: stdenv.mkDerivation ({
nativeBuildInputs = [ cmake ];
buildInputs = [ gmp llvmPackages.llvm ];
buildInputs = [ gmp libuv llvmPackages.llvm ];
# https://github.com/NixOS/nixpkgs/issues/60919
hardeningDisable = [ "all" ];
dontStrip = (args.debug or debug);
@@ -26,11 +26,7 @@ rec {
lean-bin-tools-unwrapped = buildCMake {
name = "lean-bin-tools";
outputs = [ "out" "leanc_src" ];
realSrc = sourceByRegex (src + "/src") [ "CMakeLists\.txt" "cmake.*" "bin.*" "include.*" ".*\.in" "Leanc\.lean" ];
preConfigure = ''
touch empty.cpp
sed -i 's/add_subdirectory.*//;s/set(LEAN_OBJS.*/set(LEAN_OBJS empty.cpp)/' CMakeLists.txt
'';
realSrc = sourceByRegex (src + "/src") [ "CMakeLists\.txt" "[a-z].*" ".*\.in" "Leanc\.lean" ];
dontBuild = true;
installPhase = ''
mkdir $out $leanc_src
@@ -45,11 +41,10 @@ rec {
leancpp = buildCMake {
name = "leancpp";
src = src + "/src";
buildFlags = [ "leancpp" "leanrt" "leanrt_initial-exec" "shell" ];
buildFlags = [ "leancpp" "leanrt" "leanrt_initial-exec" "leanshell" "leanmain" ];
installPhase = ''
mkdir -p $out
mv lib/ $out/
mv shell/CMakeFiles/shell.dir/lean.cpp.o $out/lib
mv runtime/libleanrt_initial-exec.a $out/lib
'';
};
@@ -122,12 +117,15 @@ rec {
touch empty.c
${stdenv.cc}/bin/cc -shared -o $out/$libName empty.c
'';
leanshared_1 = runCommand "leanshared_1" { buildInputs = [ stdenv.cc ]; libName = "leanshared_1${stdenv.hostPlatform.extensions.sharedLibrary}"; } ''
mkdir $out
touch empty.c
${stdenv.cc}/bin/cc -shared -o $out/$libName empty.c
'';
leanshared = runCommand "leanshared" { buildInputs = [ stdenv.cc ]; libName = "libleanshared${stdenv.hostPlatform.extensions.sharedLibrary}"; } ''
mkdir $out
LEAN_CC=${stdenv.cc}/bin/cc ${lean-bin-tools-unwrapped}/bin/leanc -shared ${lib.optionalString stdenv.isLinux "-Wl,-Bsymbolic"} \
${if stdenv.isDarwin
then "-Wl,-force_load,${Init.staticLib}/libInit.a -Wl,-force_load,${Std.staticLib}/libStd.a -Wl,-force_load,${Lean.staticLib}/libLean.a -Wl,-force_load,${leancpp}/lib/lean/libleancpp.a ${leancpp}/lib/libleanrt_initial-exec.a -lc++"
else "-Wl,--whole-archive -lInit -lStd -lLean -lleancpp ${leancpp}/lib/libleanrt_initial-exec.a -Wl,--no-whole-archive -lstdc++"} \
-Wl,--whole-archive ${leancpp}/lib/temp/libleanshell.a -lInit -lStd -lLean -lleancpp ${leancpp}/lib/libleanrt_initial-exec.a -Wl,--no-whole-archive -lstdc++ \
-lm ${stdlibLinkFlags} \
$(${llvmPackages.libllvm.dev}/bin/llvm-config --ldflags --libs) \
-o $out/$libName
@@ -135,18 +133,18 @@ rec {
mods = foldl' (mods: pkg: mods // pkg.mods) {} stdlib;
print-paths = Lean.makePrintPathsFor [] mods;
leanc = writeShellScriptBin "leanc" ''
LEAN_CC=${stdenv.cc}/bin/cc ${Leanc.executable}/bin/leanc -I${lean-bin-tools-unwrapped}/include ${stdlibLinkFlags} -L${libInit_shared} -L${leanshared} "$@"
LEAN_CC=${stdenv.cc}/bin/cc ${Leanc.executable}/bin/leanc -I${lean-bin-tools-unwrapped}/include ${stdlibLinkFlags} -L${libInit_shared} -L${leanshared_1} -L${leanshared} "$@"
'';
lean = runCommand "lean" { buildInputs = lib.optional stdenv.isDarwin darwin.cctools; } ''
mkdir -p $out/bin
${leanc}/bin/leanc ${leancpp}/lib/lean.cpp.o ${libInit_shared}/* ${leanshared}/* -o $out/bin/lean
${leanc}/bin/leanc ${leancpp}/lib/temp/libleanmain.a ${libInit_shared}/* ${leanshared_1}/* ${leanshared}/* -o $out/bin/lean
'';
# derivation following the directory layout of the "basic" setup, mostly useful for running tests
lean-all = stdenv.mkDerivation {
name = "lean-${desc}";
buildCommand = ''
mkdir -p $out/bin $out/lib/lean
ln -sf ${leancpp}/lib/lean/* ${lib.concatMapStringsSep " " (l: "${l.modRoot}/* ${l.staticLib}/*") (lib.reverseList stdlib)} ${libInit_shared}/* ${leanshared}/* $out/lib/lean/
ln -sf ${leancpp}/lib/lean/* ${lib.concatMapStringsSep " " (l: "${l.modRoot}/* ${l.staticLib}/*") (lib.reverseList stdlib)} ${libInit_shared}/* ${leanshared_1}/* ${leanshared}/* $out/lib/lean/
# put everything in a single final derivation so `IO.appDir` references work
cp ${lean}/bin/lean ${leanc}/bin/leanc ${Lake-Main.executable}/bin/lake $out/bin
# NOTE: `lndir` will not override existing `bin/leanc`
@@ -160,7 +158,7 @@ rec {
test = buildCMake {
name = "lean-test-${desc}";
realSrc = lib.sourceByRegex src [ "src.*" "tests.*" ];
buildInputs = [ gmp perl git ];
buildInputs = [ gmp libuv perl git ];
preConfigure = ''
cd src
'';
@@ -171,7 +169,7 @@ rec {
ln -sf ${lean-all}/* .
'';
buildPhase = ''
ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)' -j$NIX_BUILD_CORES
ctest --output-junit test-results.xml --output-on-failure -E 'leancomptest_(doc_example|foreign)|leanlaketest_reverse-ffi' -j$NIX_BUILD_CORES
'';
installPhase = ''
mkdir $out

View File

@@ -1,5 +1,5 @@
{ lean, lean-leanDeps ? lean, lean-final ? lean, leanc,
stdenv, lib, coreutils, gnused, writeShellScriptBin, bash, lean-emacs, lean-vscode, nix, substituteAll, symlinkJoin, linkFarmFromDrvs,
stdenv, lib, coreutils, gnused, writeShellScriptBin, bash, substituteAll, symlinkJoin, linkFarmFromDrvs,
runCommand, darwin, mkShell, ... }:
let lean-final' = lean-final; in
lib.makeOverridable (
@@ -197,19 +197,6 @@ with builtins; let
then map (m: m.module) header.imports
else abort "errors while parsing imports of ${mod}:\n${lib.concatStringsSep "\n" header.errors}";
in mkMod mod (map (dep: if modDepsMap ? ${dep} then modCandidates.${dep} else externalModMap.${dep}) deps)) modDepsMap;
makeEmacsWrapper = name: emacs: lean: writeShellScriptBin name ''
${emacs} --eval "(progn (setq lean4-rootdir \"${lean}\"))" "$@"
'';
makeVSCodeWrapper = name: lean: writeShellScriptBin name ''
PATH=${lean}/bin:$PATH ${lean-vscode}/bin/code "$@"
'';
printPaths = deps: writeShellScriptBin "print-paths" ''
echo '${toJSON {
oleanPath = [(depRoot "print-paths" deps)];
srcPath = ["."] ++ map (dep: dep.src) allExternalDeps;
loadDynlibPaths = map pathOfSharedLib (loadDynlibsOfDeps deps);
}}'
'';
expandGlob = g:
if typeOf g == "string" then [g]
else if g.glob == "one" then [g.mod]
@@ -257,48 +244,4 @@ in rec {
-o $out/bin/${executableName} \
${lib.concatStringsSep " " allLinkFlags}
'') {};
lean-package = writeShellScriptBin "lean" ''
LEAN_PATH=${modRoot}:$LEAN_PATH LEAN_SRC_PATH=$LEAN_SRC_PATH:${src} exec ${lean-final}/bin/lean "$@"
'';
emacs-package = makeEmacsWrapper "emacs-package" lean-package;
vscode-package = makeVSCodeWrapper "vscode-package" lean-package;
link-ilean = writeShellScriptBin "link-ilean" ''
dest=''${1:-.}
mkdir -p $dest/build/lib
ln -sf ${iTree}/* $dest/build/lib
'';
makePrintPathsFor = deps: mods: printPaths deps // mapAttrs (_: mod: makePrintPathsFor (deps ++ [mod]) mods) mods;
print-paths = makePrintPathsFor [] (mods' // externalModMap);
# `lean` wrapper that dynamically runs Nix for the actual `lean` executable so the same editor can be
# used for multiple projects/after upgrading the `lean` input/for editing both stage 1 and the tests
lean-bin-dev = substituteAll {
name = "lean";
dir = "bin";
src = ./lean-dev.in;
isExecutable = true;
srcRoot = fullSrc; # use root flake.nix in case of Lean repo
inherit bash nix srcTarget srcArgs;
};
lake-dev = substituteAll {
name = "lake";
dir = "bin";
src = ./lake-dev.in;
isExecutable = true;
srcRoot = fullSrc; # use root flake.nix in case of Lean repo
inherit bash nix srcTarget srcArgs;
};
lean-dev = symlinkJoin { name = "lean-dev"; paths = [ lean-bin-dev lake-dev ]; };
emacs-dev = makeEmacsWrapper "emacs-dev" "${lean-emacs}/bin/emacs" lean-dev;
emacs-path-dev = makeEmacsWrapper "emacs-path-dev" "emacs" lean-dev;
vscode-dev = makeVSCodeWrapper "vscode-dev" lean-dev;
devShell = mkShell {
buildInputs = [ nix ];
shellHook = ''
export LEAN_SRC_PATH="${srcPath}"
'';
};
})

View File

@@ -1,9 +1,6 @@
{ src, pkgs, nix, ... } @ args:
{ src, pkgs, ... } @ args:
with pkgs;
let
nix-pinned = writeShellScriptBin "nix" ''
${nix.packages.${system}.default}/bin/nix --experimental-features 'nix-command flakes' --extra-substituters https://lean4.cachix.org/ --option warn-dirty false "$@"
'';
# https://github.com/NixOS/nixpkgs/issues/130963
llvmPackages = if stdenv.isDarwin then llvmPackages_11 else llvmPackages_15;
cc = (ccacheWrapper.override rec {
@@ -42,40 +39,9 @@ let
inherit (lean) stdenv;
lean = lean.stage1;
inherit (lean.stage1) leanc;
inherit lean-emacs lean-vscode;
nix = nix-pinned;
}));
lean4-mode = emacsPackages.melpaBuild {
pname = "lean4-mode";
version = "1";
commit = "1";
src = args.lean4-mode;
packageRequires = with pkgs.emacsPackages.melpaPackages; [ dash f flycheck magit-section lsp-mode s ];
recipe = pkgs.writeText "recipe" ''
(lean4-mode
:repo "leanprover/lean4-mode"
:fetcher github
:files ("*.el" "data"))
'';
};
lean-emacs = emacsWithPackages [ lean4-mode ];
# updating might be nicer by building from source from a flake input, but this is good enough for now
vscode-lean4 = vscode-utils.extensionFromVscodeMarketplace {
name = "lean4";
publisher = "leanprover";
version = "0.0.63";
sha256 = "sha256-kjEex7L0F2P4pMdXi4NIZ1y59ywJVubqDqsoYagZNkI=";
};
lean-vscode = vscode-with-extensions.override {
vscodeExtensions = [ vscode-lean4 ];
};
in {
inherit cc lean4-mode buildLeanPackage llvmPackages vscode-lean4;
lean = lean.stage1;
stage0print-paths = lean.stage1.Lean.print-paths;
HEAD-as-stage0 = (lean.stage1.Lean.overrideArgs { srcTarget = "..#stage0-from-input.stage0"; srcArgs = "(--override-input lean-stage0 ..\?rev=$(git rev-parse HEAD) -- -Dinterpreter.prefer_native=false \"$@\")"; });
HEAD-as-stage1 = (lean.stage1.Lean.overrideArgs { srcTarget = "..\?rev=$(git rev-parse HEAD)#stage0"; });
nix = nix-pinned;
inherit cc buildLeanPackage llvmPackages;
nixpkgs = pkgs;
ciShell = writeShellScriptBin "ciShell" ''
set -o pipefail
@@ -83,5 +49,4 @@ in {
# prefix lines with cumulative and individual execution time
"$@" |& ts -i "(%.S)]" | ts -s "[%M:%S"
'';
vscode = lean-vscode;
} // lean.stage1.Lean // lean.stage1 // lean
} // lean.stage1

View File

@@ -0,0 +1,3 @@
* The `Lean` module has switched from `Lean.HashMap` and `Lean.HashSet` to `Std.HashMap` and `Std.HashSet`. `Lean.HashMap` and `Lean.HashSet` are now deprecated and will be removed in a future release. Users of `Lean` APIs that interact with hash maps, for example `Lean.Environment.const2ModIdx`, might encounter minor breakage due to the following breaking changes from `Lean.HashMap` to `Std.HashMap`:
* query functions use the term `get` instead of `find`,
* the notation `map[key]` no longer returns an optional value but expects a proof that the key is present in the map instead. The previous behavior is available via the `map[key]?` notation.

1
releases_drafts/libuv.md Normal file
View File

@@ -0,0 +1 @@
* #4963 [LibUV](https://libuv.org/) is now required to build Lean. This change only affects developers who compile Lean themselves instead of obtaining toolchains via `elan`. We have updated the official build instructions with information on how to obtain LibUV on our supported platforms.

View File

@@ -1,45 +0,0 @@
A new linter flags situations where a local variable's name is one of
the argumentless constructors of its type. This can arise when a user either
doesn't open a namespace or doesn't add a dot or leading qualifier, as
in the following:
````
inductive Tree (α : Type) where
| leaf
| branch (left : Tree α) (val : α) (right : Tree α)
def depth : Tree α → Nat
| leaf => 0
````
With this linter, the `leaf` pattern is highlighted as a local
variable whose name overlaps with the constructor `Tree.leaf`.
The linter can be disabled with `set_option linter.constructorNameAsVariable false`.
Additionally, the error message that occurs when a name in a pattern that takes arguments isn't valid now suggests similar names that would be valid. This means that the following definition:
```
def length (list : List α) : Nat :=
match list with
| nil => 0
| cons x xs => length xs + 1
```
now results in the following warning:
```
warning: Local variable 'nil' resembles constructor 'List.nil' - write '.nil' (with a dot) or 'List.nil' to use the constructor.
note: this linter can be disabled with `set_option linter.constructorNameAsVariable false`
```
and error:
```
invalid pattern, constructor or constant marked with '[match_pattern]' expected
Suggestion: 'List.cons' is similar
```
#4301

View File

@@ -38,7 +38,7 @@ $CP $GLIBC/lib/*crt* llvm/lib/
$CP $GLIBC/lib/*crt* stage1/lib/
# runtime
(cd llvm; $CP --parents lib/clang/*/lib/*/{clang_rt.*.o,libclang_rt.builtins*} ../stage1)
$CP llvm/lib/*/lib{c++,c++abi,unwind}.* $GMP/lib/libgmp.a stage1/lib/
$CP llvm/lib/*/lib{c++,c++abi,unwind}.* $GMP/lib/libgmp.a $LIBUV/lib/libuv.a stage1/lib/
# LLVM 15 appears to ship the dependencies in 'llvm/lib/<target-triple>/' and 'llvm/include/<target-triple>/'
# but clang-15 that we use to compile is linked against 'llvm/lib/' and 'llvm/include'
# https://github.com/llvm/llvm-project/issues/54955
@@ -62,8 +62,8 @@ fi
# use `-nostdinc` to make sure headers are not visible by default (in particular, not to `#include_next` in the clang headers),
# but do not change sysroot so users can still link against system libs
echo -n " -DLEANC_INTERNAL_FLAGS='-nostdinc -isystem ROOT/include/clang' -DLEANC_CC=ROOT/bin/clang"
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -L ROOT/lib/glibc ROOT/lib/glibc/libc_nonshared.a -Wl,--as-needed -Wl,-Bstatic -lgmp -lunwind -Wl,-Bdynamic -Wl,--no-as-needed -fuse-ld=lld'"
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -L ROOT/lib/glibc ROOT/lib/glibc/libc_nonshared.a -Wl,--as-needed -Wl,-Bstatic -lgmp -lunwind -luv -Wl,-Bdynamic -Wl,--no-as-needed -fuse-ld=lld'"
# when not using the above flags, link GMP dynamically/as usual
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-Wl,--as-needed -lgmp -Wl,--no-as-needed'"
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-Wl,--as-needed -lgmp -luv -Wl,--no-as-needed'"
# do not set `LEAN_CC` for tests
echo -n " -DLEAN_TEST_VARS=''"

View File

@@ -9,6 +9,7 @@ set -uxo pipefail
# use full LLVM release for compiling C++ code, but subset for compiling C code and distribution
GMP=${GMP:-$(brew --prefix)}
LIBUV=${LIBUV:-$(brew --prefix)}
[[ -d llvm ]] || (mkdir llvm; gtar xf $1 --strip-components 1 --directory llvm)
[[ -d llvm-host ]] || if [[ "$#" -gt 1 ]]; then
@@ -46,8 +47,9 @@ echo -n " -DLEAN_EXTRA_CXX_FLAGS='${EXTRA_FLAGS:-}'"
if [[ -L llvm-host ]]; then
echo -n " -DCMAKE_C_COMPILER=$PWD/stage1/bin/clang"
gcp $GMP/lib/libgmp.a stage1/lib/
gcp $LIBUV/lib/libuv.a stage1/lib/
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -L ROOT/lib/libc -fuse-ld=lld'"
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-lgmp'"
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-lgmp -luv'"
else
echo -n " -DCMAKE_C_COMPILER=$PWD/llvm-host/bin/clang -DLEANC_OPTS='--sysroot $PWD/stage1 -resource-dir $PWD/stage1/lib/clang/15.0.1 ${EXTRA_FLAGS:-}'"
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -L ROOT/lib/libc -fuse-ld=lld'"

View File

@@ -31,15 +31,15 @@ cp /clang64/lib/{crtbegin,crtend,crt2,dllcrt2}.o stage1/lib/
# runtime
(cd llvm; cp --parents lib/clang/*/lib/*/libclang_rt.builtins* ../stage1)
# further dependencies
cp /clang64/lib/lib{m,bcrypt,mingw32,moldname,mingwex,msvcrt,pthread,advapi32,shell32,user32,kernel32,ucrtbase}.* /clang64/lib/libgmp.a llvm/lib/lib{c++,c++abi,unwind}.a stage1/lib/
cp /clang64/lib/lib{m,bcrypt,mingw32,moldname,mingwex,msvcrt,pthread,advapi32,shell32,user32,kernel32,ucrtbase}.* /clang64/lib/libgmp.a /clang64/lib/libuv.a llvm/lib/lib{c++,c++abi,unwind}.a stage1/lib/
echo -n " -DLEAN_STANDALONE=ON"
echo -n " -DCMAKE_C_COMPILER=$PWD/stage1/bin/clang.exe -DCMAKE_C_COMPILER_WORKS=1 -DCMAKE_CXX_COMPILER=$PWD/llvm/bin/clang++.exe -DCMAKE_CXX_COMPILER_WORKS=1 -DLEAN_CXX_STDLIB='-lc++ -lc++abi'"
echo -n " -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_CXX_COMPILER=clang++"
echo -n " -DLEAN_EXTRA_CXX_FLAGS='--sysroot $PWD/llvm -idirafter /clang64/include/'"
echo -n " -DLEANC_INTERNAL_FLAGS='--sysroot ROOT -nostdinc -isystem ROOT/include/clang' -DLEANC_CC=ROOT/bin/clang.exe"
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -static-libgcc -Wl,-Bstatic -lgmp -lunwind -Wl,-Bdynamic -fuse-ld=lld'"
echo -n " -DLEANC_INTERNAL_LINKER_FLAGS='-L ROOT/lib -static-libgcc -Wl,-Bstatic -lgmp -luv -lunwind -Wl,-Bdynamic -fuse-ld=lld'"
# when not using the above flags, link GMP dynamically/as usual
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-lgmp -lucrtbase'"
echo -n " -DLEAN_EXTRA_LINKER_FLAGS='-lgmp -luv -lucrtbase'"
# do not set `LEAN_CC` for tests
echo -n " -DAUTO_THREAD_FINALIZATION=OFF -DSTAGE0_AUTO_THREAD_FINALIZATION=OFF"
echo -n " -DLEAN_TEST_VARS=''"

View File

@@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.10)
cmake_policy(SET CMP0054 NEW)
cmake_policy(SET CMP0110 NEW)
if(NOT (${CMAKE_GENERATOR} MATCHES "Unix Makefiles"))
message(FATAL_ERROR "The only supported CMake generator at the moment is 'Unix Makefiles'")
endif()
@@ -9,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 11)
set(LEAN_VERSION_MINOR 12)
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'")
@@ -242,6 +243,15 @@ if("${USE_GMP}" MATCHES "ON")
endif()
endif()
if(NOT "${CMAKE_SYSTEM_NAME}" MATCHES "Emscripten")
# LibUV
find_package(LibUV 1.0.0 REQUIRED)
include_directories(${LIBUV_INCLUDE_DIR})
endif()
if(NOT LEAN_STANDALONE)
string(APPEND LEAN_EXTRA_LINKER_FLAGS " ${LIBUV_LIBRARIES}")
endif()
# ccache
if(CCACHE AND NOT CMAKE_CXX_COMPILER_LAUNCHER AND NOT CMAKE_C_COMPILER_LAUNCHER)
find_program(CCACHE_PATH ccache)
@@ -372,6 +382,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
string(APPEND CMAKE_CXX_FLAGS " -ftls-model=initial-exec")
string(APPEND INIT_SHARED_LINKER_FLAGS " -install_name @rpath/libInit_shared.dylib")
string(APPEND LEANSHARED_1_LINKER_FLAGS " -install_name @rpath/libleanshared_1.dylib")
string(APPEND LEANSHARED_LINKER_FLAGS " -install_name @rpath/libleanshared.dylib")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/lean")
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
@@ -401,8 +412,8 @@ endif()
# executable or `leanshared`, plugins would try to look them up at load time (even though they
# are already loaded) and probably fail unless we set up LD_LIBRARY_PATH.
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
# import library created by the `leanshared` target
string(APPEND LEANC_SHARED_LINKER_FLAGS " -lInit_shared -lleanshared")
# import libraries created by the stdlib.make targets
string(APPEND LEANC_SHARED_LINKER_FLAGS " -lInit_shared -lleanshared_1 -lleanshared")
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
string(APPEND LEANC_SHARED_LINKER_FLAGS " -Wl,-undefined,dynamic_lookup")
endif()
@@ -459,6 +470,22 @@ if(CMAKE_OSX_SYSROOT AND NOT LEAN_STANDALONE)
string(APPEND LEANC_EXTRA_FLAGS " ${CMAKE_CXX_SYSROOT_FLAG}${CMAKE_OSX_SYSROOT}")
endif()
add_subdirectory(initialize)
add_subdirectory(shell)
# to be included in `leanshared` but not the smaller `leanshared_1` (as it would pull
# in the world)
add_library(leaninitialize STATIC $<TARGET_OBJECTS:initialize>)
set_target_properties(leaninitialize PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/temp
OUTPUT_NAME leaninitialize)
add_library(leanshell STATIC util/shell.cpp)
set_target_properties(leanshell PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/temp
OUTPUT_NAME leanshell)
if (${CMAKE_SYSTEM_NAME} MATCHES "Windows")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,--whole-archive -lleanmanifest -Wl,--no-whole-archive")
endif()
if(${STAGE} GREATER 1)
# reuse C++ parts, which don't change
add_library(leanrt_initial-exec STATIC IMPORTED)
@@ -467,13 +494,17 @@ if(${STAGE} GREATER 1)
add_library(leanrt STATIC IMPORTED)
set_target_properties(leanrt PROPERTIES
IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/lib/lean/libleanrt.a")
add_library(leancpp_1 STATIC IMPORTED)
set_target_properties(leancpp_1 PROPERTIES
IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/lib/temp/libleancpp_1.a")
add_library(leancpp STATIC IMPORTED)
set_target_properties(leancpp PROPERTIES
IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/lib/lean/libleancpp.a")
add_custom_target(copy-leancpp
COMMAND cmake -E copy_if_different "${PREV_STAGE}/runtime/libleanrt_initial-exec.a" "${CMAKE_BINARY_DIR}/runtime/libleanrt_initial-exec.a"
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/lean/libleanrt.a" "${CMAKE_BINARY_DIR}/lib/lean/libleanrt.a"
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/lean/libleancpp.a" "${CMAKE_BINARY_DIR}/lib/lean/libleancpp.a")
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/lean/libleancpp.a" "${CMAKE_BINARY_DIR}/lib/lean/libleancpp.a"
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/temp/libleancpp_1.a" "${CMAKE_BINARY_DIR}/lib/temp/libleancpp_1.a")
add_dependencies(leancpp copy-leancpp)
if(LLVM)
add_custom_target(copy-lean-h-bc
@@ -493,10 +524,13 @@ else()
set(LEAN_OBJS ${LEAN_OBJS} $<TARGET_OBJECTS:constructions>)
add_subdirectory(library/compiler)
set(LEAN_OBJS ${LEAN_OBJS} $<TARGET_OBJECTS:compiler>)
add_subdirectory(initialize)
set(LEAN_OBJS ${LEAN_OBJS} $<TARGET_OBJECTS:initialize>)
add_library(leancpp STATIC ${LEAN_OBJS})
# leancpp without `initialize` (see `leaninitialize` above)
add_library(leancpp_1 STATIC ${LEAN_OBJS})
set_target_properties(leancpp_1 PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/temp
OUTPUT_NAME leancpp_1)
add_library(leancpp STATIC ${LEAN_OBJS} $<TARGET_OBJECTS:initialize>)
set_target_properties(leancpp PROPERTIES
OUTPUT_NAME leancpp)
endif()
@@ -508,25 +542,12 @@ string(REGEX REPLACE "^([a-zA-Z]):" "/\\1" LEAN_BIN "${CMAKE_BINARY_DIR}/bin")
# (also looks nicer in the build log)
file(RELATIVE_PATH LIB ${LEAN_SOURCE_DIR} ${CMAKE_BINARY_DIR}/lib)
# set up libInit_shared only on Windows; see also stdlib.make.in
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(INIT_SHARED_LINKER_FLAGS "-Wl,--whole-archive ${CMAKE_BINARY_DIR}/lib/temp/libInit.a.export ${CMAKE_BINARY_DIR}/lib/temp/libStd.a.export ${CMAKE_BINARY_DIR}/runtime/libleanrt_initial-exec.a -Wl,--no-whole-archive -Wl,--out-implib,${CMAKE_BINARY_DIR}/lib/lean/libInit_shared.dll.a")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(LEANSHARED_LINKER_FLAGS "-Wl,-force_load,${CMAKE_BINARY_DIR}/lib/lean/libInit.a -Wl,-force_load,${CMAKE_BINARY_DIR}/lib/lean/libStd.a -Wl,-force_load,${CMAKE_BINARY_DIR}/lib/lean/libLean.a -Wl,-force_load,${CMAKE_BINARY_DIR}/lib/lean/libleancpp.a ${CMAKE_BINARY_DIR}/runtime/libleanrt_initial-exec.a ${LEANSHARED_LINKER_FLAGS}")
elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(LEANSHARED_LINKER_FLAGS "-Wl,--whole-archive ${CMAKE_BINARY_DIR}/lib/temp/libLean.a.export -lleancpp -Wl,--no-whole-archive -lInit_shared -Wl,--out-implib,${CMAKE_BINARY_DIR}/lib/lean/libleanshared.dll.a")
else()
set(LEANSHARED_LINKER_FLAGS "-Wl,--whole-archive -lInit -lStd -lLean -lleancpp -Wl,--no-whole-archive ${CMAKE_BINARY_DIR}/runtime/libleanrt_initial-exec.a ${LEANSHARED_LINKER_FLAGS}")
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
# We do not use dynamic linking via leanshared for Emscripten to keep things
# simple. (And we are not interested in `Lake` anyway.) To use dynamic
# linking, we would probably have to set MAIN_MODULE=2 on `leanshared`,
# SIDE_MODULE=2 on `lean`, and set CMAKE_SHARED_LIBRARY_SUFFIX to ".js".
string(APPEND LEAN_EXE_LINKER_FLAGS " ${TOOLCHAIN_STATIC_LINKER_FLAGS} ${EMSCRIPTEN_SETTINGS} -lnodefs.js -s EXIT_RUNTIME=1 -s MAIN_MODULE=1 -s LINKABLE=1 -s EXPORT_ALL=1")
string(APPEND LEAN_EXE_LINKER_FLAGS " ${LIB}/temp/libleanshell.a ${TOOLCHAIN_STATIC_LINKER_FLAGS} ${EMSCRIPTEN_SETTINGS} -lnodefs.js -s EXIT_RUNTIME=1 -s MAIN_MODULE=1 -s LINKABLE=1 -s EXPORT_ALL=1")
endif()
# Build the compiler using the bootstrapped C sources for stage0, and use
@@ -571,11 +592,11 @@ else()
add_custom_target(leanshared ALL
WORKING_DIRECTORY ${LEAN_SOURCE_DIR}
DEPENDS Init_shared leancpp
DEPENDS Init_shared leancpp_1 leancpp leanshell leaninitialize
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanshared
VERBATIM)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -lInit_shared -lleanshared")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -lInit_shared -lleanshared_1 -lleanshared")
endif()
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
@@ -612,8 +633,6 @@ file(COPY ${LEAN_SOURCE_DIR}/bin/leanmake DESTINATION ${CMAKE_BINARY_DIR}/bin)
install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/" USE_SOURCE_PERMISSIONS DESTINATION bin)
add_subdirectory(shell)
add_custom_target(clean-stdlib
COMMAND rm -rf "${CMAKE_BINARY_DIR}/lib" || true)

View File

@@ -67,12 +67,8 @@ theorem ite_some_none_eq_none [Decidable P] :
-- This is not marked as `simp` as it is already handled by `dite_eq_right_iff`.
theorem dite_some_none_eq_none [Decidable P] {x : P α} :
(if h : P then some (x h) else none) = none ¬P := by
simp only [dite_eq_right_iff]
rfl
simp
@[simp] 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 only [h, dite_cond_eq_true, dite_cond_eq_false, Option.some.injEq,
false_iff, not_exists]
case pos => exact fun h_eq Exists.intro h h_eq, fun h_exists => h_exists.2
case neg => exact fun h_false _ h_false
by_cases h : P <;> simp [h]

View File

@@ -34,7 +34,7 @@ instance : Monad (ExceptCpsT ε m) where
bind x f := fun _ k₁ k₂ => x _ (fun a => f a _ k₁ k₂) k₂
instance : LawfulMonad (ExceptCpsT σ m) := by
refine' { .. } <;> intros <;> rfl
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
instance : MonadExceptOf ε (ExceptCpsT ε m) where
throw e := fun _ _ k => k e

View File

@@ -153,7 +153,7 @@ namespace Id
@[simp] theorem pure_eq (a : α) : (pure a : Id α) = a := rfl
instance : LawfulMonad Id := by
refine' { .. } <;> intros <;> rfl
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
end Id

View File

@@ -35,7 +35,7 @@ instance : Monad (StateCpsT σ m) where
bind x f := fun δ s k => x δ s fun a s => f a δ s k
instance : LawfulMonad (StateCpsT σ m) := by
refine' { .. } <;> intros <;> rfl
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
@[always_inline]
instance : MonadStateOf σ (StateCpsT σ m) where

View File

@@ -474,6 +474,8 @@ class LawfulSingleton (α : Type u) (β : Type v) [EmptyCollection β] [Insert
insert_emptyc_eq (x : α) : (insert x : β) = singleton x
export LawfulSingleton (insert_emptyc_eq)
attribute [simp] insert_emptyc_eq
/-- Type class used to implement the notation `{ a ∈ c | p a }` -/
class Sep (α : outParam <| Type u) (γ : Type v) where
/-- Computes `{ a ∈ c | p a }`. -/
@@ -701,7 +703,7 @@ theorem Ne.elim (h : a ≠ b) : a = b → False := h
theorem Ne.irrefl (h : a a) : False := h rfl
theorem Ne.symm (h : a b) : b a := fun h₁ => h (h₁.symm)
@[symm] theorem Ne.symm (h : a b) : b a := fun h₁ => h (h₁.symm)
theorem ne_comm {α} {a b : α} : a b b a := Ne.symm, Ne.symm
@@ -754,7 +756,7 @@ noncomputable def HEq.elim {α : Sort u} {a : α} {p : α → Sort v} {b : α} (
theorem HEq.subst {p : (T : Sort u) T Prop} (h₁ : HEq a b) (h₂ : p α a) : p β b :=
HEq.ndrecOn h₁ h₂
theorem HEq.symm (h : HEq a b) : HEq b a :=
@[symm] theorem HEq.symm (h : HEq a b) : HEq b a :=
h.rec (HEq.refl a)
theorem heq_of_eq (h : a = a') : HEq a a' :=
@@ -810,15 +812,15 @@ 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 Iff.symm (h : a b) : b a := Iff.intro h.mpr h.mp
@[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
theorem iff_comm : (a b) (b a) := Iff.comm
theorem And.symm : a b b a := fun ha, hb => hb, ha
@[symm] theorem And.symm : a b b a := fun ha, hb => hb, ha
theorem And.comm : a b b a := Iff.intro And.symm And.symm
theorem and_comm : a b b a := And.comm
theorem Or.symm : a b b a := .rec .inr .inl
@[symm] theorem Or.symm : a b b a := .rec .inr .inl
theorem Or.comm : a b b a := Iff.intro Or.symm Or.symm
theorem or_comm : a b b a := Or.comm
@@ -1102,9 +1104,17 @@ inductive Relation.TransGen {α : Sort u} (r : αα → Prop) : αα
/-- Deprecated synonym for `Relation.TransGen`. -/
@[deprecated Relation.TransGen (since := "2024-07-16")] abbrev TC := @Relation.TransGen
theorem Relation.TransGen.trans {α : Sort u} {r : α α Prop} {a b c} :
TransGen r a b TransGen r b c TransGen r a c := by
intro hab hbc
induction hbc with
| single h => exact TransGen.tail hab h
| tail _ h ih => exact TransGen.tail ih h
/-! # Subtype -/
namespace Subtype
theorem existsOfSubtype {α : Type u} {p : α Prop} : { x // p x } Exists (fun x => p x)
| a, h => a, h
@@ -1201,9 +1211,13 @@ def Prod.map {α₁ : Type u₁} {α₂ : Type u₂} {β₁ : Type v₁} {β₂
/-! # Dependent products -/
theorem ex_of_PSigma {α : Type u} {p : α Prop} : (PSigma (fun x => p x)) Exists (fun x => p x)
theorem Exists.of_psigma_prop {α : Sort u} {p : α Prop} : (PSigma (fun x => p x)) Exists (fun x => p x)
| x, hx => x, hx
@[deprecated Exists.of_psigma_prop (since := "2024-07-27")]
theorem ex_of_PSigma {α : Type u} {p : α Prop} : (PSigma (fun x => p x)) Exists (fun x => p x) :=
Exists.of_psigma_prop
protected theorem PSigma.eta {α : Sort u} {β : α Sort v} {a₁ a₂ : α} {b₁ : β a₁} {b₂ : β a₂}
(h₁ : a₁ = a₂) (h₂ : Eq.ndrec b₁ h₁ = b₂) : PSigma.mk a₁ b₁ = PSigma.mk a₂ b₂ := by
subst h₁
@@ -1545,7 +1559,7 @@ protected abbrev rec
(q : Quot r) : motive q :=
Eq.ndrecOn (Quot.liftIndepPr1 f h q) ((lift (Quot.indep f) (Quot.indepCoherent f h) q).2)
@[inherit_doc Quot.rec] protected abbrev recOn
@[inherit_doc Quot.rec, elab_as_elim] protected abbrev recOn
(q : Quot r)
(f : (a : α) motive (Quot.mk r a))
(h : (a b : α) (p : r a b) Eq.ndrec (f a) (sound p) = f b)
@@ -1556,7 +1570,7 @@ protected abbrev rec
Dependent induction principle for a quotient, when the target type is a `Subsingleton`.
In this case the quotient's side condition is trivial so any function can be lifted.
-/
protected abbrev recOnSubsingleton
@[elab_as_elim] protected abbrev recOnSubsingleton
[h : (a : α) Subsingleton (motive (Quot.mk r a))]
(q : Quot r)
(f : (a : α) motive (Quot.mk r a))

View File

@@ -36,3 +36,4 @@ import Init.Data.Channel
import Init.Data.Cast
import Init.Data.Sum
import Init.Data.BEq
import Init.Data.Subtype

View File

@@ -6,7 +6,7 @@ Authors: Dany Fabian
prelude
import Init.Classical
import Init.Data.List
import Init.ByCases
namespace Lean.Data.AC
inductive Expr

View File

@@ -50,6 +50,13 @@ instance : Inhabited (Array α) where
def singleton (v : α) : Array α :=
mkArray 1 v
/-- Low-level version of `size` that directly queries the C array object cached size.
While this is not provable, `usize` always returns the exact size of the array since
the implementation only supports arrays of size less than `USize.size`.
-/
@[extern "lean_array_size", simp]
def usize (a : @& Array α) : USize := a.size.toUSize
/-- Low-level version of `fget` which is as fast as a C array read.
`Fin` values are represented as tag pointers in the Lean runtime. Thus,
`fget` may be slightly slower than `uget`. -/
@@ -101,7 +108,7 @@ def swap (a : Array α) (i j : @& Fin a.size) : Array α :=
a'.set (size_set a i v₂ j) v₁
/--
Swaps two entries in an array, or panics if either index is out of bounds.
Swaps two entries in an array, or returns the array unchanged if either index is out of bounds.
This will perform the update destructively provided that `a` has a reference
count of 1 when called.
@@ -174,7 +181,7 @@ def modifyOp (self : Array α) (idx : Nat) (f : αα) : Array α :=
This kind of low level trick can be removed with a little bit of compiler support. For example, if the compiler simplifies `as.size < usizeSz` to true. -/
@[inline] unsafe def forInUnsafe {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (as : Array α) (b : β) (f : α β m (ForInStep β)) : m β :=
let sz := USize.ofNat as.size
let sz := as.usize
let rec @[specialize] loop (i : USize) (b : β) : m β := do
if i < sz then
let a := as.uget i lcProof
@@ -280,7 +287,7 @@ def foldrM {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m] (f : α
/-- See comment at `forInUnsafe` -/
@[inline]
unsafe def mapMUnsafe {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : α m β) (as : Array α) : m (Array β) :=
let sz := USize.ofNat as.size
let sz := as.usize
let rec @[specialize] map (i : USize) (r : Array NonScalar) : m (Array PNonScalar.{v}) := do
if i < sz then
let v := r.uget i lcProof

View File

@@ -6,7 +6,8 @@ Authors: Mario Carneiro
prelude
import Init.Data.Nat.MinMax
import Init.Data.Nat.Lemmas
import Init.Data.List.Lemmas
import Init.Data.List.Monadic
import Init.Data.List.Nat.Range
import Init.Data.Fin.Basic
import Init.Data.Array.Mem
import Init.TacticsExtra
@@ -334,8 +335,19 @@ theorem mem_data {a : α} {l : Array α} : a ∈ l.data ↔ a ∈ l := (mem_def
theorem not_mem_nil (a : α) : ¬ a #[] := nofun
theorem getElem_of_mem {a : α} {as : Array α} :
a as ( (n : Nat) (h : n < as.size), as[n]'h = a) := by
intro ha
rcases List.getElem_of_mem ha.val with i, hbound, hi
exists i
exists hbound
/-- # get lemmas -/
theorem lt_of_getElem {x : α} {a : Array α} {idx : Nat} {hidx : idx < a.size} (_ : a[idx] = x) :
idx < a.size :=
hidx
theorem getElem?_mem {l : Array α} {i : Fin l.size} : l[i] l := by
erw [Array.mem_def, getElem_eq_data_getElem]
apply List.get_mem
@@ -505,6 +517,13 @@ theorem size_eq_length_data (as : Array α) : as.size = as.data.length := rfl
simp only [mkEmpty_eq, size_push] at *
omega
@[simp] theorem data_range (n : Nat) : (range n).data = List.range n := by
induction n <;> simp_all [range, Nat.fold, flip, List.range_succ]
@[simp]
theorem getElem_range {n : Nat} {x : Nat} (h : x < (Array.range n).size) : (Array.range n)[x] = x := by
simp [getElem_eq_data_getElem]
set_option linter.deprecated false in
@[simp] theorem reverse_data (a : Array α) : a.reverse.data = a.data.reverse := by
let rec go (as : Array α) (i j hj)
@@ -707,12 +726,27 @@ theorem mapIdx_spec (as : Array α) (f : Fin as.size → α → β)
unfold modify modifyM Id.run
split <;> simp
theorem getElem_modify {as : Array α} {x i} (h : i < as.size) :
(as.modify x f)[i]'(by simp [h]) = if x = i then f as[i] else as[i] := by
simp only [modify, modifyM, get_eq_getElem, Id.run, Id.pure_eq]
split
· simp only [Id.bind_eq, get_set _ _ _ h]; split <;> simp [*]
· rw [if_neg (mt (by rintro rfl; exact h) _)]
theorem getElem_modify_self {as : Array α} {i : Nat} (h : i < as.size) (f : α α) :
(as.modify i f)[i]'(by simp [h]) = f as[i] := by
simp [getElem_modify h]
theorem getElem_modify_of_ne {as : Array α} {i : Nat} (hj : j < as.size)
(f : α α) (h : i j) :
(as.modify i f)[j]'(by rwa [size_modify]) = as[j] := by
simp [getElem_modify hj, h]
@[deprecated getElem_modify (since := "2024-08-08")]
theorem get_modify {arr : Array α} {x i} (h : i < arr.size) :
(arr.modify x f).get i, by simp [h] =
if x = i then f (arr.get i, h) else arr.get i, h := by
simp [modify, modifyM, Id.run]; split
· simp [get_set _ _ _ h]; split <;> simp [*]
· rw [if_neg (mt (by rintro rfl; exact h) _)]
simp [getElem_modify h]
/-! ### filter -/

View File

@@ -38,8 +38,8 @@ macro "array_get_dec" : tactic =>
-- subsumed by simp
-- | with_reducible apply sizeOf_get
-- | with_reducible apply sizeOf_getElem
| (with_reducible apply Nat.lt_trans (sizeOf_get ..)); simp_arith
| (with_reducible apply Nat.lt_trans (sizeOf_getElem ..)); simp_arith
| (with_reducible apply Nat.lt_of_lt_of_le (sizeOf_get ..)); simp_arith
| (with_reducible apply Nat.lt_of_lt_of_le (sizeOf_getElem ..)); simp_arith
)
macro_rules | `(tactic| decreasing_trivial) => `(tactic| array_get_dec)
@@ -52,7 +52,7 @@ macro "array_mem_dec" : tactic =>
`(tactic| first
| with_reducible apply Array.sizeOf_lt_of_mem; assumption; done
| with_reducible
apply Nat.lt_trans (Array.sizeOf_lt_of_mem ?h)
apply Nat.lt_of_lt_of_le (Array.sizeOf_lt_of_mem ?h)
case' h => assumption
simp_arith)

View File

@@ -5,7 +5,7 @@ Authors: Markus Himmel
-/
prelude
import Init.Data.Array.Lemmas
import Init.Data.List.TakeDrop
import Init.Data.List.Nat.TakeDrop
namespace Array

View File

@@ -20,6 +20,8 @@ We define many of the bitvector operations from the
of SMT-LIBv2.
-/
set_option linter.missingDocs true
/--
A bitvector of the specified width.
@@ -34,14 +36,14 @@ structure BitVec (w : Nat) where
O(1), because we use `Fin` as the internal representation of a bitvector. -/
toFin : Fin (2^w)
@[deprecated (since := "2024-04-12")]
protected abbrev Std.BitVec := _root_.BitVec
/--
Bitvectors have decidable equality. This should be used via the instance `DecidableEq (BitVec n)`.
-/
-- We manually derive the `DecidableEq` instances for `BitVec` because
-- we want to have builtin support for bit-vector literals, and we
-- need a name for this function to implement `canUnfoldAtMatcher` at `WHNF.lean`.
def BitVec.decEq (a b : BitVec n) : Decidable (a = b) :=
match a, b with
def BitVec.decEq (x y : BitVec n) : Decidable (x = y) :=
match x, y with
| n, m =>
if h : n = m then
isTrue (h rfl)
@@ -67,9 +69,9 @@ protected def ofNat (n : Nat) (i : Nat) : BitVec n where
instance instOfNat : OfNat (BitVec n) i where ofNat := .ofNat n i
instance natCastInst : NatCast (BitVec w) := BitVec.ofNat w
/-- Given a bitvector `a`, return the underlying `Nat`. This is O(1) because `BitVec` is a
/-- Given a bitvector `x`, return the underlying `Nat`. This is O(1) because `BitVec` is a
(zero-cost) wrapper around a `Nat`. -/
protected def toNat (a : BitVec n) : Nat := a.toFin.val
protected def toNat (x : BitVec n) : Nat := x.toFin.val
/-- Return the bound in terms of toNat. -/
theorem isLt (x : BitVec w) : x.toNat < 2^w := x.toFin.isLt
@@ -121,18 +123,18 @@ section getXsb
@[inline] def getMsb (x : BitVec w) (i : Nat) : Bool := i < w && getLsb x (w-1-i)
/-- Return most-significant bit in bitvector. -/
@[inline] protected def msb (a : BitVec n) : Bool := getMsb a 0
@[inline] protected def msb (x : BitVec n) : Bool := getMsb x 0
end getXsb
section Int
/-- Interpret the bitvector as an integer stored in two's complement form. -/
protected def toInt (a : BitVec n) : Int :=
if 2 * a.toNat < 2^n then
a.toNat
protected def toInt (x : BitVec n) : Int :=
if 2 * x.toNat < 2^n then
x.toNat
else
(a.toNat : Int) - (2^n : Nat)
(x.toNat : Int) - (2^n : Nat)
/-- The `BitVec` with value `(2^n + (i mod 2^n)) mod 2^n`. -/
protected def ofInt (n : Nat) (i : Int) : BitVec n := .ofNatLt (i % (Int.ofNat (2^n))).toNat (by
@@ -213,7 +215,7 @@ instance : Neg (BitVec n) := ⟨.neg⟩
/--
Return the absolute value of a signed bitvector.
-/
protected def abs (s : BitVec n) : BitVec n := if s.msb then .neg s else s
protected def abs (x : BitVec n) : BitVec n := if x.msb then .neg x else x
/--
Multiplication for bit vectors. This can be interpreted as either signed or unsigned negation
@@ -260,12 +262,12 @@ sdiv 5#4 -2 = -2#4
sdiv (-7#4) (-2) = 3#4
```
-/
def sdiv (s t : BitVec n) : BitVec n :=
match s.msb, t.msb with
| false, false => udiv s t
| false, true => .neg (udiv s (.neg t))
| true, false => .neg (udiv (.neg s) t)
| true, true => udiv (.neg s) (.neg t)
def sdiv (x y : BitVec n) : BitVec n :=
match x.msb, y.msb with
| false, false => udiv x y
| false, true => .neg (udiv x (.neg y))
| true, false => .neg (udiv (.neg x) y)
| true, true => udiv (.neg x) (.neg y)
/--
Signed division for bit vectors using SMTLIB rules for division by zero.
@@ -274,40 +276,40 @@ Specifically, `smtSDiv x 0 = if x >= 0 then -1 else 1`
SMT-Lib name: `bvsdiv`.
-/
def smtSDiv (s t : BitVec n) : BitVec n :=
match s.msb, t.msb with
| false, false => smtUDiv s t
| false, true => .neg (smtUDiv s (.neg t))
| true, false => .neg (smtUDiv (.neg s) t)
| true, true => smtUDiv (.neg s) (.neg t)
def smtSDiv (x y : BitVec n) : BitVec n :=
match x.msb, y.msb with
| false, false => smtUDiv x y
| false, true => .neg (smtUDiv x (.neg y))
| true, false => .neg (smtUDiv (.neg x) y)
| true, true => smtUDiv (.neg x) (.neg y)
/--
Remainder for signed division rounding to zero.
SMT_Lib name: `bvsrem`.
-/
def srem (s t : BitVec n) : BitVec n :=
match s.msb, t.msb with
| false, false => umod s t
| false, true => umod s (.neg t)
| true, false => .neg (umod (.neg s) t)
| true, true => .neg (umod (.neg s) (.neg t))
def srem (x y : BitVec n) : BitVec n :=
match x.msb, y.msb with
| false, false => umod x y
| false, true => umod x (.neg y)
| true, false => .neg (umod (.neg x) y)
| true, true => .neg (umod (.neg x) (.neg y))
/--
Remainder for signed division rounded to negative infinity.
SMT_Lib name: `bvsmod`.
-/
def smod (s t : BitVec m) : BitVec m :=
match s.msb, t.msb with
| false, false => umod s t
def smod (x y : BitVec m) : BitVec m :=
match x.msb, y.msb with
| false, false => umod x y
| false, true =>
let u := umod s (.neg t)
(if u = .zero m then u else .add u t)
let u := umod x (.neg y)
(if u = .zero m then u else .add u y)
| true, false =>
let u := umod (.neg s) t
(if u = .zero m then u else .sub t u)
| true, true => .neg (umod (.neg s) (.neg t))
let u := umod (.neg x) y
(if u = .zero m then u else .sub y u)
| true, true => .neg (umod (.neg x) (.neg y))
end arithmetic
@@ -371,8 +373,8 @@ end relations
section cast
/-- `cast eq i` embeds `i` into an equal `BitVec` type. -/
@[inline] def cast (eq : n = m) (i : BitVec n) : BitVec m := .ofNatLt i.toNat (eq i.isLt)
/-- `cast eq x` embeds `x` into an equal `BitVec` type. -/
@[inline] def cast (eq : n = m) (x : BitVec n) : BitVec m := .ofNatLt x.toNat (eq x.isLt)
@[simp] theorem cast_ofNat {n m : Nat} (h : n = m) (x : Nat) :
cast h (BitVec.ofNat n x) = BitVec.ofNat m x := by
@@ -389,7 +391,7 @@ Extraction of bits `start` to `start + len - 1` from a bit vector of size `n` to
new bitvector of size `len`. If `start + len > n`, then the vector will be zero-padded in the
high bits.
-/
def extractLsb' (start len : Nat) (a : BitVec n) : BitVec len := .ofNat _ (a.toNat >>> start)
def extractLsb' (start len : Nat) (x : BitVec n) : BitVec len := .ofNat _ (x.toNat >>> start)
/--
Extraction of bits `hi` (inclusive) down to `lo` (inclusive) from a bit vector of size `n` to
@@ -397,12 +399,12 @@ yield a new bitvector of size `hi - lo + 1`.
SMT-Lib name: `extract`.
-/
def extractLsb (hi lo : Nat) (a : BitVec n) : BitVec (hi - lo + 1) := extractLsb' lo _ a
def extractLsb (hi lo : Nat) (x : BitVec n) : BitVec (hi - lo + 1) := extractLsb' lo _ x
/--
A version of `zeroExtend` that requires a proof, but is a noop.
-/
def zeroExtend' {n w : Nat} (le : n w) (x : BitVec n) : BitVec w :=
def zeroExtend' {n w : Nat} (le : n w) (x : BitVec n) : BitVec w :=
x.toNat#'(by
apply Nat.lt_of_lt_of_le x.isLt
exact Nat.pow_le_pow_of_le_right (by trivial) le)
@@ -411,8 +413,8 @@ def zeroExtend' {n w : Nat} (le : n ≤ w) (x : BitVec n) : BitVec w :=
`shiftLeftZeroExtend x n` returns `zeroExtend (w+n) x <<< n` without
needing to compute `x % 2^(2+n)`.
-/
def shiftLeftZeroExtend (msbs : BitVec w) (m : Nat) : BitVec (w+m) :=
let shiftLeftLt {x : Nat} (p : x < 2^w) (m : Nat) : x <<< m < 2^(w+m) := by
def shiftLeftZeroExtend (msbs : BitVec w) (m : Nat) : BitVec (w + m) :=
let shiftLeftLt {x : Nat} (p : x < 2^w) (m : Nat) : x <<< m < 2^(w + m) := by
simp [Nat.shiftLeft_eq, Nat.pow_add]
apply Nat.mul_lt_mul_of_pos_right p
exact (Nat.two_pow_pos m)
@@ -500,24 +502,24 @@ instance : Complement (BitVec w) := ⟨.not⟩
/--
Left shift for bit vectors. The low bits are filled with zeros. As a numeric operation, this is
equivalent to `a * 2^s`, modulo `2^n`.
equivalent to `x * 2^s`, modulo `2^n`.
SMT-Lib name: `bvshl` except this operator uses a `Nat` shift value.
-/
protected def shiftLeft (a : BitVec n) (s : Nat) : BitVec n := BitVec.ofNat n (a.toNat <<< s)
protected def shiftLeft (x : BitVec n) (s : Nat) : BitVec n := BitVec.ofNat n (x.toNat <<< s)
instance : HShiftLeft (BitVec w) Nat (BitVec w) := .shiftLeft
/--
(Logical) right shift for bit vectors. The high bits are filled with zeros.
As a numeric operation, this is equivalent to `a / 2^s`, rounding down.
As a numeric operation, this is equivalent to `x / 2^s`, rounding down.
SMT-Lib name: `bvlshr` except this operator uses a `Nat` shift value.
-/
def ushiftRight (a : BitVec n) (s : Nat) : BitVec n :=
(a.toNat >>> s)#'(by
let a, lt := a
def ushiftRight (x : BitVec n) (s : Nat) : BitVec n :=
(x.toNat >>> s)#'(by
let x, lt := x
simp only [BitVec.toNat, Nat.shiftRight_eq_div_pow, Nat.div_lt_iff_lt_mul (Nat.two_pow_pos s)]
rw [Nat.mul_one a]
rw [Nat.mul_one x]
exact Nat.mul_lt_mul_of_lt_of_le' lt (Nat.two_pow_pos s) (Nat.le_refl 1))
instance : HShiftRight (BitVec w) Nat (BitVec w) := .ushiftRight
@@ -525,15 +527,24 @@ instance : HShiftRight (BitVec w) Nat (BitVec w) := ⟨.ushiftRight⟩
/--
Arithmetic right shift for bit vectors. The high bits are filled with the
most-significant bit.
As a numeric operation, this is equivalent to `a.toInt >>> s`.
As a numeric operation, this is equivalent to `x.toInt >>> s`.
SMT-Lib name: `bvashr` except this operator uses a `Nat` shift value.
-/
def sshiftRight (a : BitVec n) (s : Nat) : BitVec n := .ofInt n (a.toInt >>> s)
def sshiftRight (x : BitVec n) (s : Nat) : BitVec n := .ofInt n (x.toInt >>> s)
instance {n} : HShiftLeft (BitVec m) (BitVec n) (BitVec m) := fun x y => x <<< y.toNat
instance {n} : HShiftRight (BitVec m) (BitVec n) (BitVec m) := fun x y => x >>> y.toNat
/--
Arithmetic right shift for bit vectors. The high bits are filled with the
most-significant bit.
As a numeric operation, this is equivalent to `a.toInt >>> s.toNat`.
SMT-Lib name: `bvashr`.
-/
def sshiftRight' (a : BitVec n) (s : BitVec m) : BitVec n := a.sshiftRight s.toNat
/-- Auxiliary function for `rotateLeft`, which does not take into account the case where
the rotation amount is greater than the bitvector width. -/
def rotateLeftAux (x : BitVec w) (n : Nat) : BitVec w :=
@@ -583,11 +594,9 @@ instance : HAppend (BitVec w) (BitVec v) (BitVec (w + v)) := ⟨.append⟩
-- TODO: write this using multiplication
/-- `replicate i x` concatenates `i` copies of `x` into a new vector of length `w*i`. -/
def replicate : (i : Nat) BitVec w BitVec (w*i)
| 0, _ => 0
| 0, _ => 0#0
| n+1, x =>
have hEq : w + w*n = w*(n + 1) := by
rw [Nat.mul_add, Nat.add_comm, Nat.mul_one]
hEq (x ++ replicate n x)
(x ++ replicate n x).cast (by rw [Nat.mul_succ]; omega)
/-!
### Cons and Concat

View File

@@ -28,6 +28,8 @@ https://github.com/mhk119/lean-smt/blob/bitvec/Smt/Data/Bitwise.lean.
-/
set_option linter.missingDocs true
open Nat Bool
namespace Bool
@@ -98,6 +100,37 @@ theorem carry_succ (i : Nat) (x y : BitVec w) (c : Bool) :
exact mod_two_pow_add_mod_two_pow_add_bool_lt_two_pow_succ ..
cases x.toNat.testBit i <;> cases y.toNat.testBit i <;> (simp; omega)
/--
If `x &&& y = 0`, then the carry bit `(x + y + 0)` is always `false` for any index `i`.
Intuitively, this is because a carry is only produced when at least two of `x`, `y`, and the
previous carry are true. However, since `x &&& y = 0`, at most one of `x, y` can be true,
and thus we never have a previous carry, which means that the sum cannot produce a carry.
-/
theorem carry_of_and_eq_zero {x y : BitVec w} (h : x &&& y = 0#w) : carry i x y false = false := by
induction i with
| zero => simp
| succ i ih =>
replace h := congrArg (·.getLsb i) h
simp_all [carry_succ]
/-- The final carry bit when computing `x + y + c` is `true` iff `x.toNat + y.toNat + c.toNat ≥ 2^w`. -/
theorem carry_width {x y : BitVec w} :
carry w x y c = decide (x.toNat + y.toNat + c.toNat 2^w) := by
simp [carry]
/--
If `x &&& y = 0`, then addition does not overflow, and thus `(x + y).toNat = x.toNat + y.toNat`.
-/
theorem toNat_add_of_and_eq_zero {x y : BitVec w} (h : x &&& y = 0#w) :
(x + y).toNat = x.toNat + y.toNat := by
rw [toNat_add]
apply Nat.mod_eq_of_lt
suffices ¬ decide (x.toNat + y.toNat + false.toNat 2^w) by
simp only [decide_eq_true_eq] at this
omega
rw [ carry_width]
simp [not_eq_true, carry_of_and_eq_zero h]
/-- Carry function for bitwise addition. -/
def adcb (x y c : Bool) : Bool × Bool := (atLeastTwo x y c, Bool.xor x (Bool.xor y c))
@@ -256,18 +289,18 @@ theorem sle_eq_carry (x y : BitVec w) :
A recurrence that describes multiplication as repeated addition.
Is useful for bitblasting multiplication.
-/
def mulRec (l r : BitVec w) (s : Nat) : BitVec w :=
let cur := if r.getLsb s then (l <<< s) else 0
def mulRec (x y : BitVec w) (s : Nat) : BitVec w :=
let cur := if y.getLsb s then (x <<< s) else 0
match s with
| 0 => cur
| s + 1 => mulRec l r s + cur
| s + 1 => mulRec x y s + cur
theorem mulRec_zero_eq (l r : BitVec w) :
mulRec l r 0 = if r.getLsb 0 then l else 0 := by
theorem mulRec_zero_eq (x y : BitVec w) :
mulRec x y 0 = if y.getLsb 0 then x else 0 := by
simp [mulRec]
theorem mulRec_succ_eq (l r : BitVec w) (s : Nat) :
mulRec l r (s + 1) = mulRec l r s + if r.getLsb (s + 1) then (l <<< (s + 1)) else 0 := rfl
theorem mulRec_succ_eq (x y : BitVec w) (s : Nat) :
mulRec x y (s + 1) = mulRec x y s + if y.getLsb (s + 1) then (x <<< (s + 1)) else 0 := rfl
/--
Recurrence lemma: truncating to `i+1` bits and then zero extending to `w`
@@ -290,32 +323,32 @@ theorem zeroExtend_truncate_succ_eq_zeroExtend_truncate_add_twoPow (x : BitVec w
simp [hik', hik'']
· ext k
simp
omega
by_cases hi : x.getLsb i <;> simp [hi] <;> omega
/--
Recurrence lemma: multiplying `l` with the first `s` bits of `r` is the
same as truncating `r` to `s` bits, then zero extending to the original length,
Recurrence lemma: multiplying `x` with the first `s` bits of `y` is the
same as truncating `y` to `s` bits, then zero extending to the original length,
and performing the multplication. -/
theorem mulRec_eq_mul_signExtend_truncate (l r : BitVec w) (s : Nat) :
mulRec l r s = l * ((r.truncate (s + 1)).zeroExtend w) := by
theorem mulRec_eq_mul_signExtend_truncate (x y : BitVec w) (s : Nat) :
mulRec x y s = x * ((y.truncate (s + 1)).zeroExtend w) := by
induction s
case zero =>
simp only [mulRec_zero_eq, ofNat_eq_ofNat, Nat.reduceAdd]
by_cases r.getLsb 0
case pos hr =>
simp only [hr, reduceIte, truncate, zeroExtend_one_eq_ofBool_getLsb_zero,
hr, ofBool_true, ofNat_eq_ofNat]
by_cases y.getLsb 0
case pos hy =>
simp only [hy, reduceIte, truncate, zeroExtend_one_eq_ofBool_getLsb_zero,
ofBool_true, ofNat_eq_ofNat]
rw [zeroExtend_ofNat_one_eq_ofNat_one_of_lt (by omega)]
simp
case neg hr =>
simp [hr, zeroExtend_one_eq_ofBool_getLsb_zero]
case neg hy =>
simp [hy, zeroExtend_one_eq_ofBool_getLsb_zero]
case succ s' hs =>
rw [mulRec_succ_eq, hs]
have heq :
(if r.getLsb (s' + 1) = true then l <<< (s' + 1) else 0) =
(l * (r &&& (BitVec.twoPow w (s' + 1)))) := by
simp only [ofNat_eq_ofNat, and_twoPow_eq]
by_cases hr : r.getLsb (s' + 1) <;> simp [hr]
(if y.getLsb (s' + 1) = true then x <<< (s' + 1) else 0) =
(x * (y &&& (BitVec.twoPow w (s' + 1)))) := by
simp only [ofNat_eq_ofNat, and_twoPow]
by_cases hy : y.getLsb (s' + 1) <;> simp [hy]
rw [heq, BitVec.mul_add, zeroExtend_truncate_succ_eq_zeroExtend_truncate_add_twoPow]
theorem getLsb_mul (x y : BitVec w) (i : Nat) :
@@ -326,4 +359,198 @@ theorem getLsb_mul (x y : BitVec w) (i : Nat) :
· simp
· omega
/-! ## shiftLeft recurrence for bitblasting -/
/--
`shiftLeftRec x y n` shifts `x` to the left by the first `n` bits of `y`.
The theorem `shiftLeft_eq_shiftLeftRec` proves the equivalence of `(x <<< y)` and `shiftLeftRec`.
Together with equations `shiftLeftRec_zero`, `shiftLeftRec_succ`,
this allows us to unfold `shiftLeft` into a circuit for bitblasting.
-/
def shiftLeftRec (x : BitVec w₁) (y : BitVec w₂) (n : Nat) : BitVec w₁ :=
let shiftAmt := (y &&& (twoPow w₂ n))
match n with
| 0 => x <<< shiftAmt
| n + 1 => (shiftLeftRec x y n) <<< shiftAmt
@[simp]
theorem shiftLeftRec_zero {x : BitVec w₁} {y : BitVec w₂} :
shiftLeftRec x y 0 = x <<< (y &&& twoPow w₂ 0) := by
simp [shiftLeftRec]
@[simp]
theorem shiftLeftRec_succ {x : BitVec w₁} {y : BitVec w₂} :
shiftLeftRec x y (n + 1) = (shiftLeftRec x y n) <<< (y &&& twoPow w₂ (n + 1)) := by
simp [shiftLeftRec]
/--
If `y &&& z = 0`, `x <<< (y ||| z) = x <<< y <<< z`.
This follows as `y &&& z = 0` implies `y ||| z = y + z`,
and thus `x <<< (y ||| z) = x <<< (y + z) = x <<< y <<< z`.
-/
theorem shiftLeft_or_of_and_eq_zero {x : BitVec w₁} {y z : BitVec w₂}
(h : y &&& z = 0#w₂) :
x <<< (y ||| z) = x <<< y <<< z := by
rw [ add_eq_or_of_and_eq_zero _ _ h,
shiftLeft_eq', toNat_add_of_and_eq_zero h]
simp [shiftLeft_add]
/--
`shiftLeftRec x y n` shifts `x` to the left by the first `n` bits of `y`.
-/
theorem shiftLeftRec_eq {x : BitVec w₁} {y : BitVec w₂} {n : Nat} :
shiftLeftRec x y n = x <<< (y.truncate (n + 1)).zeroExtend w₂ := by
induction n generalizing x y
case zero =>
ext i
simp only [shiftLeftRec_zero, twoPow_zero, Nat.reduceAdd, truncate_one,
and_one_eq_zeroExtend_ofBool_getLsb]
case succ n ih =>
simp only [shiftLeftRec_succ, and_twoPow]
rw [ih]
by_cases h : y.getLsb (n + 1)
· simp only [h, reduceIte]
rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_or_twoPow_of_getLsb_true h,
shiftLeft_or_of_and_eq_zero]
simp
· simp only [h, false_eq_true, reduceIte, shiftLeft_zero']
rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_of_getLsb_false (i := n + 1)]
simp [h]
/--
Show that `x <<< y` can be written in terms of `shiftLeftRec`.
This can be unfolded in terms of `shiftLeftRec_zero`, `shiftLeftRec_succ` for bitblasting.
-/
theorem shiftLeft_eq_shiftLeftRec (x : BitVec w₁) (y : BitVec w₂) :
x <<< y = shiftLeftRec x y (w₂ - 1) := by
rcases w₂ with rfl | w₂
· simp [of_length_zero]
· simp [shiftLeftRec_eq]
/- ### Arithmetic shift right (sshiftRight) recurrence -/
/--
`sshiftRightRec x y n` shifts `x` arithmetically/signed to the right by the first `n` bits of `y`.
The theorem `sshiftRight_eq_sshiftRightRec` proves the equivalence of `(x.sshiftRight y)` and `sshiftRightRec`.
Together with equations `sshiftRightRec_zero`, `sshiftRightRec_succ`,
this allows us to unfold `sshiftRight` into a circuit for bitblasting.
-/
def sshiftRightRec (x : BitVec w₁) (y : BitVec w₂) (n : Nat) : BitVec w₁ :=
let shiftAmt := (y &&& (twoPow w₂ n))
match n with
| 0 => x.sshiftRight' shiftAmt
| n + 1 => (sshiftRightRec x y n).sshiftRight' shiftAmt
@[simp]
theorem sshiftRightRec_zero_eq (x : BitVec w₁) (y : BitVec w₂) :
sshiftRightRec x y 0 = x.sshiftRight' (y &&& 1#w₂) := by
simp only [sshiftRightRec, twoPow_zero]
@[simp]
theorem sshiftRightRec_succ_eq (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
sshiftRightRec x y (n + 1) = (sshiftRightRec x y n).sshiftRight' (y &&& twoPow w₂ (n + 1)) := by
simp [sshiftRightRec]
/--
If `y &&& z = 0`, `x.sshiftRight (y ||| z) = (x.sshiftRight y).sshiftRight z`.
This follows as `y &&& z = 0` implies `y ||| z = y + z`,
and thus `x.sshiftRight (y ||| z) = x.sshiftRight (y + z) = (x.sshiftRight y).sshiftRight z`.
-/
theorem sshiftRight'_or_of_and_eq_zero {x : BitVec w₁} {y z : BitVec w₂}
(h : y &&& z = 0#w₂) :
x.sshiftRight' (y ||| z) = (x.sshiftRight' y).sshiftRight' z := by
simp [sshiftRight', add_eq_or_of_and_eq_zero _ _ h,
toNat_add_of_and_eq_zero h, sshiftRight_add]
theorem sshiftRightRec_eq (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
sshiftRightRec x y n = x.sshiftRight' ((y.truncate (n + 1)).zeroExtend w₂) := by
induction n generalizing x y
case zero =>
ext i
simp [twoPow_zero, Nat.reduceAdd, and_one_eq_zeroExtend_ofBool_getLsb, truncate_one]
case succ n ih =>
simp only [sshiftRightRec_succ_eq, and_twoPow, ih]
by_cases h : y.getLsb (n + 1)
· rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_or_twoPow_of_getLsb_true h,
sshiftRight'_or_of_and_eq_zero (by simp), h]
simp
· rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_of_getLsb_false (i := n + 1)
(by simp [h])]
simp [h]
/--
Show that `x.sshiftRight y` can be written in terms of `sshiftRightRec`.
This can be unfolded in terms of `sshiftRightRec_zero_eq`, `sshiftRightRec_succ_eq` for bitblasting.
-/
theorem sshiftRight_eq_sshiftRightRec (x : BitVec w₁) (y : BitVec w₂) :
(x.sshiftRight' y).getLsb i = (sshiftRightRec x y (w₂ - 1)).getLsb i := by
rcases w₂ with rfl | w₂
· simp [of_length_zero]
· simp [sshiftRightRec_eq]
/- ### Logical shift right (ushiftRight) recurrence for bitblasting -/
/--
`ushiftRightRec x y n` shifts `x` logically to the right by the first `n` bits of `y`.
The theorem `shiftRight_eq_ushiftRightRec` proves the equivalence
of `(x >>> y)` and `ushiftRightRec`.
Together with equations `ushiftRightRec_zero`, `ushiftRightRec_succ`,
this allows us to unfold `ushiftRight` into a circuit for bitblasting.
-/
def ushiftRightRec (x : BitVec w₁) (y : BitVec w₂) (n : Nat) : BitVec w₁ :=
let shiftAmt := (y &&& (twoPow w₂ n))
match n with
| 0 => x >>> shiftAmt
| n + 1 => (ushiftRightRec x y n) >>> shiftAmt
@[simp]
theorem ushiftRightRec_zero (x : BitVec w₁) (y : BitVec w₂) :
ushiftRightRec x y 0 = x >>> (y &&& twoPow w₂ 0) := by
simp [ushiftRightRec]
@[simp]
theorem ushiftRightRec_succ (x : BitVec w₁) (y : BitVec w₂) :
ushiftRightRec x y (n + 1) = (ushiftRightRec x y n) >>> (y &&& twoPow w₂ (n + 1)) := by
simp [ushiftRightRec]
/--
If `y &&& z = 0`, `x >>> (y ||| z) = x >>> y >>> z`.
This follows as `y &&& z = 0` implies `y ||| z = y + z`,
and thus `x >>> (y ||| z) = x >>> (y + z) = x >>> y >>> z`.
-/
theorem ushiftRight'_or_of_and_eq_zero {x : BitVec w₁} {y z : BitVec w₂}
(h : y &&& z = 0#w₂) :
x >>> (y ||| z) = x >>> y >>> z := by
simp [ add_eq_or_of_and_eq_zero _ _ h, toNat_add_of_and_eq_zero h, shiftRight_add]
theorem ushiftRightRec_eq (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
ushiftRightRec x y n = x >>> (y.truncate (n + 1)).zeroExtend w₂ := by
induction n generalizing x y
case zero =>
ext i
simp only [ushiftRightRec_zero, twoPow_zero, Nat.reduceAdd,
and_one_eq_zeroExtend_ofBool_getLsb, truncate_one]
case succ n ih =>
simp only [ushiftRightRec_succ, and_twoPow]
rw [ih]
by_cases h : y.getLsb (n + 1) <;> simp only [h, reduceIte]
· rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_or_twoPow_of_getLsb_true h,
ushiftRight'_or_of_and_eq_zero]
simp
· simp [zeroExtend_truncate_succ_eq_zeroExtend_truncate_of_getLsb_false, h]
/--
Show that `x >>> y` can be written in terms of `ushiftRightRec`.
This can be unfolded in terms of `ushiftRightRec_zero`, `ushiftRightRec_succ` for bitblasting.
-/
theorem shiftRight_eq_ushiftRightRec (x : BitVec w₁) (y : BitVec w₂) :
x >>> y = ushiftRightRec x y (w₂ - 1) := by
rcases w₂ with rfl | w₂
· simp [of_length_zero]
· simp [ushiftRightRec_eq]
end BitVec

View File

@@ -8,6 +8,8 @@ import Init.Data.BitVec.Lemmas
import Init.Data.Nat.Lemmas
import Init.Data.Fin.Iterate
set_option linter.missingDocs true
namespace BitVec
/--

View File

@@ -12,6 +12,8 @@ import Init.Data.Nat.Lemmas
import Init.Data.Nat.Mod
import Init.Data.Int.Bitwise.Lemmas
set_option linter.missingDocs true
namespace BitVec
/--
@@ -21,7 +23,7 @@ theorem ofFin_eq_ofNat : @BitVec.ofFin w (Fin.mk x lt) = BitVec.ofNat w x := by
simp only [BitVec.ofNat, Fin.ofNat', lt, Nat.mod_eq_of_lt]
/-- Prove equality of bitvectors in terms of nat operations. -/
theorem eq_of_toNat_eq {n} : {i j : BitVec n}, i.toNat = j.toNat i = j
theorem eq_of_toNat_eq {n} : {x y : BitVec n}, x.toNat = y.toNat x = y
| _, _, _, _, rfl => rfl
@[simp] theorem val_toFin (x : BitVec w) : x.toFin.val = x.toNat := rfl
@@ -160,6 +162,16 @@ theorem toNat_zero (n : Nat) : (0#n).toNat = 0 := by trivial
@[simp] theorem toNat_mod_cancel (x : BitVec n) : x.toNat % (2^n) = x.toNat :=
Nat.mod_eq_of_lt x.isLt
@[simp] theorem sub_toNat_mod_cancel {x : BitVec w} (h : ¬ x = 0#w) :
(2 ^ w - x.toNat) % 2 ^ w = 2 ^ w - x.toNat := by
simp only [toNat_eq, toNat_ofNat, Nat.zero_mod] at h
rw [Nat.mod_eq_of_lt (by omega)]
@[simp] theorem sub_sub_toNat_cancel {x : BitVec w} :
2 ^ w - (2 ^ w - x.toNat) = x.toNat := by
simp [Nat.sub_sub_eq_min, Nat.min_eq_right]
omega
private theorem lt_two_pow_of_le {x m n : Nat} (lt : x < 2 ^ m) (le : m n) : x < 2 ^ n :=
Nat.lt_of_lt_of_le lt (Nat.pow_le_pow_of_le_right (by trivial : 0 < 2) le)
@@ -226,12 +238,12 @@ theorem toNat_ge_of_msb_true {x : BitVec n} (p : BitVec.msb x = true) : x.toNat
/-! ### toInt/ofInt -/
/-- Prove equality of bitvectors in terms of nat operations. -/
theorem toInt_eq_toNat_cond (i : BitVec n) :
i.toInt =
if 2*i.toNat < 2^n then
(i.toNat : Int)
theorem toInt_eq_toNat_cond (x : BitVec n) :
x.toInt =
if 2*x.toNat < 2^n then
(x.toNat : Int)
else
(i.toNat : Int) - (2^n : Nat) :=
(x.toNat : Int) - (2^n : Nat) :=
rfl
theorem msb_eq_false_iff_two_mul_lt (x : BitVec w) : x.msb = false 2 * x.toNat < 2^w := by
@@ -258,13 +270,13 @@ theorem toInt_eq_toNat_bmod (x : BitVec n) : x.toInt = Int.bmod x.toNat (2^n) :=
omega
/-- Prove equality of bitvectors in terms of nat operations. -/
theorem eq_of_toInt_eq {i j : BitVec n} : i.toInt = j.toInt i = j := by
theorem eq_of_toInt_eq {x y : BitVec n} : x.toInt = y.toInt x = y := by
intro eq
simp [toInt_eq_toNat_cond] at eq
apply eq_of_toNat_eq
revert eq
have _ilt := i.isLt
have _jlt := j.isLt
have _xlt := x.isLt
have _ylt := y.isLt
split <;> split <;> omega
theorem toInt_inj (x y : BitVec n) : x.toInt = y.toInt x = y :=
@@ -298,8 +310,7 @@ theorem truncate_eq_zeroExtend {v : Nat} {x : BitVec w} :
@[simp, bv_toNat] theorem toNat_zeroExtend' {m n : Nat} (p : m n) (x : BitVec m) :
(zeroExtend' p x).toNat = x.toNat := by
unfold zeroExtend'
simp [p, x.isLt, Nat.mod_eq_of_lt]
simp [zeroExtend']
@[bv_toNat] theorem toNat_zeroExtend (i : Nat) (x : BitVec n) :
BitVec.toNat (zeroExtend i x) = x.toNat % 2^i := by
@@ -436,6 +447,12 @@ theorem zeroExtend_ofNat_one_eq_ofNat_one_of_lt {v w : Nat} (hv : 0 < v) :
have hv := Nat.testBit_one_eq_true_iff_self_eq_zero.mp hi₁
omega
/-- Truncating to width 1 produces a bitvector equal to the least significant bit. -/
theorem truncate_one {x : BitVec w} :
x.truncate 1 = ofBool (x.getLsb 0) := by
ext i
simp [show i = 0 by omega]
/-! ## extractLsb -/
@[simp]
@@ -455,10 +472,18 @@ protected theorem extractLsb_ofNat (x n : Nat) (hi lo : Nat) :
@[simp] theorem extractLsb_toNat (hi lo : Nat) (x : BitVec n) :
(extractLsb hi lo x).toNat = (x.toNat >>> lo) % 2^(hi-lo+1) := rfl
@[simp] theorem getLsb_extractLsb' (start len : Nat) (x : BitVec n) (i : Nat) :
(extractLsb' start len x).getLsb i = (i < len && x.getLsb (start+i)) := by
simp [getLsb, Nat.lt_succ]
@[simp] theorem getLsb_extract (hi lo : Nat) (x : BitVec n) (i : Nat) :
getLsb (extractLsb hi lo x) i = (i (hi-lo) && getLsb x (lo+i)) := by
unfold getLsb
simp [Nat.lt_succ]
simp [getLsb, Nat.lt_succ]
theorem extractLsb'_eq_extractLsb {w : Nat} (x : BitVec w) (start len : Nat) (h : len > 0) :
x.extractLsb' start len = (x.extractLsb (len - 1 + start) start).cast (by omega) := by
apply eq_of_toNat_eq
simp [extractLsb, show len - 1 + 1 = len by omega]
/-! ### allOnes -/
@@ -499,6 +524,13 @@ theorem or_assoc (x y z : BitVec w) :
x ||| y ||| z = x ||| (y ||| z) := by
ext i
simp [Bool.or_assoc]
instance : Std.Associative (α := BitVec n) (· ||| ·) := BitVec.or_assoc
theorem or_comm (x y : BitVec w) :
x ||| y = y ||| x := by
ext i
simp [Bool.or_comm]
instance : Std.Commutative (fun (x y : BitVec w) => x ||| y) := BitVec.or_comm
/-! ### and -/
@@ -530,6 +562,13 @@ theorem and_assoc (x y z : BitVec w) :
x &&& y &&& z = x &&& (y &&& z) := by
ext i
simp [Bool.and_assoc]
instance : Std.Associative (α := BitVec n) (· &&& ·) := BitVec.and_assoc
theorem and_comm (x y : BitVec w) :
x &&& y = y &&& x := by
ext i
simp [Bool.and_comm]
instance : Std.Commutative (fun (x y : BitVec w) => x &&& y) := BitVec.and_comm
/-! ### xor -/
@@ -546,6 +585,15 @@ theorem and_assoc (x y z : BitVec w) :
rw [ testBit_toNat, getLsb, getLsb]
simp
@[simp] theorem getMsb_xor {x y : BitVec w} :
(x ^^^ y).getMsb i = (xor (x.getMsb i) (y.getMsb i)) := by
simp only [getMsb]
by_cases h : i < w <;> simp [h]
@[simp] theorem msb_xor {x y : BitVec w} :
(x ^^^ y).msb = (xor x.msb y.msb) := by
simp [BitVec.msb]
@[simp] theorem truncate_xor {x y : BitVec w} :
(x ^^^ y).truncate k = x.truncate k ^^^ y.truncate k := by
ext
@@ -555,6 +603,13 @@ theorem xor_assoc (x y z : BitVec w) :
x ^^^ y ^^^ z = x ^^^ (y ^^^ z) := by
ext i
simp [Bool.xor_assoc]
instance : Std.Associative (fun (x y : BitVec w) => x ^^^ y) := BitVec.xor_assoc
theorem xor_comm (x y : BitVec w) :
x ^^^ y = y ^^^ x := by
ext i
simp [Bool.xor_comm]
instance : Std.Commutative (fun (x y : BitVec w) => x ^^^ y) := BitVec.xor_comm
/-! ### not -/
@@ -626,6 +681,10 @@ theorem shiftLeft_zero_eq (x : BitVec w) : x <<< 0 = x := by
apply eq_of_toNat_eq
simp
@[simp]
theorem zero_shiftLeft (n : Nat) : 0#w <<< n = 0#w := by
simp [bv_toNat]
@[simp] theorem getLsb_shiftLeft (x : BitVec m) (n) :
getLsb (x <<< n) i = (decide (i < m) && !decide (i < n) && getLsb x (i - n)) := by
rw [ testBit_toNat, getLsb]
@@ -634,6 +693,27 @@ theorem shiftLeft_zero_eq (x : BitVec w) : x <<< 0 = x := by
cases h₁ : decide (i < m) <;> cases h₂ : decide (n i) <;> cases h₃ : decide (i < n)
all_goals { simp_all <;> omega }
theorem shiftLeft_xor_distrib (x y : BitVec w) (n : Nat) :
(x ^^^ y) <<< n = (x <<< n) ^^^ (y <<< n) := by
ext i
simp only [getLsb_shiftLeft, Fin.is_lt, decide_True, Bool.true_and, getLsb_xor]
by_cases h : i < n
<;> simp [h]
theorem shiftLeft_and_distrib (x y : BitVec w) (n : Nat) :
(x &&& y) <<< n = (x <<< n) &&& (y <<< n) := by
ext i
simp only [getLsb_shiftLeft, Fin.is_lt, decide_True, Bool.true_and, getLsb_and]
by_cases h : i < n
<;> simp [h]
theorem shiftLeft_or_distrib (x y : BitVec w) (n : Nat) :
(x ||| y) <<< n = (x <<< n) ||| (y <<< n) := by
ext i
simp only [getLsb_shiftLeft, Fin.is_lt, decide_True, Bool.true_and, getLsb_or]
by_cases h : i < n
<;> simp [h]
@[simp] theorem getMsb_shiftLeft (x : BitVec w) (i) :
(x <<< i).getMsb k = x.getMsb (k + i) := by
simp only [getMsb, getLsb_shiftLeft]
@@ -691,6 +771,22 @@ theorem shiftLeft_shiftLeft {w : Nat} (x : BitVec w) (n m : Nat) :
(x <<< n) <<< m = x <<< (n + m) := by
rw [shiftLeft_add]
/-! ### shiftLeft reductions from BitVec to Nat -/
@[simp]
theorem shiftLeft_eq' {x : BitVec w₁} {y : BitVec w₂} : x <<< y = x <<< y.toNat := by rfl
@[simp]
theorem shiftLeft_zero' {x : BitVec w₁} : x <<< 0#w₂ = x := by simp
theorem shiftLeft_shiftLeft' {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₃} :
x <<< y <<< z = x <<< (y.toNat + z.toNat) := by
simp [shiftLeft_add]
theorem getLsb_shiftLeft' {x : BitVec w₁} {y : BitVec w₂} {i : Nat} :
(x <<< y).getLsb i = (decide (i < w₁) && !decide (i < y.toNat) && x.getLsb (i - y.toNat)) := by
simp [shiftLeft_eq', getLsb_shiftLeft]
/-! ### ushiftRight -/
@[simp, bv_toNat] theorem toNat_ushiftRight (x : BitVec n) (i : Nat) :
@@ -700,6 +796,31 @@ theorem shiftLeft_shiftLeft {w : Nat} (x : BitVec w) (n m : Nat) :
getLsb (x >>> i) j = getLsb x (i+j) := by
unfold getLsb ; simp
theorem ushiftRight_xor_distrib (x y : BitVec w) (n : Nat) :
(x ^^^ y) >>> n = (x >>> n) ^^^ (y >>> n) := by
ext
simp
theorem ushiftRight_and_distrib (x y : BitVec w) (n : Nat) :
(x &&& y) >>> n = (x >>> n) &&& (y >>> n) := by
ext
simp
theorem ushiftRight_or_distrib (x y : BitVec w) (n : Nat) :
(x ||| y) >>> n = (x >>> n) ||| (y >>> n) := by
ext
simp
@[simp]
theorem ushiftRight_zero_eq (x : BitVec w) : x >>> 0 = x := by
simp [bv_toNat]
/-! ### ushiftRight reductions from BitVec to Nat -/
@[simp]
theorem ushiftRight_eq' (x : BitVec w₁) (y : BitVec w₂) :
x >>> y = x >>> y.toNat := by rfl
/-! ### sshiftRight -/
theorem sshiftRight_eq {x : BitVec n} {i : Nat} :
@@ -743,7 +864,7 @@ theorem sshiftRight_eq_of_msb_true {x : BitVec w} {s : Nat} (h : x.msb = true) :
· rw [Nat.shiftRight_eq_div_pow]
apply Nat.lt_of_le_of_lt (Nat.div_le_self _ _) (by omega)
theorem getLsb_sshiftRight (x : BitVec w) (s i : Nat) :
@[simp] theorem getLsb_sshiftRight (x : BitVec w) (s i : Nat) :
getLsb (x.sshiftRight s) i =
(!decide (w i) && if s + i < w then x.getLsb (s + i) else x.msb) := by
rcases hmsb : x.msb with rfl | rfl
@@ -764,6 +885,90 @@ theorem getLsb_sshiftRight (x : BitVec w) (s i : Nat) :
Nat.not_lt, decide_eq_true_eq]
omega
theorem sshiftRight_xor_distrib (x y : BitVec w) (n : Nat) :
(x ^^^ y).sshiftRight n = (x.sshiftRight n) ^^^ (y.sshiftRight n) := by
ext i
simp only [getLsb_sshiftRight, getLsb_xor, msb_xor]
split
<;> by_cases w i
<;> simp [*]
theorem sshiftRight_and_distrib (x y : BitVec w) (n : Nat) :
(x &&& y).sshiftRight n = (x.sshiftRight n) &&& (y.sshiftRight n) := by
ext i
simp only [getLsb_sshiftRight, getLsb_and, msb_and]
split
<;> by_cases w i
<;> simp [*]
theorem sshiftRight_or_distrib (x y : BitVec w) (n : Nat) :
(x ||| y).sshiftRight n = (x.sshiftRight n) ||| (y.sshiftRight n) := by
ext i
simp only [getLsb_sshiftRight, getLsb_or, msb_or]
split
<;> by_cases w i
<;> simp [*]
/-- The msb after arithmetic shifting right equals the original msb. -/
theorem sshiftRight_msb_eq_msb {n : Nat} {x : BitVec w} :
(x.sshiftRight n).msb = x.msb := by
rw [msb_eq_getLsb_last, getLsb_sshiftRight, msb_eq_getLsb_last]
by_cases hw₀ : w = 0
· simp [hw₀]
· simp only [show ¬(w w - 1) by omega, decide_False, Bool.not_false, Bool.true_and,
ite_eq_right_iff]
intros h
simp [show n = 0 by omega]
@[simp] theorem sshiftRight_zero {x : BitVec w} : x.sshiftRight 0 = x := by
ext i
simp
theorem sshiftRight_add {x : BitVec w} {m n : Nat} :
x.sshiftRight (m + n) = (x.sshiftRight m).sshiftRight n := by
ext i
simp only [getLsb_sshiftRight, Nat.add_assoc]
by_cases h₁ : w (i : Nat)
· simp [h₁]
· simp only [h₁, decide_False, Bool.not_false, Bool.true_and]
by_cases h₂ : n + i < w
· simp [h₂]
· simp only [h₂, reduceIte]
by_cases h₃ : m + (n + i) < w
· simp [h₃]
omega
· simp [h₃, sshiftRight_msb_eq_msb]
/-! ### sshiftRight reductions from BitVec to Nat -/
@[simp]
theorem sshiftRight_eq' (x : BitVec w) : x.sshiftRight' y = x.sshiftRight y.toNat := rfl
/-! ### udiv -/
theorem udiv_eq {x y : BitVec n} : x.udiv y = BitVec.ofNat n (x.toNat / y.toNat) := by
have h : x.toNat / y.toNat < 2 ^ n := Nat.lt_of_le_of_lt (Nat.div_le_self ..) (by omega)
simp [udiv, bv_toNat, h, Nat.mod_eq_of_lt]
@[simp, bv_toNat]
theorem toNat_udiv {x y : BitVec n} : (x.udiv y).toNat = x.toNat / y.toNat := by
simp only [udiv_eq]
by_cases h : y = 0
· simp [h]
· rw [toNat_ofNat, Nat.mod_eq_of_lt]
exact Nat.lt_of_le_of_lt (Nat.div_le_self ..) (by omega)
/-! ### umod -/
theorem umod_eq {x y : BitVec n} :
x.umod y = BitVec.ofNat n (x.toNat % y.toNat) := by
have h : x.toNat % y.toNat < 2 ^ n := Nat.lt_of_le_of_lt (Nat.mod_le _ _) x.isLt
simp [umod, bv_toNat, Nat.mod_eq_of_lt h]
@[simp, bv_toNat]
theorem toNat_umod {x y : BitVec n} :
(x.umod y).toNat = x.toNat % y.toNat := rfl
/-! ### signExtend -/
/-- Equation theorem for `Int.sub` when both arguments are `Int.ofNat` -/
@@ -826,15 +1031,15 @@ theorem append_def (x : BitVec v) (y : BitVec w) :
(x ++ y).toNat = x.toNat <<< n ||| y.toNat :=
rfl
@[simp] theorem getLsb_append {v : BitVec n} {w : BitVec m} :
getLsb (v ++ w) i = bif i < m then getLsb w i else getLsb v (i - m) := by
@[simp] theorem getLsb_append {x : BitVec n} {y : BitVec m} :
getLsb (x ++ y) i = bif i < m then getLsb y i else getLsb x (i - m) := by
simp only [append_def, getLsb_or, getLsb_shiftLeftZeroExtend, getLsb_zeroExtend']
by_cases h : i < m
· simp [h]
· simp [h]; simp_all
@[simp] theorem getMsb_append {v : BitVec n} {w : BitVec m} :
getMsb (v ++ w) i = bif n i then getMsb w (i - n) else getMsb v i := by
@[simp] theorem getMsb_append {x : BitVec n} {y : BitVec m} :
getMsb (x ++ y) i = bif n i then getMsb y (i - n) else getMsb x i := by
simp [append_def]
by_cases h : n i
· simp [h]
@@ -1155,6 +1360,19 @@ theorem neg_eq_not_add (x : BitVec w) : -x = ~~~x + 1 := by
have hx : x.toNat < 2^w := x.isLt
rw [Nat.sub_sub, Nat.add_comm 1 x.toNat, Nat.sub_sub, Nat.sub_add_cancel (by omega)]
@[simp]
theorem neg_neg {x : BitVec w} : - - x = x := by
by_cases h : x = 0#w
· simp [h]
· simp [bv_toNat, h]
theorem neg_ne_iff_ne_neg {x y : BitVec w} : -x y x -y := by
constructor
all_goals
intro h h'
subst h'
simp at h
/-! ### mul -/
theorem mul_def {n} {x y : BitVec n} : x * y = (ofFin <| x.toFin * y.toFin) := by rfl
@@ -1452,12 +1670,18 @@ theorem getLsb_twoPow (i j : Nat) : (twoPow w i).getLsb j = ((i < w) && (i = j))
simp at hi
simp_all
theorem and_twoPow_eq (x : BitVec w) (i : Nat) :
@[simp]
theorem and_twoPow (x : BitVec w) (i : Nat) :
x &&& (twoPow w i) = if x.getLsb i then twoPow w i else 0#w := by
ext j
simp only [getLsb_and, getLsb_twoPow]
by_cases hj : i = j <;> by_cases hx : x.getLsb i <;> simp_all
@[simp]
theorem twoPow_and (x : BitVec w) (i : Nat) :
(twoPow w i) &&& x = if x.getLsb i then twoPow w i else 0#w := by
rw [BitVec.and_comm, and_twoPow]
@[simp]
theorem mul_twoPow_eq_shiftLeft (x : BitVec w) (i : Nat) :
x * (twoPow w i) = x <<< i := by
@@ -1471,6 +1695,14 @@ theorem mul_twoPow_eq_shiftLeft (x : BitVec w) (i : Nat) :
apply Nat.pow_dvd_pow 2 (by omega)
simp [Nat.mul_mod, hpow]
theorem twoPow_zero {w : Nat} : twoPow w 0 = 1#w := by
apply eq_of_toNat_eq
simp
@[simp]
theorem getLsb_one {w i : Nat} : (1#w).getLsb i = (decide (0 < w) && decide (0 = i)) := by
rw [ twoPow_zero, getLsb_twoPow]
/- ### zeroExtend, truncate, and bitwise operations -/
/--
@@ -1504,4 +1736,54 @@ theorem zeroExtend_truncate_succ_eq_zeroExtend_truncate_or_twoPow_of_getLsb_true
simp [hx]
· by_cases hik' : k < i + 1 <;> simp [hik, hik'] <;> omega
/-- Bitwise and of `(x : BitVec w)` with `1#w` equals zero extending `x.lsb` to `w`. -/
theorem and_one_eq_zeroExtend_ofBool_getLsb {x : BitVec w} :
(x &&& 1#w) = zeroExtend w (ofBool (x.getLsb 0)) := by
ext i
simp only [getLsb_and, getLsb_one, getLsb_zeroExtend, Fin.is_lt, decide_True, getLsb_ofBool,
Bool.true_and]
by_cases h : (0 = (i : Nat)) <;> simp [h] <;> omega
@[simp]
theorem replicate_zero_eq {x : BitVec w} : x.replicate 0 = 0#0 := by
simp [replicate]
@[simp]
theorem replicate_succ_eq {x : BitVec w} :
x.replicate (n + 1) =
(x ++ replicate n x).cast (by rw [Nat.mul_succ]; omega) := by
simp [replicate]
/--
If a number `w * n ≤ i < w * (n + 1)`, then `i - w * n` equals `i % w`.
This is true by subtracting `w * n` from the inequality, giving
`0 ≤ i - w * n < w`, which uniquely identifies `i % w`.
-/
private theorem Nat.sub_mul_eq_mod_of_lt_of_le (hlo : w * n i) (hhi : i < w * (n + 1)) :
i - w * n = i % w := by
rw [Nat.mod_def]
congr
symm
apply Nat.div_eq_of_lt_le
(by rw [Nat.mul_comm]; omega)
(by rw [Nat.mul_comm]; omega)
@[simp]
theorem getLsb_replicate {n w : Nat} (x : BitVec w) :
(x.replicate n).getLsb i =
(decide (i < w * n) && x.getLsb (i % w)) := by
induction n generalizing x
case zero => simp
case succ n ih =>
simp only [replicate_succ_eq, getLsb_cast, getLsb_append]
by_cases hi : i < w * (n + 1)
· simp only [hi, decide_True, Bool.true_and]
by_cases hi' : i < w * n
· simp [hi', ih]
· simp only [hi', decide_False, cond_false]
rw [Nat.sub_mul_eq_mod_of_lt_of_le] <;> omega
· rw [Nat.mul_succ] at hi
simp only [show ¬i < w * n by omega, decide_False, cond_false, hi, Bool.false_and]
apply BitVec.getLsb_ge (x := x) (i := i - w * n) (ge := by omega)
end BitVec

View File

@@ -438,6 +438,24 @@ Added for confluence between `if_true_left` and `ite_false_same` on
-/
@[simp] theorem eq_true_imp_eq_false : (b:Bool), (b = true b = false) (b = false) := by decide
/-! ### forall -/
theorem forall_bool' {p : Bool Prop} (b : Bool) : ( x, p x) p b p !b :=
fun h h _, h _, fun h₁, h₂ x by cases b <;> cases x <;> assumption
@[simp]
theorem forall_bool {p : Bool Prop} : ( b, p b) p false p true :=
forall_bool' false
/-! ### exists -/
theorem exists_bool' {p : Bool Prop} (b : Bool) : ( x, p x) p b p !b :=
fun x, hx by cases x <;> cases b <;> first | exact .inl _ | exact .inr _,
fun h by cases h <;> exact _, _
@[simp]
theorem exists_bool {p : Bool Prop} : ( b, p b) p false p true :=
exists_bool' false
/-! ### cond -/
@@ -531,3 +549,19 @@ export Bool (cond_eq_if)
@[simp] theorem true_eq_decide_iff {p : Prop} [h : Decidable p] : true = decide p p := by
cases h with | _ q => simp [q]
/-! ### coercions -/
/--
This should not be turned on globally as an instance because it degrades performance in Mathlib,
but may be used locally.
-/
def boolPredToPred : Coe (α Bool) (α Prop) where
coe r := fun a => Eq (r a) true
/--
This should not be turned on globally as an instance because it degrades performance in Mathlib,
but may be used locally.
-/
def boolRelToRel : Coe (α α Bool) (α α Prop) where
coe r := fun a b => Eq (r a b) true

View File

@@ -37,6 +37,10 @@ def push : ByteArray → UInt8 → ByteArray
def size : (@& ByteArray) Nat
| bs => bs.size
@[extern "lean_sarray_size", simp]
def usize (a : @& ByteArray) : USize :=
a.size.toUSize
@[extern "lean_byte_array_uget"]
def uget : (a : @& ByteArray) (i : USize) i.toNat < a.size UInt8
| bs, i, h => bs[i]
@@ -119,7 +123,7 @@ def toList (bs : ByteArray) : List UInt8 :=
TODO: avoid code duplication in the future after we improve the compiler.
-/
@[inline] unsafe def forInUnsafe {β : Type v} {m : Type v Type w} [Monad m] (as : ByteArray) (b : β) (f : UInt8 β m (ForInStep β)) : m β :=
let sz := USize.ofNat as.size
let sz := as.usize
let rec @[specialize] loop (i : USize) (b : β) : m β := do
if i < sz then
let a := as.uget i lcProof
@@ -187,6 +191,121 @@ def foldlM {β : Type v} {m : Type v → Type w} [Monad m] (f : β → UInt8 →
def foldl {β : Type v} (f : β UInt8 β) (init : β) (as : ByteArray) (start := 0) (stop := as.size) : β :=
Id.run <| as.foldlM f init start stop
/-- Iterator over the bytes (`UInt8`) of a `ByteArray`.
Typically created by `arr.iter`, where `arr` is a `ByteArray`.
An iterator is *valid* if the position `i` is *valid* for the array `arr`, meaning `0 ≤ i ≤ arr.size`
Most operations on iterators return arbitrary values if the iterator is not valid. The functions in
the `ByteArray.Iterator` API should rule out the creation of invalid iterators, with two exceptions:
- `Iterator.next iter` is invalid if `iter` is already at the end of the array (`iter.atEnd` is
`true`)
- `Iterator.forward iter n`/`Iterator.nextn iter n` is invalid if `n` is strictly greater than the
number of remaining bytes.
-/
structure Iterator where
/-- The array the iterator is for. -/
array : ByteArray
/-- The current position.
This position is not necessarily valid for the array, for instance if one keeps calling
`Iterator.next` when `Iterator.atEnd` is true. If the position is not valid, then the
current byte is `(default : UInt8)`. -/
idx : Nat
deriving Inhabited
/-- Creates an iterator at the beginning of an array. -/
def mkIterator (arr : ByteArray) : Iterator :=
arr, 0
@[inherit_doc mkIterator]
abbrev iter := mkIterator
/-- The size of an array iterator is the number of bytes remaining. -/
instance : SizeOf Iterator where
sizeOf i := i.array.size - i.idx
theorem Iterator.sizeOf_eq (i : Iterator) : sizeOf i = i.array.size - i.idx :=
rfl
namespace Iterator
/-- Number of bytes remaining in the iterator. -/
def remainingBytes : Iterator Nat
| arr, i => arr.size - i
@[inherit_doc Iterator.idx]
def pos := Iterator.idx
/-- The byte at the current position.
On an invalid position, returns `(default : UInt8)`. -/
@[inline]
def curr : Iterator UInt8
| arr, i =>
if h:i < arr.size then
arr[i]'h
else
default
/-- Moves the iterator's position forward by one byte, unconditionally.
It is only valid to call this function if the iterator is not at the end of the array, *i.e.*
`Iterator.atEnd` is `false`; otherwise, the resulting iterator will be invalid. -/
@[inline]
def next : Iterator Iterator
| arr, i => arr, i + 1
/-- Decreases the iterator's position.
If the position is zero, this function is the identity. -/
@[inline]
def prev : Iterator Iterator
| arr, i => arr, i - 1
/-- True if the iterator is past the array's last byte. -/
@[inline]
def atEnd : Iterator Bool
| arr, i => i arr.size
/-- True if the iterator is not past the array's last byte. -/
@[inline]
def hasNext : Iterator Bool
| arr, i => i < arr.size
/-- True if the position is not zero. -/
@[inline]
def hasPrev : Iterator Bool
| _, i => i > 0
/-- Moves the iterator's position to the end of the array.
Note that `i.toEnd.atEnd` is always `true`. -/
@[inline]
def toEnd : Iterator Iterator
| arr, _ => arr, arr.size
/-- Moves the iterator's position several bytes forward.
The resulting iterator is only valid if the number of bytes to skip is less than or equal to
the number of bytes left in the iterator. -/
@[inline]
def forward : Iterator Nat Iterator
| arr, i, f => arr, i + f
@[inherit_doc forward, inline]
def nextn : Iterator Nat Iterator := forward
/-- Moves the iterator's position several bytes back.
If asked to go back more bytes than available, stops at the beginning of the array. -/
@[inline]
def prevn : Iterator Nat Iterator
| arr, i, f => arr, i - f
end Iterator
end ByteArray
def List.toByteArray (bs : List UInt8) : ByteArray :=

View File

@@ -149,6 +149,9 @@ instance : Inhabited (Fin (no_index (n+1))) where
@[simp] theorem zero_eta : (0, Nat.zero_lt_succ _ : Fin (n + 1)) = 0 := rfl
theorem ne_of_val_ne {i j : Fin n} (h : val i val j) : i j :=
fun h' => absurd (val_eq_of_eq h') h
theorem val_ne_of_ne {i j : Fin n} (h : i j) : val i val j :=
fun h' => absurd (eq_of_val_eq h') h

View File

@@ -37,6 +37,10 @@ def push : FloatArray → Float → FloatArray
def size : (@& FloatArray) Nat
| ds => ds.size
@[extern "lean_sarray_size", simp]
def usize (a : @& FloatArray) : USize :=
a.size.toUSize
@[extern "lean_float_array_uget"]
def uget : (a : @& FloatArray) (i : USize) i.toNat < a.size Float
| ds, i, h => ds[i]
@@ -90,7 +94,7 @@ partial def toList (ds : FloatArray) : List Float :=
-/
-- TODO: avoid code duplication in the future after we improve the compiler.
@[inline] unsafe def forInUnsafe {β : Type v} {m : Type v Type w} [Monad m] (as : FloatArray) (b : β) (f : Float β m (ForInStep β)) : m β :=
let sz := USize.ofNat as.size
let sz := as.usize
let rec @[specialize] loop (i : USize) (b : β) : m β := do
if i < sz then
let a := as.uget i lcProof

View File

@@ -322,8 +322,8 @@ protected def pow (m : Int) : Nat → Int
| 0 => 1
| succ n => Int.pow m n * m
instance : HPow Int Nat Int where
hPow := Int.pow
instance : NatPow Int where
pow := Int.pow
instance : LawfulBEq Int where
eq_of_beq h := by simp [BEq.beq] at h; assumption

View File

@@ -4,11 +4,22 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Init.Data.List.Attach
import Init.Data.List.Basic
import Init.Data.List.BasicAux
import Init.Data.List.Control
import Init.Data.List.Lemmas
import Init.Data.List.Attach
import Init.Data.List.Count
import Init.Data.List.Erase
import Init.Data.List.Find
import Init.Data.List.Impl
import Init.Data.List.TakeDrop
import Init.Data.List.Lemmas
import Init.Data.List.MinMax
import Init.Data.List.Monadic
import Init.Data.List.Nat
import Init.Data.List.Notation
import Init.Data.List.Pairwise
import Init.Data.List.Sublist
import Init.Data.List.TakeDrop
import Init.Data.List.Zip
import Init.Data.List.Perm
import Init.Data.List.Sort

View File

@@ -4,7 +4,8 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
prelude
import Init.Data.List.Lemmas
import Init.Data.List.Count
import Init.Data.Subtype
namespace List
@@ -44,3 +45,155 @@ Unsafe implementation of `attachWith`, taking advantage of the fact that the rep
| nil, hL' => rfl
| cons _ L', hL' => congrArg _ <| go L' fun _ hx => hL' (.tail _ hx)
exact go L h'
@[simp] theorem attach_nil : ([] : List α).attach = [] := rfl
@[simp]
theorem pmap_eq_map (p : α Prop) (f : α β) (l : List α) (H) :
@pmap _ _ p (fun a _ => f a) l H = map f l := by
induction l
· rfl
· simp only [*, pmap, map]
theorem pmap_congr {p q : α Prop} {f : a, p a β} {g : a, q a β} (l : List α) {H₁ H₂}
(h : a l, (h₁ h₂), f a h₁ = g a h₂) : pmap f l H₁ = pmap g l H₂ := by
induction l with
| nil => rfl
| cons x l ih => rw [pmap, pmap, h _ (mem_cons_self _ _), ih fun a ha => h a (mem_cons_of_mem _ ha)]
theorem map_pmap {p : α Prop} (g : β γ) (f : a, p a β) (l H) :
map g (pmap f l H) = pmap (fun a h => g (f a h)) l H := by
induction l
· rfl
· simp only [*, pmap, map]
theorem pmap_map {p : β Prop} (g : b, p b γ) (f : α β) (l H) :
pmap g (map f l) H = pmap (fun a h => g (f a) h) l fun a h => H _ (mem_map_of_mem _ h) := by
induction l
· rfl
· simp only [*, pmap, map]
theorem pmap_eq_map_attach {p : α Prop} (f : a, p a β) (l H) :
pmap f l H = l.attach.map fun x => f x.1 (H _ x.2) := by
rw [attach, attachWith, map_pmap]; exact pmap_congr l fun _ _ _ _ => rfl
theorem attach_map_coe (l : List α) (f : α β) :
(l.attach.map fun (i : {i // i l}) => f i) = l.map f := by
rw [attach, attachWith, map_pmap]; exact pmap_eq_map _ _ _ _
theorem attach_map_val (l : List α) (f : α β) : (l.attach.map fun i => f i.val) = l.map f :=
attach_map_coe _ _
@[simp]
theorem attach_map_subtype_val (l : List α) : l.attach.map Subtype.val = l :=
(attach_map_coe _ _).trans l.map_id
theorem countP_attach (l : List α) (p : α Bool) : l.attach.countP (fun a : {x // x l} => p a) = l.countP p := by
simp only [ Function.comp_apply (g := Subtype.val), countP_map, attach_map_subtype_val]
@[simp]
theorem count_attach [DecidableEq α] (l : List α) (a : {x // x l}) : l.attach.count a = l.count a :=
Eq.trans (countP_congr fun _ _ => by simp [Subtype.ext_iff]) <| countP_attach _ _
@[simp]
theorem mem_attach (l : List α) : x, x l.attach
| a, h => by
have := mem_map.1 (by rw [attach_map_subtype_val] <;> exact h)
rcases this with _, _, m, rfl
exact m
@[simp]
theorem mem_pmap {p : α Prop} {f : a, p a β} {l H b} :
b pmap f l H (a : _) (h : a l), f a (H a h) = b := by
simp only [pmap_eq_map_attach, mem_map, mem_attach, true_and, Subtype.exists, eq_comm]
@[simp]
theorem length_pmap {p : α Prop} {f : a, p a β} {l H} : length (pmap f l H) = length l := by
induction l
· rfl
· simp only [*, pmap, length]
@[simp]
theorem length_attach (L : List α) : L.attach.length = L.length :=
length_pmap
@[simp]
theorem pmap_eq_nil {p : α Prop} {f : a, p a β} {l H} : pmap f l H = [] l = [] := by
rw [ length_eq_zero, length_pmap, length_eq_zero]
@[simp]
theorem attach_eq_nil (l : List α) : l.attach = [] l = [] :=
pmap_eq_nil
theorem getLast_pmap (p : α Prop) (f : a, p a β) (l : List α)
(hl₁ : a l, p a) (hl₂ : l []) :
(l.pmap f hl₁).getLast (mt List.pmap_eq_nil.1 hl₂) =
f (l.getLast hl₂) (hl₁ _ (List.getLast_mem hl₂)) := by
induction l with
| nil => apply (hl₂ rfl).elim
| cons l_hd l_tl l_ih =>
by_cases hl_tl : l_tl = []
· simp [hl_tl]
· simp only [pmap]
rw [getLast_cons, l_ih _ hl_tl]
simp only [getLast_cons hl_tl]
theorem getElem?_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) (n : Nat) :
(pmap f l h)[n]? = Option.pmap f l[n]? fun x H => h x (getElem?_mem H) := by
induction l generalizing n with
| nil => simp
| cons hd tl hl =>
rcases n with n
· simp only [Option.pmap]
split <;> simp_all
· simp only [hl, pmap, Option.pmap, getElem?_cons_succ]
split <;> rename_i h₁ _ <;> split <;> rename_i h₂ _
· simp_all
· simp at h₂
simp_all
· simp_all
· simp_all
theorem get?_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) (n : Nat) :
get? (pmap f l h) n = Option.pmap f (get? l n) fun x H => h x (get?_mem H) := by
simp only [get?_eq_getElem?]
simp [getElem?_pmap, h]
theorem getElem_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) {n : Nat}
(hn : n < (pmap f l h).length) :
(pmap f l h)[n] =
f (l[n]'(@length_pmap _ _ p f l h hn))
(h _ (getElem_mem l n (@length_pmap _ _ p f l h hn))) := by
induction l generalizing n with
| nil =>
simp only [length, pmap] at hn
exact absurd hn (Nat.not_lt_of_le n.zero_le)
| cons hd tl hl =>
cases n
· simp
· simp [hl]
theorem get_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) {n : Nat}
(hn : n < (pmap f l h).length) :
get (pmap f l h) n, hn =
f (get l n, @length_pmap _ _ p f l h hn)
(h _ (get_mem l n (@length_pmap _ _ p f l h hn))) := by
simp only [get_eq_getElem]
simp [getElem_pmap]
theorem pmap_append {p : ι Prop} (f : a : ι, p a α) (l₁ l₂ : List ι)
(h : a l₁ ++ l₂, p a) :
(l₁ ++ l₂).pmap f h =
(l₁.pmap f fun a ha => h a (mem_append_left l₂ ha)) ++
l₂.pmap f fun a ha => h a (mem_append_right l₁ ha) := by
induction l₁ with
| nil => rfl
| cons _ _ ih =>
dsimp only [pmap, cons_append]
rw [ih]
theorem pmap_append' {p : α Prop} (f : a : α, p a β) (l₁ l₂ : List α)
(h₁ : a l₁, p a) (h₂ : a l₂, p a) :
((l₁ ++ l₂).pmap f fun a ha => (List.mem_append.1 ha).elim (h₁ a) (h₂ a)) =
l₁.pmap f h₁ ++ l₂.pmap f h₂ :=
pmap_append f l₁ l₂ _

View File

@@ -27,24 +27,32 @@ Recall that `length`, `get`, `set`, `foldl`, and `concat` have already been defi
The operations are organized as follow:
* Equality: `beq`, `isEqv`.
* Lexicographic ordering: `lt`, `le`, and instances.
* Head and tail operators: `head`, `head?`, `headD?`, `tail`, `tail?`, `tailD`.
* Basic operations:
`map`, `filter`, `filterMap`, `foldr`, `append`, `join`, `pure`, `bind`, `replicate`, and `reverse`.
`map`, `filter`, `filterMap`, `foldr`, `append`, `join`, `pure`, `bind`, `replicate`, and
`reverse`.
* Additional functions defined in terms of these: `leftpad`, `rightPad`, and `reduceOption`.
* List membership: `isEmpty`, `elem`, `contains`, `mem` (and the `∈` notation),
and decidability for predicates quantifying over membership in a `List`.
* Sublists: `take`, `drop`, `takeWhile`, `dropWhile`, `partition`, `dropLast`,
`isPrefixOf`, `isPrefixOf?`, `isSuffixOf`, `isSuffixOf?`, `Subset`, `Sublist`, `rotateLeft` and `rotateRight`.
* Manipulating elements: `replace`, `insert`, `erase`, `eraseP`, `eraseIdx`, `find?`, `findSome?`, and `lookup`.
`isPrefixOf`, `isPrefixOf?`, `isSuffixOf`, `isSuffixOf?`, `Subset`, `Sublist`,
`rotateLeft` and `rotateRight`.
* Manipulating elements: `replace`, `insert`, `erase`, `eraseP`, `eraseIdx`.
* Finding elements: `find?`, `findSome?`, `findIdx`, `indexOf`, `findIdx?`, `indexOf?`,
`countP`, `count`, and `lookup`.
* Logic: `any`, `all`, `or`, and `and`.
* Zippers: `zipWith`, `zip`, `zipWithAll`, and `unzip`.
* Ranges and enumeration: `range`, `iota`, `enumFrom`, and `enum`.
* Minima and maxima: `minimum?` and `maximum?`.
* Other functions: `intersperse`, `intercalate`, `eraseDups`, `eraseReps`, `span`, `groupBy`, `removeAll`
* Other functions: `intersperse`, `intercalate`, `eraseDups`, `eraseReps`, `span`, `groupBy`,
`removeAll`
(currently these functions are mostly only used in meta code,
and do not have API suitable for verification).
Further operations are defined in `Init.Data.List.BasicAux` (because they use `Array` in their implementations), namely:
Further operations are defined in `Init.Data.List.BasicAux`
(because they use `Array` in their implementations), namely:
* Variant getters: `get!`, `get?`, `getD`, `getLast`, `getLast!`, `getLast?`, and `getLastD`.
* Head and tail: `head`, `head!`, `head?`, `headD`, `tail!`, `tail?`, and `tailD`.
* Head and tail: `head!`, `tail!`.
* Other operations on sublists: `partitionMap`, `rotateLeft`, and `rotateRight`.
-/
@@ -315,6 +323,16 @@ def headD : (as : List α) → (fallback : α) → α
@[simp 1100] theorem headD_nil : @headD α [] d = d := rfl
@[simp 1100] theorem headD_cons : @headD α (a::l) d = a := rfl
/-! ### tail -/
/-- Get the tail of a nonempty list, or return `[]` for `[]`. -/
def tail : List α List α
| [] => []
| _::as => as
@[simp] theorem tail_nil : @tail α [] = [] := rfl
@[simp] theorem tail_cons : @tail α (a::as) = as := rfl
/-! ### tail? -/
/--
@@ -577,6 +595,28 @@ theorem replicate_succ (a : α) (n) : replicate (n+1) a = a :: replicate n a :=
| zero => simp
| succ n ih => simp only [ih, replicate_succ, length_cons, Nat.succ_eq_add_one]
/-! ## Additional functions -/
/-! ### leftpad and rightpad -/
/--
Pads `l : List α` on the left with repeated occurrences of `a : α` until it is of length `n`.
If `l` is initially larger than `n`, just return `l`.
-/
def leftpad (n : Nat) (a : α) (l : List α) : List α := replicate (n - length l) a ++ l
/--
Pads `l : List α` on the right with repeated occurrences of `a : α` until it is of length `n`.
If `l` is initially larger than `n`, just return `l`.
-/
def rightpad (n : Nat) (a : α) (l : List α) : List α := l ++ replicate (n - length l) a
/-! ### reduceOption -/
/-- Drop `none`s from a list, and replace each remaining `some a` with `a`. -/
@[inline] def reduceOption {α} : List (Option α) List α :=
List.filterMap id
/-! ## List membership
* `L.contains a : Bool` determines, using a `[BEq α]` instance, whether `L` contains an element `· == a`.
@@ -719,7 +759,7 @@ def take : Nat → List α → List α
@[simp] theorem take_nil : ([] : List α).take i = [] := by cases i <;> rfl
@[simp] theorem take_zero (l : List α) : l.take 0 = [] := rfl
@[simp] theorem take_cons_succ : (a::as).take (i+1) = a :: as.take i := rfl
@[simp] theorem take_succ_cons : (a::as).take (i+1) = a :: as.take i := rfl
/-! ### drop -/
@@ -826,46 +866,6 @@ def dropLast {α} : List α → List α
have ih := length_dropLast_cons b bs
simp [dropLast, ih]
/-! ### isPrefixOf -/
/-- `isPrefixOf l₁ l₂` returns `true` Iff `l₁` is a prefix of `l₂`.
That is, there exists a `t` such that `l₂ == l₁ ++ t`. -/
def isPrefixOf [BEq α] : List α List α Bool
| [], _ => true
| _, [] => false
| a::as, b::bs => a == b && isPrefixOf as bs
@[simp] theorem isPrefixOf_nil_left [BEq α] : isPrefixOf ([] : List α) l = true := by
simp [isPrefixOf]
@[simp] theorem isPrefixOf_cons_nil [BEq α] : isPrefixOf (a::as) ([] : List α) = false := rfl
theorem isPrefixOf_cons₂ [BEq α] {a : α} :
isPrefixOf (a::as) (b::bs) = (a == b && isPrefixOf as bs) := rfl
/-! ### isPrefixOf? -/
/-- `isPrefixOf? l₁ l₂` returns `some t` when `l₂ == l₁ ++ t`. -/
def isPrefixOf? [BEq α] : List α List α Option (List α)
| [], l₂ => some l₂
| _, [] => none
| (x₁ :: l₁), (x₂ :: l₂) =>
if x₁ == x₂ then isPrefixOf? l₁ l₂ else none
/-! ### isSuffixOf -/
/-- `isSuffixOf l₁ l₂` returns `true` Iff `l₁` is a suffix of `l₂`.
That is, there exists a `t` such that `l₂ == t ++ l₁`. -/
def isSuffixOf [BEq α] (l₁ l₂ : List α) : Bool :=
isPrefixOf l₁.reverse l₂.reverse
@[simp] theorem isSuffixOf_nil_left [BEq α] : isSuffixOf ([] : List α) l = true := by
simp [isSuffixOf]
/-! ### isSuffixOf? -/
/-- `isSuffixOf? l₁ l₂` returns `some t` when `l₂ == t ++ l₁`.-/
def isSuffixOf? [BEq α] (l₁ l₂ : List α) : Option (List α) :=
Option.map List.reverse <| isPrefixOf? l₁.reverse l₂.reverse
/-! ### Subset -/
/--
@@ -900,6 +900,88 @@ def isSublist [BEq α] : List α → List α → Bool
then tl₁.isSublist tl₂
else l₁.isSublist tl₂
/-! ### IsPrefix / isPrefixOf / isPrefixOf? -/
/--
`IsPrefix l₁ l₂`, or `l₁ <+: l₂`, means that `l₁` is a prefix of `l₂`,
that is, `l₂` has the form `l₁ ++ t` for some `t`.
-/
def IsPrefix (l₁ : List α) (l₂ : List α) : Prop := Exists fun t => l₁ ++ t = l₂
@[inherit_doc] infixl:50 " <+: " => IsPrefix
/-- `isPrefixOf l₁ l₂` returns `true` Iff `l₁` is a prefix of `l₂`.
That is, there exists a `t` such that `l₂ == l₁ ++ t`. -/
def isPrefixOf [BEq α] : List α List α Bool
| [], _ => true
| _, [] => false
| a::as, b::bs => a == b && isPrefixOf as bs
@[simp] theorem isPrefixOf_nil_left [BEq α] : isPrefixOf ([] : List α) l = true := by
simp [isPrefixOf]
@[simp] theorem isPrefixOf_cons_nil [BEq α] : isPrefixOf (a::as) ([] : List α) = false := rfl
theorem isPrefixOf_cons₂ [BEq α] {a : α} :
isPrefixOf (a::as) (b::bs) = (a == b && isPrefixOf as bs) := rfl
/-- `isPrefixOf? l₁ l₂` returns `some t` when `l₂ == l₁ ++ t`. -/
def isPrefixOf? [BEq α] : List α List α Option (List α)
| [], l₂ => some l₂
| _, [] => none
| (x₁ :: l₁), (x₂ :: l₂) =>
if x₁ == x₂ then isPrefixOf? l₁ l₂ else none
/-! ### IsSuffix / isSuffixOf / isSuffixOf? -/
/-- `isSuffixOf l₁ l₂` returns `true` Iff `l₁` is a suffix of `l₂`.
That is, there exists a `t` such that `l₂ == t ++ l₁`. -/
def isSuffixOf [BEq α] (l₁ l₂ : List α) : Bool :=
isPrefixOf l₁.reverse l₂.reverse
@[simp] theorem isSuffixOf_nil_left [BEq α] : isSuffixOf ([] : List α) l = true := by
simp [isSuffixOf]
/-- `isSuffixOf? l₁ l₂` returns `some t` when `l₂ == t ++ l₁`.-/
def isSuffixOf? [BEq α] (l₁ l₂ : List α) : Option (List α) :=
Option.map List.reverse <| isPrefixOf? l₁.reverse l₂.reverse
/--
`IsSuffix l₁ l₂`, or `l₁ <:+ l₂`, means that `l₁` is a suffix of `l₂`,
that is, `l₂` has the form `t ++ l₁` for some `t`.
-/
def IsSuffix (l₁ : List α) (l₂ : List α) : Prop := Exists fun t => t ++ l₁ = l₂
@[inherit_doc] infixl:50 " <:+ " => IsSuffix
/-! ### IsInfix -/
/--
`IsInfix l₁ l₂`, or `l₁ <:+: l₂`, means that `l₁` is a contiguous
substring of `l₂`, that is, `l₂` has the form `s ++ l₁ ++ t` for some `s, t`.
-/
def IsInfix (l₁ : List α) (l₂ : List α) : Prop := Exists fun s => Exists fun t => s ++ l₁ ++ t = l₂
@[inherit_doc] infixl:50 " <:+: " => IsInfix
/-! ### splitAt -/
/--
Split a list at an index.
```
splitAt 2 [a, b, c] = ([a, b], [c])
```
-/
def splitAt (n : Nat) (l : List α) : List α × List α := go l n [] where
/--
Auxiliary for `splitAt`:
`splitAt.go l xs n acc = (acc.reverse ++ take n xs, drop n xs)` if `n < xs.length`,
and `(l, [])` otherwise.
-/
go : List α Nat List α List α × List α
| [], _, _ => (l, []) -- This branch ensures the pointer equality of the result with the input
-- without any runtime branching cost.
| x :: xs, n+1, acc => go xs n (x :: acc)
| xs, _, acc => (acc.reverse, xs)
/-! ### rotateLeft -/
/--
@@ -1058,6 +1140,8 @@ def eraseIdx : List α → Nat → List α
@[simp] theorem eraseIdx_cons_zero : (a::as).eraseIdx 0 = as := rfl
@[simp] theorem eraseIdx_cons_succ : (a::as).eraseIdx (i+1) = a :: as.eraseIdx i := rfl
/-! Finding elements -/
/-! ### find? -/
/--
@@ -1095,6 +1179,50 @@ theorem findSome?_cons {f : α → Option β} :
(a::as).findSome? f = match f a with | some b => some b | none => as.findSome? f :=
rfl
/-! ### findIdx -/
/-- Returns the index of the first element satisfying `p`, or the length of the list otherwise. -/
@[inline] def findIdx (p : α Bool) (l : List α) : Nat := go l 0 where
/-- Auxiliary for `findIdx`: `findIdx.go p l n = findIdx p l + n` -/
@[specialize] go : List α Nat Nat
| [], n => n
| a :: l, n => bif p a then n else go l (n + 1)
@[simp] theorem findIdx_nil {α : Type _} (p : α Bool) : [].findIdx p = 0 := rfl
/-! ### indexOf -/
/-- Returns the index of the first element equal to `a`, or the length of the list otherwise. -/
def indexOf [BEq α] (a : α) : List α Nat := findIdx (· == a)
@[simp] theorem indexOf_nil [BEq α] : ([] : List α).indexOf x = 0 := rfl
/-! ### findIdx? -/
/-- Return the index of the first occurrence of an element satisfying `p`. -/
def findIdx? (p : α Bool) : List α (start : Nat := 0) Option Nat
| [], _ => none
| a :: l, i => if p a then some i else findIdx? p l (i + 1)
/-! ### indexOf? -/
/-- Return the index of the first occurrence of `a` in the list. -/
@[inline] def indexOf? [BEq α] (a : α) : List α Option Nat := findIdx? (· == a)
/-! ### countP -/
/-- `countP p l` is the number of elements of `l` that satisfy `p`. -/
@[inline] def countP (p : α Bool) (l : List α) : Nat := go l 0 where
/-- Auxiliary for `countP`: `countP.go p l acc = countP p l + acc`. -/
@[specialize] go : List α Nat Nat
| [], acc => acc
| x :: xs, acc => bif p x then go xs (acc + 1) else go xs acc
/-! ### count -/
/-- `count a l` is the number of occurrences of `a` in `l`. -/
@[inline] def count [BEq α] (a : α) : List α Nat := countP (· == a)
/-! ### lookup -/
/--
@@ -1115,6 +1243,36 @@ theorem lookup_cons [BEq α] {k : α} :
((k,b)::es).lookup a = match a == k with | true => some b | false => es.lookup a :=
rfl
/-! ## Permutations -/
/-! ### Perm -/
/--
`Perm l₁ l₂` or `l₁ ~ l₂` asserts that `l₁` and `l₂` are permutations
of each other. This is defined by induction using pairwise swaps.
-/
inductive Perm : List α List α Prop
/-- `[] ~ []` -/
| nil : Perm [] []
/-- `l₁ ~ l₂ → x::l₁ ~ x::l₂` -/
| cons (x : α) {l₁ l₂ : List α} : Perm l₁ l₂ Perm (x :: l₁) (x :: l₂)
/-- `x::y::l ~ y::x::l` -/
| swap (x y : α) (l : List α) : Perm (y :: x :: l) (x :: y :: l)
/-- `Perm` is transitive. -/
| trans {l₁ l₂ l₃ : List α} : Perm l₁ l₂ Perm l₂ l₃ Perm l₁ l₃
@[inherit_doc] scoped infixl:50 " ~ " => Perm
/-! ### isPerm -/
/--
`O(|l₁| * |l₂|)`. Computes whether `l₁` is a permutation of `l₂`. See `isPerm_iff` for a
characterization in terms of `List.Perm`.
-/
def isPerm [BEq α] : List α List α Bool
| [], l₂ => l₂.isEmpty
| a :: l₁, l₂ => l₂.contains a && l₁.isPerm (l₂.erase a)
/-! ## Logical operations -/
/-! ### any -/
@@ -1236,6 +1394,14 @@ def unzip : List (α × β) → List α × List β
/-! ## Ranges and enumeration -/
/-- Sum of a list of natural numbers. -/
-- This is not in the `List` namespace as later `List.sum` will be defined polymorphically.
protected def _root_.Nat.sum (l : List Nat) : Nat := l.foldr (·+·) 0
@[simp] theorem _root_.Nat.sum_nil : Nat.sum ([] : List Nat) = 0 := rfl
@[simp] theorem _root_.Nat.sum_cons (a : Nat) (l : List Nat) :
Nat.sum (a::l) = a + Nat.sum l := rfl
/-! ### range -/
/--
@@ -1251,6 +1417,14 @@ where
@[simp] theorem range_zero : range 0 = [] := rfl
/-! ### range' -/
/-- `range' start len step` is the list of numbers `[start, start+step, ..., start+(len-1)*step]`.
It is intended mainly for proving properties of `range` and `iota`. -/
def range' : (start len : Nat) (step : Nat := 1) List Nat
| _, 0, _ => []
| s, n+1, step => s :: range' (s+step) n step
/-! ### iota -/
/--

View File

@@ -192,7 +192,7 @@ macro "sizeOf_list_dec" : tactic =>
`(tactic| first
| with_reducible apply sizeOf_lt_of_mem; assumption; done
| with_reducible
apply Nat.lt_trans (sizeOf_lt_of_mem ?h)
apply Nat.lt_of_lt_of_le (sizeOf_lt_of_mem ?h)
case' h => assumption
simp_arith)

View File

@@ -127,12 +127,12 @@ results `y` for which `f x` returns `some y`.
@[inline]
def filterMapM {m : Type u Type v} [Monad m] {α β : Type u} (f : α m (Option β)) (as : List α) : m (List β) :=
let rec @[specialize] loop
| [], bs => pure bs
| [], bs => pure bs.reverse
| a :: as, bs => do
match ( f a) with
| none => loop as bs
| some b => loop as (b::bs)
loop as.reverse []
loop as []
/--
Folds a monadic function over a list from left to right:
@@ -227,6 +227,8 @@ def findSomeM? {m : Type u → Type v} [Monad m] {α : Type w} {β : Type u} (f
instance : ForIn m (List α) α where
forIn := List.forIn
@[simp] theorem forIn_eq_forIn [Monad m] : @List.forIn α β m _ = forIn := rfl
@[simp] theorem forIn_nil [Monad m] (f : α β m (ForInStep β)) (b : β) : forIn [] b f = pure b :=
rfl

View File

@@ -0,0 +1,250 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Sublist
/-!
# Lemmas about `List.countP` and `List.count`.
-/
namespace List
open Nat
/-! ### countP -/
section countP
variable (p q : α Bool)
@[simp] theorem countP_nil : countP p [] = 0 := rfl
protected theorem countP_go_eq_add (l) : countP.go p l n = n + countP.go p l 0 := by
induction l generalizing n with
| nil => rfl
| cons head tail ih =>
unfold countP.go
rw [ih (n := n + 1), ih (n := n), ih (n := 1)]
if h : p head then simp [h, Nat.add_assoc] else simp [h]
@[simp] theorem countP_cons_of_pos (l) (pa : p a) : countP p (a :: l) = countP p l + 1 := by
have : countP.go p (a :: l) 0 = countP.go p l 1 := show cond .. = _ by rw [pa]; rfl
unfold countP
rw [this, Nat.add_comm, List.countP_go_eq_add]
@[simp] theorem countP_cons_of_neg (l) (pa : ¬p a) : countP p (a :: l) = countP p l := by
simp [countP, countP.go, pa]
theorem countP_cons (a : α) (l) : countP p (a :: l) = countP p l + if p a then 1 else 0 := by
by_cases h : p a <;> simp [h]
theorem length_eq_countP_add_countP (l) : length l = countP p l + countP (fun a => ¬p a) l := by
induction l with
| nil => rfl
| cons x h ih =>
if h : p x then
rw [countP_cons_of_pos _ _ h, countP_cons_of_neg _ _ _, length, ih]
· rw [Nat.add_assoc, Nat.add_comm _ 1, Nat.add_assoc]
· simp only [h, not_true_eq_false, decide_False, not_false_eq_true]
else
rw [countP_cons_of_pos (fun a => ¬p a) _ _, countP_cons_of_neg _ _ h, length, ih]
· rfl
· simp only [h, not_false_eq_true, decide_True]
theorem countP_eq_length_filter (l) : countP p l = length (filter p l) := by
induction l with
| nil => rfl
| cons x l ih =>
if h : p x
then rw [countP_cons_of_pos p l h, ih, filter_cons_of_pos h, length]
else rw [countP_cons_of_neg p l h, ih, filter_cons_of_neg h]
theorem countP_le_length : countP p l l.length := by
simp only [countP_eq_length_filter]
apply length_filter_le
@[simp] theorem countP_append (l₁ l₂) : countP p (l₁ ++ l₂) = countP p l₁ + countP p l₂ := by
simp only [countP_eq_length_filter, filter_append, length_append]
theorem countP_pos : 0 < countP p l a l, p a := by
simp only [countP_eq_length_filter, length_pos_iff_exists_mem, mem_filter, exists_prop]
theorem countP_eq_zero : countP p l = 0 a l, ¬p a := by
simp only [countP_eq_length_filter, length_eq_zero, filter_eq_nil]
theorem countP_eq_length : countP p l = l.length a l, p a := by
rw [countP_eq_length_filter, filter_length_eq_length]
theorem Sublist.countP_le (s : l₁ <+ l₂) : countP p l₁ countP p l₂ := by
simp only [countP_eq_length_filter]
apply s.filter _ |>.length_le
theorem IsPrefix.countP_le (s : l₁ <+: l₂) : countP p l₁ countP p l₂ := s.sublist.countP_le _
theorem IsSuffix.countP_le (s : l₁ <:+ l₂) : countP p l₁ countP p l₂ := s.sublist.countP_le _
theorem IsInfix.countP_le (s : l₁ <:+: l₂) : countP p l₁ countP p l₂ := s.sublist.countP_le _
theorem countP_filter (l : List α) :
countP p (filter q l) = countP (fun a => p a q a) l := by
simp only [countP_eq_length_filter, filter_filter]
@[simp] theorem countP_true {l : List α} : (l.countP fun _ => true) = l.length := by
rw [countP_eq_length]
simp
@[simp] theorem countP_false {l : List α} : (l.countP fun _ => false) = 0 := by
rw [countP_eq_zero]
simp
@[simp] theorem countP_map (p : β Bool) (f : α β) :
l, countP p (map f l) = countP (p f) l
| [] => rfl
| a :: l => by rw [map_cons, countP_cons, countP_cons, countP_map p f l]; rfl
variable {p q}
theorem countP_mono_left (h : x l, p x q x) : countP p l countP q l := by
induction l with
| nil => apply Nat.le_refl
| cons a l ihl =>
rw [forall_mem_cons] at h
have ha, hl := h
simp [countP_cons]
cases h : p a
· simp only [Bool.false_eq_true, reduceIte, Nat.add_zero]
apply Nat.le_trans ?_ (Nat.le_add_right _ _)
apply ihl hl
· simp only [reduceIte, ha h, succ_le_succ_iff]
apply ihl hl
theorem countP_congr (h : x l, p x q x) : countP p l = countP q l :=
Nat.le_antisymm
(countP_mono_left fun x hx => (h x hx).1)
(countP_mono_left fun x hx => (h x hx).2)
end countP
/-! ### count -/
section count
variable [BEq α]
@[simp] theorem count_nil (a : α) : count a [] = 0 := rfl
theorem count_cons (a b : α) (l : List α) :
count a (b :: l) = count a l + if b == a then 1 else 0 := by
simp [count, countP_cons]
theorem count_tail : (l : List α) (a : α) (h : l []),
l.tail.count a = l.count a - if l.head h == a then 1 else 0
| head :: tail, a, _ => by simp [count_cons]
theorem count_le_length (a : α) (l : List α) : count a l l.length := countP_le_length _
theorem Sublist.count_le (h : l₁ <+ l₂) (a : α) : count a l₁ count a l₂ := h.countP_le _
theorem IsPrefix.count_le (h : l₁ <+: l₂) (a : α) : count a l₁ count a l₂ := h.sublist.count_le _
theorem IsSuffix.count_le (h : l₁ <:+ l₂) (a : α) : count a l₁ count a l₂ := h.sublist.count_le _
theorem IsInfix.count_le (h : l₁ <:+: l₂) (a : α) : count a l₁ count a l₂ := h.sublist.count_le _
theorem count_le_count_cons (a b : α) (l : List α) : count a l count a (b :: l) :=
(sublist_cons_self _ _).count_le _
theorem count_singleton (a b : α) : count a [b] = if b == a then 1 else 0 := by
simp [count_cons]
@[simp] theorem count_append (a : α) : l₁ l₂, count a (l₁ ++ l₂) = count a l₁ + count a l₂ :=
countP_append _
variable [LawfulBEq α]
@[simp] theorem count_cons_self (a : α) (l : List α) : count a (a :: l) = count a l + 1 := by
simp [count_cons]
@[simp] theorem count_cons_of_ne (h : a b) (l : List α) : count a (b :: l) = count a l := by
simp only [count_cons, cond_eq_if, beq_iff_eq]
split <;> simp_all
theorem count_singleton_self (a : α) : count a [a] = 1 := by simp
theorem count_concat_self (a : α) (l : List α) :
count a (concat l a) = (count a l) + 1 := by simp
@[simp]
theorem count_pos_iff_mem {a : α} {l : List α} : 0 < count a l a l := by
simp only [count, countP_pos, beq_iff_eq, exists_eq_right]
theorem count_eq_zero_of_not_mem {a : α} {l : List α} (h : a l) : count a l = 0 :=
Decidable.byContradiction fun h' => h <| count_pos_iff_mem.1 (Nat.pos_of_ne_zero h')
theorem not_mem_of_count_eq_zero {a : α} {l : List α} (h : count a l = 0) : a l :=
fun h' => Nat.ne_of_lt (count_pos_iff_mem.2 h') h.symm
theorem count_eq_zero {l : List α} : count a l = 0 a l :=
not_mem_of_count_eq_zero, count_eq_zero_of_not_mem
theorem count_eq_length {l : List α} : count a l = l.length b l, a = b := by
rw [count, countP_eq_length]
refine fun h b hb => Eq.symm ?_, fun h b hb => ?_
· simpa using h b hb
· rw [h b hb, beq_self_eq_true]
@[simp] theorem count_replicate_self (a : α) (n : Nat) : count a (replicate n a) = n :=
(count_eq_length.2 <| fun _ h => (eq_of_mem_replicate h).symm).trans (length_replicate ..)
theorem count_replicate (a b : α) (n : Nat) : count a (replicate n b) = if b == a then n else 0 := by
split <;> (rename_i h; simp only [beq_iff_eq] at h)
· exact b = a count_replicate_self ..
· exact count_eq_zero.2 <| mt eq_of_mem_replicate (Ne.symm h)
theorem filter_beq (l : List α) (a : α) : l.filter (· == a) = replicate (count a l) a := by
simp only [count, countP_eq_length_filter, eq_replicate, mem_filter, beq_iff_eq]
exact trivial, fun _ h => h.2
theorem filter_eq {α} [DecidableEq α] (l : List α) (a : α) : l.filter (· = a) = replicate (count a l) a :=
filter_beq l a
theorem le_count_iff_replicate_sublist {l : List α} : n count a l replicate n a <+ l := by
refine fun h => ?_, fun h => ?_
· exact ((replicate_sublist_replicate a).2 h).trans <| filter_beq l a filter_sublist _
· simpa only [count_replicate_self] using h.count_le a
theorem replicate_count_eq_of_count_eq_length {l : List α} (h : count a l = length l) :
replicate (count a l) a = l :=
(le_count_iff_replicate_sublist.mp (Nat.le_refl _)).eq_of_length <|
(length_replicate (count a l) a).trans h
@[simp] theorem count_filter {l : List α} (h : p a) : count a (filter p l) = count a l := by
rw [count, countP_filter]; congr; funext b
simp; rintro rfl; exact h
theorem count_le_count_map [DecidableEq β] (l : List α) (f : α β) (x : α) :
count x l count (f x) (map f l) := by
rw [count, count, countP_map]
apply countP_mono_left; simp (config := { contextual := true })
theorem count_erase (a b : α) :
l : List α, count a (l.erase b) = count a l - if b == a then 1 else 0
| [] => by simp
| c :: l => by
rw [erase_cons]
if hc : c = b then
have hc_beq := (beq_iff_eq _ _).mpr hc
rw [if_pos hc_beq, hc, count_cons, Nat.add_sub_cancel]
else
have hc_beq := beq_false_of_ne hc
simp only [hc_beq, if_false, count_cons, count_cons, count_erase a b l]
if ha : b = a then
rw [ha, eq_comm] at hc
rw [if_pos ((beq_iff_eq _ _).2 ha), if_neg (by simpa using Ne.symm hc), Nat.add_zero, Nat.add_zero]
else
rw [if_neg (by simpa using ha), Nat.sub_zero, Nat.sub_zero]
@[simp] theorem count_erase_self (a : α) (l : List α) :
count a (List.erase l a) = count a l - 1 := by rw [count_erase, if_pos (by simp)]
@[simp] theorem count_erase_of_ne (ab : a b) (l : List α) : count a (l.erase b) = count a l := by
rw [count_erase, if_neg (by simpa using ab.symm), Nat.sub_zero]
end count

View File

@@ -0,0 +1,457 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro,
Yury Kudryashov
-/
prelude
import Init.Data.List.Pairwise
/-!
# Lemmas about `List.eraseP` and `List.erase`.
-/
namespace List
open Nat
/-! ### eraseP -/
@[simp] theorem eraseP_nil : [].eraseP p = [] := rfl
theorem eraseP_cons (a : α) (l : List α) :
(a :: l).eraseP p = bif p a then l else a :: l.eraseP p := rfl
@[simp] theorem eraseP_cons_of_pos {l : List α} {p} (h : p a) : (a :: l).eraseP p = l := by
simp [eraseP_cons, h]
@[simp] theorem eraseP_cons_of_neg {l : List α} {p} (h : ¬p a) :
(a :: l).eraseP p = a :: l.eraseP p := by simp [eraseP_cons, h]
theorem eraseP_of_forall_not {l : List α} (h : a, a l ¬p a) : l.eraseP p = l := by
induction l with
| nil => rfl
| cons _ _ ih => simp [h _ (.head ..), ih (forall_mem_cons.1 h).2]
theorem exists_of_eraseP : {l : List α} {a} (al : a l) (pa : p a),
a l₁ l₂, ( b l₁, ¬p b) p a l = l₁ ++ a :: l₂ l.eraseP p = l₁ ++ l₂
| b :: l, a, al, pa =>
if pb : p b then
b, [], l, forall_mem_nil _, pb, by simp [pb]
else
match al with
| .head .. => nomatch pb pa
| .tail _ al =>
let c, l₁, l₂, h₁, h₂, h₃, h₄ := exists_of_eraseP al pa
c, b::l₁, l₂, (forall_mem_cons ..).2 pb, h₁,
h₂, by rw [h₃, cons_append], by simp [pb, h₄]
theorem exists_or_eq_self_of_eraseP (p) (l : List α) :
l.eraseP p = l
a l₁ l₂, ( b l₁, ¬p b) p a l = l₁ ++ a :: l₂ l.eraseP p = l₁ ++ l₂ :=
if h : a l, p a then
let _, ha, pa := h
.inr (exists_of_eraseP ha pa)
else
.inl (eraseP_of_forall_not (h ·, ·, ·))
@[simp] theorem length_eraseP_of_mem (al : a l) (pa : p a) :
length (l.eraseP p) = length l - 1 := by
let _, l₁, l₂, _, _, e₁, e₂ := exists_of_eraseP al pa
rw [e₂]; simp [length_append, e₁]; rfl
theorem length_eraseP {l : List α} : (l.eraseP p).length = if l.any p then l.length - 1 else l.length := by
split <;> rename_i h
· simp only [any_eq_true] at h
obtain x, m, h := h
simp [length_eraseP_of_mem m h]
· simp only [any_eq_true] at h
rw [eraseP_of_forall_not]
simp_all
theorem eraseP_sublist (l : List α) : l.eraseP p <+ l := by
match exists_or_eq_self_of_eraseP p l with
| .inl h => rw [h]; apply Sublist.refl
| .inr c, l₁, l₂, _, _, h₃, h₄ => rw [h₄, h₃]; simp
theorem eraseP_subset (l : List α) : l.eraseP p l := (eraseP_sublist l).subset
protected theorem Sublist.eraseP : l₁ <+ l₂ l₁.eraseP p <+ l₂.eraseP p
| .slnil => Sublist.refl _
| .cons a s => by
by_cases h : p a
· simpa [h] using s.eraseP.trans (eraseP_sublist _)
· simpa [h] using s.eraseP.cons _
| .cons₂ a s => by
by_cases h : p a
· simpa [h] using s
· simpa [h] using s.eraseP
theorem length_eraseP_le (l : List α) : (l.eraseP p).length l.length :=
l.eraseP_sublist.length_le
theorem mem_of_mem_eraseP {l : List α} : a l.eraseP p a l := (eraseP_subset _ ·)
@[simp] theorem mem_eraseP_of_neg {l : List α} (pa : ¬p a) : a l.eraseP p a l := by
refine mem_of_mem_eraseP, fun al => ?_
match exists_or_eq_self_of_eraseP p l with
| .inl h => rw [h]; assumption
| .inr c, l₁, l₂, h₁, h₂, h₃, h₄ =>
rw [h₄]; rw [h₃] at al
have : a c := fun h => (h pa).elim h₂
simp [this] at al; simp [al]
@[simp] theorem eraseP_eq_self_iff {p} {l : List α} : l.eraseP p = l a l, ¬ p a := by
rw [ Sublist.length_eq (eraseP_sublist l), length_eraseP]
split <;> rename_i h
· simp only [any_eq_true, length_eq_zero] at h
constructor
· intro; simp_all [Nat.sub_one_eq_self]
· intro; obtain x, m, h := h; simp_all
· simp_all
theorem eraseP_map (f : β α) : (l : List β), (map f l).eraseP p = map f (l.eraseP (p f))
| [] => rfl
| b::l => by by_cases h : p (f b) <;> simp [h, eraseP_map f l, eraseP_cons_of_pos]
theorem eraseP_filterMap (f : α Option β) : (l : List α),
(filterMap f l).eraseP p = filterMap f (l.eraseP (fun x => match f x with | some y => p y | none => false))
| [] => rfl
| a::l => by
rw [filterMap_cons, eraseP_cons]
split <;> rename_i h
· simp [h, eraseP_filterMap]
· rename_i b
rw [h, eraseP_cons]
by_cases w : p b
· simp [w]
· simp only [w, cond_false]
rw [filterMap_cons_some h, eraseP_filterMap]
theorem eraseP_filter (f : α Bool) (l : List α) :
(filter f l).eraseP p = filter f (l.eraseP (fun x => p x && f x)) := by
rw [ filterMap_eq_filter, eraseP_filterMap]
congr
ext x
simp only [Option.guard]
split <;> split at * <;> simp_all
theorem eraseP_append_left {a : α} (pa : p a) :
{l₁ : List α} l₂, a l₁ (l₁++l₂).eraseP p = l₁.eraseP p ++ l₂
| x :: xs, l₂, h => by
by_cases h' : p x <;> simp [h']
rw [eraseP_append_left pa l₂ ((mem_cons.1 h).resolve_left (mt _ h'))]
intro | rfl => exact pa
theorem eraseP_append_right :
{l₁ : List α} l₂, ( b l₁, ¬p b) eraseP p (l₁++l₂) = l₁ ++ l₂.eraseP p
| [], l₂, _ => rfl
| x :: xs, l₂, h => by
simp [(forall_mem_cons.1 h).1, eraseP_append_right _ (forall_mem_cons.1 h).2]
theorem eraseP_append (l₁ l₂ : List α) :
(l₁ ++ l₂).eraseP p = if l₁.any p then l₁.eraseP p ++ l₂ else l₁ ++ l₂.eraseP p := by
split <;> rename_i h
· simp only [any_eq_true] at h
obtain x, m, h := h
rw [eraseP_append_left h _ m]
· simp only [any_eq_true] at h
rw [eraseP_append_right _]
simp_all
protected theorem IsPrefix.eraseP (h : l₁ <+: l₂) : l₁.eraseP p <+: l₂.eraseP p := by
rw [IsPrefix] at h
obtain t, rfl := h
rw [eraseP_append]
split
· exact prefix_append (eraseP p l₁) t
· rw [eraseP_of_forall_not (by simp_all)]
exact prefix_append l₁ (eraseP p t)
theorem eraseP_eq_iff {p} {l : List α} :
l.eraseP p = l'
(( a l, ¬ p a) l = l')
a l₁ l₂, ( b l₁, ¬ p b) p a l = l₁ ++ a :: l₂ l' = l₁ ++ l₂ := by
cases exists_or_eq_self_of_eraseP p l with
| inl h =>
constructor
· intro h'
left
exact eraseP_eq_self_iff.1 h, by simp_all
· rintro (-, rfl | a, l₁, l₂, h₁, h₂, rfl, rfl)
· assumption
· rw [eraseP_append_right _ h₁, eraseP_cons_of_pos h₂]
| inr h =>
obtain a, l₁, l₂, h₁, h₂, w₁, w₂ := h
rw [w₂]
subst w₁
constructor
· rintro rfl
right
refine a, l₁, l₂, ?_
simp_all
· rintro (h | h)
· simp_all
· obtain a', l₁', l₂', h₁', h₂', h, rfl := h
have p : l₁ = l₁' := by
have q : l₁ = takeWhile (fun x => !p x) (l₁ ++ a :: l₂) := by
rw [takeWhile_append_of_pos (by simp_all),
takeWhile_cons_of_neg (by simp [h₂]), append_nil]
have q' : l₁' = takeWhile (fun x => !p x) (l₁' ++ a' :: l₂') := by
rw [takeWhile_append_of_pos (by simpa using h₁'),
takeWhile_cons_of_neg (by simp [h₂']), append_nil]
simp [h] at q
rw [q', q]
subst p
simp_all
@[simp] theorem eraseP_replicate_of_pos {n : Nat} {a : α} (h : p a) :
(replicate n a).eraseP p = replicate (n - 1) a := by
cases n <;> simp [replicate_succ, h]
@[simp] theorem eraseP_replicate_of_neg {n : Nat} {a : α} (h : ¬p a) :
(replicate n a).eraseP p = replicate n a := by
rw [eraseP_of_forall_not (by simp_all)]
theorem Nodup.eraseP (p) : Nodup l Nodup (l.eraseP p) :=
Nodup.sublist <| eraseP_sublist _
theorem eraseP_comm {l : List α} (h : a l, ¬ p a ¬ q a) :
(l.eraseP p).eraseP q = (l.eraseP q).eraseP p := by
induction l with
| nil => rfl
| cons a l ih =>
simp only [eraseP_cons]
by_cases h₁ : p a
· by_cases h₂ : q a
· simp_all
· simp [h₁, h₂, ih (fun b m => h b (mem_cons_of_mem _ m))]
· by_cases h₂ : q a
· simp [h₁, h₂, ih (fun b m => h b (mem_cons_of_mem _ m))]
· simp [h₁, h₂, ih (fun b m => h b (mem_cons_of_mem _ m))]
/-! ### erase -/
section erase
variable [BEq α]
@[simp] theorem erase_cons_head [LawfulBEq α] (a : α) (l : List α) : (a :: l).erase a = l := by
simp [erase_cons]
@[simp] theorem erase_cons_tail {a b : α} {l : List α} (h : ¬(b == a)) :
(b :: l).erase a = b :: l.erase a := by simp only [erase_cons, if_neg h]
theorem erase_of_not_mem [LawfulBEq α] {a : α} : {l : List α}, a l l.erase a = l
| [], _ => rfl
| b :: l, h => by
rw [mem_cons, not_or] at h
simp only [erase_cons, if_neg, erase_of_not_mem h.2, beq_iff_eq, Ne.symm h.1, not_false_eq_true]
theorem erase_eq_eraseP' (a : α) (l : List α) : l.erase a = l.eraseP (· == a) := by
induction l
· simp
· next b t ih =>
rw [erase_cons, eraseP_cons, ih]
if h : b == a then simp [h] else simp [h]
theorem erase_eq_eraseP [LawfulBEq α] (a : α) : l : List α, l.erase a = l.eraseP (a == ·)
| [] => rfl
| b :: l => by
if h : a = b then simp [h] else simp [h, Ne.symm h, erase_eq_eraseP a l]
theorem exists_erase_eq [LawfulBEq α] {a : α} {l : List α} (h : a l) :
l₁ l₂, a l₁ l = l₁ ++ a :: l₂ l.erase a = l₁ ++ l₂ := by
let _, l₁, l₂, h₁, e, h₂, h₃ := exists_of_eraseP h (beq_self_eq_true _)
rw [erase_eq_eraseP]; exact l₁, l₂, fun h => h₁ _ h (beq_self_eq_true _), eq_of_beq e h₂, h₃
@[simp] theorem length_erase_of_mem [LawfulBEq α] {a : α} {l : List α} (h : a l) :
length (l.erase a) = length l - 1 := by
rw [erase_eq_eraseP]; exact length_eraseP_of_mem h (beq_self_eq_true a)
theorem length_erase [LawfulBEq α] (a : α) (l : List α) :
length (l.erase a) = if a l then length l - 1 else length l := by
rw [erase_eq_eraseP, length_eraseP]
split <;> split <;> simp_all
theorem erase_sublist (a : α) (l : List α) : l.erase a <+ l :=
erase_eq_eraseP' a l eraseP_sublist ..
theorem erase_subset (a : α) (l : List α) : l.erase a l := (erase_sublist a l).subset
theorem Sublist.erase (a : α) {l₁ l₂ : List α} (h : l₁ <+ l₂) : l₁.erase a <+ l₂.erase a := by
simp only [erase_eq_eraseP']; exact h.eraseP
theorem IsPrefix.erase (a : α) {l₁ l₂ : List α} (h : l₁ <+: l₂) : l₁.erase a <+: l₂.erase a := by
simp only [erase_eq_eraseP']; exact h.eraseP
theorem length_erase_le (a : α) (l : List α) : (l.erase a).length l.length :=
(erase_sublist a l).length_le
theorem mem_of_mem_erase {a b : α} {l : List α} (h : a l.erase b) : a l := erase_subset _ _ h
@[simp] theorem mem_erase_of_ne [LawfulBEq α] {a b : α} {l : List α} (ab : a b) :
a l.erase b a l :=
erase_eq_eraseP b l mem_eraseP_of_neg (mt eq_of_beq ab.symm)
@[simp] theorem erase_eq_self_iff [LawfulBEq α] {l : List α} : l.erase a = l a l := by
rw [erase_eq_eraseP', eraseP_eq_self_iff]
simp
theorem erase_filter [LawfulBEq α] (f : α Bool) (l : List α) :
(filter f l).erase a = filter f (l.erase a) := by
induction l with
| nil => rfl
| cons x xs ih =>
by_cases h : a = x
· rw [erase_cons]
simp only [h, beq_self_eq_true, reduceIte]
rw [filter_cons]
split
· rw [erase_cons_head]
· rw [erase_of_not_mem]
simp_all [mem_filter]
· rw [erase_cons_tail (by simpa using Ne.symm h), filter_cons, filter_cons]
split
· rw [erase_cons_tail (by simpa using Ne.symm h), ih]
· rw [ih]
theorem erase_append_left [LawfulBEq α] {l₁ : List α} (l₂) (h : a l₁) :
(l₁ ++ l₂).erase a = l₁.erase a ++ l₂ := by
simp [erase_eq_eraseP]; exact eraseP_append_left (beq_self_eq_true a) l₂ h
theorem erase_append_right [LawfulBEq α] {a : α} {l₁ : List α} (l₂ : List α) (h : a l₁) :
(l₁ ++ l₂).erase a = (l₁ ++ l₂.erase a) := by
rw [erase_eq_eraseP, erase_eq_eraseP, eraseP_append_right]
intros b h' h''; rw [eq_of_beq h''] at h; exact h h'
theorem erase_append [LawfulBEq α] {a : α} {l₁ l₂ : List α} :
(l₁ ++ l₂).erase a = if a l₁ then l₁.erase a ++ l₂ else l₁ ++ l₂.erase a := by
simp [erase_eq_eraseP, eraseP_append]
theorem erase_comm [LawfulBEq α] (a b : α) (l : List α) :
(l.erase a).erase b = (l.erase b).erase a := by
if ab : a == b then rw [eq_of_beq ab] else ?_
if ha : a l then ?_ else
simp only [erase_of_not_mem ha, erase_of_not_mem (mt mem_of_mem_erase ha)]
if hb : b l then ?_ else
simp only [erase_of_not_mem hb, erase_of_not_mem (mt mem_of_mem_erase hb)]
match l, l.erase a, exists_erase_eq ha with
| _, _, l₁, l₂, ha', rfl, rfl =>
if h₁ : b l₁ then
rw [erase_append_left _ h₁, erase_append_left _ h₁,
erase_append_right _ (mt mem_of_mem_erase ha'), erase_cons_head]
else
rw [erase_append_right _ h₁, erase_append_right _ h₁, erase_append_right _ ha',
erase_cons_tail ab, erase_cons_head]
theorem erase_eq_iff [LawfulBEq α] {a : α} {l : List α} :
l.erase a = l'
(a l l = l')
l₁ l₂, a l₁ l = l₁ ++ a :: l₂ l' = l₁ ++ l₂ := by
rw [erase_eq_eraseP', eraseP_eq_iff]
simp only [beq_iff_eq, forall_mem_ne', exists_and_left]
constructor
· rintro (h, rfl | a', l', h, rfl, x, rfl, rfl)
· left; simp_all
· right; refine l', h, x, by simp
· rintro (h, rfl | l₁, h, x, rfl, rfl)
· left; simp_all
· right; refine a, l₁, h, by simp
@[simp] theorem erase_replicate_self [LawfulBEq α] {a : α} :
(replicate n a).erase a = replicate (n - 1) a := by
cases n <;> simp [replicate_succ]
@[simp] theorem erase_replicate_ne [LawfulBEq α] {a b : α} (h : !b == a) :
(replicate n a).erase b = replicate n a := by
rw [erase_of_not_mem]
simp_all
theorem Nodup.erase_eq_filter [LawfulBEq α] {l} (d : Nodup l) (a : α) : l.erase a = l.filter (· != a) := by
induction d with
| nil => rfl
| cons m _n ih =>
rename_i b l
by_cases h : b = a
· subst h
rw [erase_cons_head, filter_cons_of_neg (by simp)]
apply Eq.symm
rw [filter_eq_self]
simpa [@eq_comm α] using m
· simp [beq_false_of_ne h, ih, h]
theorem Nodup.mem_erase_iff [LawfulBEq α] {a : α} (d : Nodup l) : a l.erase b a b a l := by
rw [Nodup.erase_eq_filter d, mem_filter, and_comm, bne_iff_ne]
theorem Nodup.not_mem_erase [LawfulBEq α] {a : α} (h : Nodup l) : a l.erase a := fun H => by
simpa using ((Nodup.mem_erase_iff h).mp H).left
theorem Nodup.erase [LawfulBEq α] (a : α) : Nodup l Nodup (l.erase a) :=
Nodup.sublist <| erase_sublist _ _
end erase
/-! ### eraseIdx -/
theorem length_eraseIdx : {l i}, i < length l length (@eraseIdx α l i) = length l - 1
| [], _, _ => rfl
| _::_, 0, _ => by simp [eraseIdx]
| x::xs, i+1, h => by
have : i < length xs := Nat.lt_of_succ_lt_succ h
simp [eraseIdx, Nat.add_one]
rw [length_eraseIdx this, Nat.sub_add_cancel (Nat.lt_of_le_of_lt (Nat.zero_le _) this)]
@[simp] theorem eraseIdx_zero (l : List α) : eraseIdx l 0 = tail l := by cases l <;> rfl
theorem eraseIdx_eq_take_drop_succ :
(l : List α) (i : Nat), l.eraseIdx i = l.take i ++ l.drop (i + 1)
| nil, _ => by simp
| a::l, 0 => by simp
| a::l, i + 1 => by simp [eraseIdx_eq_take_drop_succ l i]
theorem eraseIdx_sublist : (l : List α) (k : Nat), eraseIdx l k <+ l
| [], _ => by simp
| a::l, 0 => by simp
| a::l, k + 1 => by simp [eraseIdx_sublist l k]
theorem eraseIdx_subset (l : List α) (k : Nat) : eraseIdx l k l := (eraseIdx_sublist l k).subset
@[simp]
theorem eraseIdx_eq_self : {l : List α} {k : Nat}, eraseIdx l k = l length l k
| [], _ => by simp
| a::l, 0 => by simp [(cons_ne_self _ _).symm]
| a::l, k + 1 => by simp [eraseIdx_eq_self]
theorem eraseIdx_of_length_le {l : List α} {k : Nat} (h : length l k) : eraseIdx l k = l := by
rw [eraseIdx_eq_self.2 h]
theorem eraseIdx_append_of_lt_length {l : List α} {k : Nat} (hk : k < length l) (l' : List α) :
eraseIdx (l ++ l') k = eraseIdx l k ++ l' := by
induction l generalizing k with
| nil => simp_all
| cons x l ih =>
cases k with
| zero => rfl
| succ k => simp_all [eraseIdx_cons_succ, Nat.succ_lt_succ_iff]
theorem eraseIdx_append_of_length_le {l : List α} {k : Nat} (hk : length l k) (l' : List α) :
eraseIdx (l ++ l') k = l ++ eraseIdx l' (k - length l) := by
induction l generalizing k with
| nil => simp_all
| cons x l ih =>
cases k with
| zero => simp_all
| succ k => simp_all [eraseIdx_cons_succ, Nat.succ_sub_succ]
protected theorem IsPrefix.eraseIdx {l l' : List α} (h : l <+: l') (k : Nat) :
eraseIdx l k <+: eraseIdx l' k := by
rcases h with t, rfl
if hkl : k < length l then
simp [eraseIdx_append_of_lt_length hkl]
else
rw [Nat.not_lt] at hkl
simp [eraseIdx_append_of_length_le hkl, eraseIdx_of_length_le hkl]
-- See also `mem_eraseIdx_iff_getElem` and `mem_eraseIdx_iff_getElem?` in
-- `Init/Data/List/Nat/Basic.lean`.
end List

View File

@@ -0,0 +1,558 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro,
Kim Morrison, Jannis Limperg
-/
prelude
import Init.Data.List.Lemmas
import Init.Data.List.Sublist
import Init.Data.List.Range
/-!
# Lemmas about `List.find?`, `List.findSome?`, `List.findIdx`, `List.findIdx?`, and `List.indexOf`.
-/
namespace List
open Nat
/-! ### find? -/
@[simp] theorem find?_cons_of_pos (l) (h : p a) : find? p (a :: l) = some a := by
simp [find?, h]
@[simp] theorem find?_cons_of_neg (l) (h : ¬p a) : find? p (a :: l) = find? p l := by
simp [find?, h]
@[simp] theorem find?_eq_none : find? p l = none x l, ¬ p x := by
induction l <;> simp [find?_cons]; split <;> simp [*]
theorem find?_some : {l}, find? p l = some a p a
| b :: l, H => by
by_cases h : p b <;> simp [find?, h] at H
· exact H h
· exact find?_some H
@[simp] theorem mem_of_find?_eq_some : {l}, find? p l = some a a l
| b :: l, H => by
by_cases h : p b <;> simp [find?, h] at H
· exact H .head _
· exact .tail _ (mem_of_find?_eq_some H)
@[simp] theorem find?_map (f : β α) (l : List β) : find? p (l.map f) = (l.find? (p f)).map f := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [map_cons, find?]
by_cases h : p (f x) <;> simp [h, ih]
theorem find?_append {l₁ l₂ : List α} : (l₁ ++ l₂).find? p = (l₁.find? p).or (l₂.find? p) := by
induction l₁ with
| nil => simp
| cons x xs ih =>
simp only [cons_append, find?]
by_cases h : p x <;> simp [h, ih]
theorem find?_replicate : find? p (replicate n a) = if n = 0 then none else if p a then some a else none := by
cases n
· simp
· by_cases p a <;> simp_all [replicate_succ]
@[simp] theorem find?_replicate_of_length_pos (h : 0 < n) : find? p (replicate n a) = if p a then some a else none := by
simp [find?_replicate, Nat.ne_of_gt h]
@[simp] theorem find?_replicate_of_pos (h : p a) : find? p (replicate n a) = if n = 0 then none else some a := by
simp [find?_replicate, h]
@[simp] theorem find?_replicate_of_neg (h : ¬ p a) : find? p (replicate n a) = none := by
simp [find?_replicate, h]
theorem Sublist.find?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) : (l₁.find? p).isSome (l₂.find? p).isSome := by
induction h with
| slnil => simp
| cons a h ih
| cons₂ a h ih =>
simp only [find?]
split <;> simp_all
theorem Sublist.find?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) : l₂.find? p = none l₁.find? p = none := by
simp only [List.find?_eq_none, Bool.not_eq_true]
exact fun w x m => w x (Sublist.mem m h)
theorem IsPrefix.find?_eq_some {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂) :
List.find? p l₁ = some b List.find? p l₂ = some b := by
rw [IsPrefix] at h
obtain t, rfl := h
simp (config := {contextual := true}) [find?_append]
theorem IsPrefix.find?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂) :
List.find? p l₂ = none List.find? p l₁ = none :=
h.sublist.find?_eq_none
theorem IsSuffix.find?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <:+ l₂) :
List.find? p l₂ = none List.find? p l₁ = none :=
h.sublist.find?_eq_none
theorem IsInfix.find?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <:+: l₂) :
List.find? p l₂ = none List.find? p l₁ = none :=
h.sublist.find?_eq_none
/-! ### findSome? -/
@[simp] theorem findSome?_cons_of_isSome (l) (h : (f a).isSome) : findSome? f (a :: l) = f a := by
simp only [findSome?]
split <;> simp_all
@[simp] theorem findSome?_cons_of_isNone (l) (h : (f a).isNone) : findSome? f (a :: l) = findSome? f l := by
simp only [findSome?]
split <;> simp_all
theorem exists_of_findSome?_eq_some {l : List α} {f : α Option β} (w : l.findSome? f = some b) :
a, a l f a = b := by
induction l with
| nil => simp_all
| cons h l ih =>
simp_all only [findSome?_cons, mem_cons, exists_eq_or_imp]
split at w <;> simp_all
@[simp] theorem findSome?_eq_none : findSome? p l = none x l, p x = none := by
induction l <;> simp [findSome?_cons]; split <;> simp [*]
@[simp] theorem map_findSome? (f : α Option β) (g : β γ) (l : List α) :
(l.findSome? f).map g = l.findSome? (Option.map g f) := by
induction l <;> simp [findSome?_cons]; split <;> simp [*]
@[simp] theorem findSome?_map (f : β γ) (l : List β) : findSome? p (l.map f) = l.findSome? (p f) := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [map_cons, findSome?]
split <;> simp_all
theorem findSome?_append {l₁ l₂ : List α} : (l₁ ++ l₂).findSome? f = (l₁.findSome? f).or (l₂.findSome? f) := by
induction l₁ with
| nil => simp
| cons x xs ih =>
simp only [cons_append, findSome?]
split <;> simp_all
theorem findSome?_replicate : findSome? f (replicate n a) = if n = 0 then none else f a := by
cases n with
| zero => simp
| succ n =>
simp only [replicate_succ, findSome?_cons]
split <;> simp_all
@[simp] theorem findSome?_replicate_of_pos (h : 0 < n) : findSome? f (replicate n a) = f a := by
simp [findSome?_replicate, Nat.ne_of_gt h]
-- Argument is unused, but used to decide whether `simp` should unfold.
@[simp] theorem find?_replicate_of_isSome (_ : (f a).isSome) : findSome? f (replicate n a) = if n = 0 then none else f a := by
simp [findSome?_replicate]
@[simp] theorem find?_replicate_of_isNone (h : (f a).isNone) : findSome? f (replicate n a) = none := by
rw [Option.isNone_iff_eq_none] at h
simp [findSome?_replicate, h]
theorem Sublist.findSome?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :
(l₁.findSome? f).isSome (l₂.findSome? f).isSome := by
induction h with
| slnil => simp
| cons a h ih
| cons₂ a h ih =>
simp only [findSome?]
split <;> simp_all
theorem Sublist.findSome?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) :
l₂.findSome? f = none l₁.findSome? f = none := by
simp only [List.findSome?_eq_none, Bool.not_eq_true]
exact fun w x m => w x (Sublist.mem m h)
theorem IsPrefix.findSome?_eq_some {l₁ l₂ : List α} {f : α Option β} (h : l₁ <+: l₂) :
List.findSome? f l₁ = some b List.findSome? f l₂ = some b := by
rw [IsPrefix] at h
obtain t, rfl := h
simp (config := {contextual := true}) [findSome?_append]
theorem IsPrefix.findSome?_eq_none {l₁ l₂ : List α} {f : α Option β} (h : l₁ <+: l₂) :
List.findSome? f l₂ = none List.findSome? f l₁ = none :=
h.sublist.findSome?_eq_none
theorem IsSuffix.findSome?_eq_none {l₁ l₂ : List α} {f : α Option β} (h : l₁ <:+ l₂) :
List.findSome? f l₂ = none List.findSome? f l₁ = none :=
h.sublist.findSome?_eq_none
theorem IsInfix.findSome?_eq_none {l₁ l₂ : List α} {f : α Option β} (h : l₁ <:+: l₂) :
List.findSome? f l₂ = none List.findSome? f l₁ = none :=
h.sublist.findSome?_eq_none
/-! ### findIdx -/
theorem findIdx_cons (p : α Bool) (b : α) (l : List α) :
(b :: l).findIdx p = bif p b then 0 else (l.findIdx p) + 1 := by
cases H : p b with
| true => simp [H, findIdx, findIdx.go]
| false => simp [H, findIdx, findIdx.go, findIdx_go_succ]
where
findIdx_go_succ (p : α Bool) (l : List α) (n : Nat) :
List.findIdx.go p l (n + 1) = (findIdx.go p l n) + 1 := by
cases l with
| nil => unfold findIdx.go; exact Nat.succ_eq_add_one n
| cons head tail =>
unfold findIdx.go
cases p head <;> simp only [cond_false, cond_true]
exact findIdx_go_succ p tail (n + 1)
theorem findIdx_of_getElem?_eq_some {xs : List α} (w : xs[xs.findIdx p]? = some y) : p y := by
induction xs with
| nil => simp_all
| cons x xs ih => by_cases h : p x <;> simp_all [findIdx_cons]
theorem findIdx_getElem {xs : List α} {w : xs.findIdx p < xs.length} :
p xs[xs.findIdx p] :=
xs.findIdx_of_getElem?_eq_some (getElem?_eq_getElem w)
@[deprecated findIdx_of_getElem?_eq_some (since := "2024-08-12")]
theorem findIdx_of_get?_eq_some {xs : List α} (w : xs.get? (xs.findIdx p) = some y) : p y :=
findIdx_of_getElem?_eq_some (by simpa using w)
@[deprecated findIdx_getElem (since := "2024-08-12")]
theorem findIdx_get {xs : List α} {w : xs.findIdx p < xs.length} :
p (xs.get xs.findIdx p, w) :=
xs.findIdx_of_getElem?_eq_some (getElem?_eq_getElem w)
theorem findIdx_lt_length_of_exists {xs : List α} (h : x xs, p x) :
xs.findIdx p < xs.length := by
induction xs with
| nil => simp_all
| cons x xs ih =>
by_cases p x
· simp_all only [forall_exists_index, and_imp, mem_cons, exists_eq_or_imp, true_or,
findIdx_cons, cond_true, length_cons]
apply Nat.succ_pos
· simp_all [findIdx_cons, Nat.succ_lt_succ_iff]
obtain x', m', h' := h
exact ih x' m' h'
theorem findIdx_getElem?_eq_getElem_of_exists {xs : List α} (h : x xs, p x) :
xs[xs.findIdx p]? = some (xs[xs.findIdx p]'(xs.findIdx_lt_length_of_exists h)) :=
getElem?_eq_getElem (findIdx_lt_length_of_exists h)
@[deprecated findIdx_getElem?_eq_getElem_of_exists (since := "2024-08-12")]
theorem findIdx_get?_eq_get_of_exists {xs : List α} (h : x xs, p x) :
xs.get? (xs.findIdx p) = some (xs.get xs.findIdx p, xs.findIdx_lt_length_of_exists h) :=
get?_eq_get (findIdx_lt_length_of_exists h)
@[simp]
theorem findIdx_eq_length {p : α Bool} {xs : List α} :
xs.findIdx p = xs.length x xs, p x = false := by
induction xs with
| nil => simp_all
| cons x xs ih =>
rw [findIdx_cons, length_cons]
simp only [cond_eq_if]
split <;> simp_all [Nat.succ.injEq]
theorem findIdx_eq_length_of_false {p : α Bool} {xs : List α} (h : x xs, p x = false) :
xs.findIdx p = xs.length := by
rw [findIdx_eq_length]
exact h
theorem findIdx_le_length (p : α Bool) {xs : List α} : xs.findIdx p xs.length := by
by_cases e : x xs, p x
· exact Nat.le_of_lt (findIdx_lt_length_of_exists e)
· simp at e
exact Nat.le_of_eq (findIdx_eq_length.mpr e)
@[simp]
theorem findIdx_lt_length {p : α Bool} {xs : List α} :
xs.findIdx p < xs.length x xs, p x := by
rw [ Decidable.not_iff_not, Nat.not_lt]
have := @Nat.le_antisymm_iff (xs.findIdx p) xs.length
simp only [findIdx_le_length, true_and] at this
rw [ this, findIdx_eq_length, not_exists]
simp only [Bool.not_eq_true, not_and]
/-- `p` does not hold for elements with indices less than `xs.findIdx p`. -/
theorem not_of_lt_findIdx {p : α Bool} {xs : List α} {i : Nat} (h : i < xs.findIdx p) :
¬p (xs[i]'(Nat.le_trans h (findIdx_le_length p))) := by
revert i
induction xs with
| nil => intro i h; rw [findIdx_nil] at h; simp at h
| cons x xs ih =>
intro i h
have ho := h
rw [findIdx_cons] at h
have npx : ¬p x := by intro y; rw [y, cond_true] at h; simp at h
simp [npx, cond_false] at h
cases i.eq_zero_or_pos with
| inl e => simpa only [e, Fin.zero_eta, get_cons_zero]
| inr e =>
have ipm := Nat.succ_pred_eq_of_pos e
have ilt := Nat.le_trans ho (findIdx_le_length p)
simp (config := { singlePass := true }) only [ ipm, getElem_cons_succ]
rw [ ipm, Nat.succ_lt_succ_iff] at h
simpa using ih h
/-- If `¬ p xs[j]` for all `j < i`, then `i ≤ xs.findIdx p`. -/
theorem le_findIdx_of_not {p : α Bool} {xs : List α} {i : Nat} (h : i < xs.length)
(h2 : j (hji : j < i), ¬p (xs[j]'(Nat.lt_trans hji h))) : i xs.findIdx p := by
apply Decidable.byContradiction
intro f
simp only [Nat.not_le] at f
exact absurd (@findIdx_getElem _ p xs (Nat.lt_trans f h)) (h2 (xs.findIdx p) f)
/-- If `¬ p xs[j]` for all `j ≤ i`, then `i < xs.findIdx p`. -/
theorem lt_findIdx_of_not {p : α Bool} {xs : List α} {i : Nat} (h : i < xs.length)
(h2 : j (hji : j i), ¬p (xs.get j, Nat.lt_of_le_of_lt hji h)) : i < xs.findIdx p := by
apply Decidable.byContradiction
intro f
simp only [Nat.not_lt] at f
exact absurd (@findIdx_getElem _ p xs (Nat.lt_of_le_of_lt f h)) (h2 (xs.findIdx p) f)
/-- `xs.findIdx p = i` iff `p xs[i]` and `¬ p xs [j]` for all `j < i`. -/
theorem findIdx_eq {p : α Bool} {xs : List α} {i : Nat} (h : i < xs.length) :
xs.findIdx p = i p xs[i] j (hji : j < i), ¬p (xs[j]'(Nat.lt_trans hji h)) := by
refine fun f f (@findIdx_getElem _ p xs (f h)), fun _ hji not_of_lt_findIdx (f hji),
fun h1, h2 ?_
apply Nat.le_antisymm _ (le_findIdx_of_not h h2)
apply Decidable.byContradiction
intro h3
simp at h3
exact not_of_lt_findIdx h3 h1
theorem findIdx_append (p : α Bool) (l₁ l₂ : List α) :
(l₁ ++ l₂).findIdx p =
if l₁.findIdx p < l₁.length then l₁.findIdx p else l₂.findIdx p + l₁.length := by
simp
induction l₁ with
| nil => simp
| cons x xs ih =>
simp only [findIdx_cons, length_cons, cons_append]
by_cases h : p x
· simp [h]
· simp only [h, ih, cond_eq_if, Bool.false_eq_true, reduceIte, mem_cons, exists_eq_or_imp,
false_or]
split <;> simp [Nat.add_assoc]
theorem IsPrefix.findIdx_le {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂) :
l₁.findIdx p l₂.findIdx p := by
rw [IsPrefix] at h
obtain t, rfl := h
simp only [findIdx_append, findIdx_lt_length]
split
· exact Nat.le_refl ..
· simp_all [findIdx_eq_length_of_false]
theorem IsPrefix.findIdx_eq_of_findIdx_lt_length {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂)
(lt : l₁.findIdx p < l₁.length) : l₂.findIdx p = l₁.findIdx p := by
rw [IsPrefix] at h
obtain t, rfl := h
simp only [findIdx_append, findIdx_lt_length]
split
· rfl
· simp_all
/-! ### findIdx? -/
@[simp] theorem findIdx?_nil : ([] : List α).findIdx? p i = none := rfl
@[simp] theorem findIdx?_cons :
(x :: xs).findIdx? p i = if p x then some i else findIdx? p xs (i + 1) := rfl
@[simp] theorem findIdx?_succ :
(xs : List α).findIdx? p (i+1) = (xs.findIdx? p i).map fun i => i + 1 := by
induction xs generalizing i with simp
| cons _ _ _ => split <;> simp_all
@[simp]
theorem findIdx?_eq_none_iff {xs : List α} {p : α Bool} :
xs.findIdx? p = none x, x xs p x = false := by
induction xs with
| nil => simp_all
| cons x xs ih =>
simp only [findIdx?_cons]
split <;> simp_all [cond_eq_if]
theorem findIdx?_isSome {xs : List α} {p : α Bool} :
(xs.findIdx? p).isSome = xs.any p := by
induction xs with
| nil => simp
| cons x xs ih =>
simp only [findIdx?_cons]
split <;> simp_all
theorem findIdx?_isNone {xs : List α} {p : α Bool} :
(xs.findIdx? p).isNone = xs.all (¬p ·) := by
induction xs with
| nil => simp
| cons x xs ih =>
simp only [findIdx?_cons]
split <;> simp_all
theorem findIdx?_eq_some_iff_findIdx_eq {xs : List α} {p : α Bool} {i : Nat} :
xs.findIdx? p = some i i < xs.length xs.findIdx p = i := by
induction xs generalizing i with
| nil => simp_all
| cons x xs ih =>
simp only [findIdx?_cons, findIdx_cons]
split
· simp_all [cond_eq_if]
rintro rfl
exact zero_lt_succ xs.length
· simp_all [cond_eq_if, and_assoc]
constructor
· rintro a, lt, rfl, rfl
simp_all [Nat.succ_lt_succ_iff]
· rintro h, rfl
exact _, by simp_all [Nat.succ_lt_succ_iff], rfl, rfl
theorem findIdx?_eq_some_of_exists {xs : List α} {p : α Bool} (h : x, x xs p x) :
xs.findIdx? p = some (xs.findIdx p) := by
rw [findIdx?_eq_some_iff_findIdx_eq]
exact findIdx_lt_length_of_exists h, rfl
theorem findIdx?_eq_none_iff_findIdx_eq {xs : List α} {p : α Bool} :
xs.findIdx? p = none xs.findIdx p = xs.length := by
simp
theorem findIdx?_eq_some_iff_getElem (xs : List α) (p : α Bool) :
xs.findIdx? p = some i
h : i < xs.length, p xs[i] j (hji : j < i), ¬p (xs[j]'(Nat.lt_trans hji h)) := by
induction xs generalizing i with
| nil => simp
| cons x xs ih =>
simp only [findIdx?_cons, Nat.zero_add, findIdx?_succ]
split
· simp only [Option.some.injEq, Bool.not_eq_true, length_cons]
cases i with
| zero => simp_all
| succ i =>
simp only [Bool.not_eq_true, zero_ne_add_one, getElem_cons_succ, false_iff, not_exists,
not_and, Classical.not_forall, Bool.not_eq_false]
intros
refine 0, zero_lt_succ i, _
· simp only [Option.map_eq_some', ih, Bool.not_eq_true, length_cons]
constructor
· rintro a, h, h₁, h₂, rfl
refine Nat.succ_lt_succ_iff.mpr h, by simpa, fun j hj => ?_
cases j with
| zero => simp_all
| succ j =>
apply h₂
simp_all [Nat.succ_lt_succ_iff]
· rintro h, h₁, h₂
cases i with
| zero => simp_all
| succ i =>
refine i, Nat.succ_lt_succ_iff.mp h, by simpa, fun j hj => ?_, rfl
simpa using h₂ (j + 1) (Nat.succ_lt_succ_iff.mpr hj)
theorem findIdx?_of_eq_some {xs : List α} {p : α Bool} (w : xs.findIdx? p = some i) :
match xs[i]? with | some a => p a | none => false := by
induction xs generalizing i with
| nil => simp_all
| cons x xs ih =>
simp_all only [findIdx?_cons, Nat.zero_add, findIdx?_succ]
split at w <;> cases i <;> simp_all [succ_inj']
theorem findIdx?_of_eq_none {xs : List α} {p : α Bool} (w : xs.findIdx? p = none) :
i : Nat, match xs[i]? with | some a => ¬ p a | none => true := by
intro i
induction xs generalizing i with
| nil => simp_all
| cons x xs ih =>
simp_all only [Bool.not_eq_true, findIdx?_cons, Nat.zero_add, findIdx?_succ]
cases i with
| zero =>
split at w <;> simp_all
| succ i =>
simp only [getElem?_cons_succ]
apply ih
split at w <;> simp_all
@[simp] theorem findIdx?_map (f : β α) (l : List β) : findIdx? p (l.map f) = l.findIdx? (p f) := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [map_cons, findIdx?]
split <;> simp_all
@[simp] theorem findIdx?_append :
(xs ++ ys : List α).findIdx? p =
(xs.findIdx? p).or ((ys.findIdx? p).map fun i => i + xs.length) := by
induction xs with simp
| cons _ _ _ => split <;> simp_all [Option.map_or', Option.map_map]; rfl
theorem findIdx?_join {l : List (List α)} {p : α Bool} :
l.join.findIdx? p =
(l.findIdx? (·.any p)).map
fun i => Nat.sum ((l.take i).map List.length) +
(l[i]?.map fun xs => xs.findIdx p).getD 0 := by
induction l with
| nil => simp
| cons xs l ih =>
simp only [join, findIdx?_append, map_take, map_cons, findIdx?, any_eq_true, Nat.zero_add,
findIdx?_succ]
split
· simp only [Option.map_some', take_zero, sum_nil, length_cons, zero_lt_succ,
getElem?_eq_getElem, getElem_cons_zero, Option.getD_some, Nat.zero_add]
rw [Option.or_of_isSome (by simpa [findIdx?_isSome])]
rw [findIdx?_eq_some_of_exists _]
· simp_all only [map_take, not_exists, not_and, Bool.not_eq_true, Option.map_map]
rw [Option.or_of_isNone (by simpa [findIdx?_isNone])]
congr 1
ext i
simp [Nat.add_comm, Nat.add_assoc]
@[simp] theorem findIdx?_replicate :
(replicate n a).findIdx? p = if 0 < n p a then some 0 else none := by
cases n with
| zero => simp
| succ n =>
simp only [replicate, findIdx?_cons, Nat.zero_add, findIdx?_succ, zero_lt_succ, true_and]
split <;> simp_all
theorem findIdx?_eq_enum_findSome? {xs : List α} {p : α Bool} :
xs.findIdx? p = xs.enum.findSome? fun i, a => if p a then some i else none := by
induction xs with
| nil => simp
| cons x xs ih =>
simp only [findIdx?_cons, Nat.zero_add, findIdx?_succ, enum]
split
· simp_all
· simp_all only [enumFrom_cons, ite_false, Option.isNone_none, findSome?_cons_of_isNone]
simp [Function.comp_def, map_fst_add_enum_eq_enumFrom]
theorem Sublist.findIdx?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :
(l₁.findIdx? p).isSome (l₂.findIdx? p).isSome := by
simp only [List.findIdx?_isSome, any_eq_true]
rintro w, m, q
exact w, h.mem m, q
theorem Sublist.findIdx?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) :
l₂.findIdx? p = none l₁.findIdx? p = none := by
simp only [findIdx?_eq_none_iff]
exact fun w x m => w x (h.mem m)
theorem IsPrefix.findIdx?_eq_some {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂) :
List.findIdx? p l₁ = some i List.findIdx? p l₂ = some i := by
rw [IsPrefix] at h
obtain t, rfl := h
intro h
simp [findIdx?_append, h]
theorem IsPrefix.findIdx?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <+: l₂) :
List.findIdx? p l₂ = none List.findIdx? p l₁ = none :=
h.sublist.findIdx?_eq_none
theorem IsSuffix.findIdx?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <:+ l₂) :
List.findIdx? p l₂ = none List.findIdx? p l₁ = none :=
h.sublist.findIdx?_eq_none
theorem IsInfix.findIdx?_eq_none {l₁ l₂ : List α} {p : α Bool} (h : l₁ <:+: l₂) :
List.findIdx? p l₂ = none List.findIdx? p l₁ = none :=
h.sublist.findIdx?_eq_none
/-! ### indexOf -/
theorem indexOf_cons [BEq α] :
(x :: xs : List α).indexOf y = bif x == y then 0 else xs.indexOf y + 1 := by
dsimp [indexOf]
simp [findIdx_cons]
end List

View File

@@ -193,6 +193,17 @@ theorem replicateTR_loop_eq : ∀ n, replicateTR.loop a n acc = replicate n a ++
apply funext; intro α; apply funext; intro n; apply funext; intro a
exact (replicateTR_loop_replicate_eq _ 0 n).symm
/-! ## Additional functions -/
/-! ### leftpad -/
/-- Optimized version of `leftpad`. -/
@[inline] def leftpadTR (n : Nat) (a : α) (l : List α) : List α :=
replicateTR.loop a (n - length l) l
@[csimp] theorem leftpad_eq_leftpadTR : @leftpad = @leftpadTR := by
funext α n a l; simp [leftpad, leftpadTR, replicateTR_loop_eq]
/-! ## Sublists -/
/-! ### take -/
@@ -366,6 +377,26 @@ def unzipTR (l : List (α × β)) : List α × List β :=
/-! ## Ranges and enumeration -/
/-! ### range' -/
/-- Optimized version of `range'`. -/
@[inline] def range'TR (s n : Nat) (step : Nat := 1) : List Nat := go n (s + step * n) [] where
/-- Auxiliary for `range'TR`: `range'TR.go n e = [e-n, ..., e-1] ++ acc`. -/
go : Nat Nat List Nat List Nat
| 0, _, acc => acc
| n+1, e, acc => go n (e-step) ((e-step) :: acc)
@[csimp] theorem range'_eq_range'TR : @range' = @range'TR := by
funext s n step
let rec go (s) : n m,
range'TR.go step n (s + step * n) (range' (s + step * n) m step) = range' s (n + m) step
| 0, m => by simp [range'TR.go]
| n+1, m => by
simp [range'TR.go]
rw [Nat.mul_succ, Nat.add_assoc, Nat.add_sub_cancel, Nat.add_right_comm n]
exact go s n (m + 1)
exact (go s n 0).symm
/-! ### iota -/
/-- Tail-recursive version of `List.iota`. -/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Lemmas
/-!
# Lemmas about `List.minimum?` and `List.maximum?.
-/
namespace List
open Nat
/-! ## Minima and maxima -/
/-! ### minimum? -/
@[simp] theorem minimum?_nil [Min α] : ([] : List α).minimum? = none := rfl
-- We don't put `@[simp]` on `minimum?_cons`,
-- because the definition in terms of `foldl` is not useful for proofs.
theorem minimum?_cons [Min α] {xs : List α} : (x :: xs).minimum? = foldl min x xs := rfl
@[simp] theorem minimum?_eq_none_iff {xs : List α} [Min α] : xs.minimum? = none xs = [] := by
cases xs <;> simp [minimum?]
theorem minimum?_mem [Min α] (min_eq_or : a b : α, min a b = a min a b = b) :
{xs : List α} xs.minimum? = some a a xs := by
intro xs
match xs with
| nil => simp
| x :: xs =>
simp only [minimum?_cons, Option.some.injEq, List.mem_cons]
intro eq
induction xs generalizing x with
| nil =>
simp at eq
simp [eq]
| cons y xs ind =>
simp at eq
have p := ind _ eq
cases p with
| inl p =>
cases min_eq_or x y with | _ q => simp [p, q]
| inr p => simp [p, mem_cons]
-- See also `Init.Data.List.Nat.Basic` for specialisations of the next two results to `Nat`.
theorem le_minimum?_iff [Min α] [LE α]
(le_min_iff : a b c : α, a min b c a b a c) :
{xs : List α} xs.minimum? = some a x, x a b, b xs x b
| nil => by simp
| cons x xs => by
rw [minimum?]
intro eq y
simp only [Option.some.injEq] at eq
induction xs generalizing x with
| nil =>
simp at eq
simp [eq]
| cons z xs ih =>
simp at eq
simp [ih _ eq, le_min_iff, and_assoc]
-- This could be refactored by designing appropriate typeclasses to replace `le_refl`, `min_eq_or`,
-- and `le_min_iff`.
theorem minimum?_eq_some_iff [Min α] [LE α] [anti : Antisymm ((· : α) ·)]
(le_refl : a : α, a a)
(min_eq_or : a b : α, min a b = a min a b = b)
(le_min_iff : a b c : α, a min b c a b a c) {xs : List α} :
xs.minimum? = some a a xs b, b xs a b := by
refine fun h => minimum?_mem min_eq_or h, (le_minimum?_iff le_min_iff h _).1 (le_refl _), ?_
intro h₁, h₂
cases xs with
| nil => simp at h₁
| cons x xs =>
exact congrArg some <| anti.1
((le_minimum?_iff le_min_iff (xs := x::xs) rfl _).1 (le_refl _) _ h₁)
(h₂ _ (minimum?_mem min_eq_or (xs := x::xs) rfl))
theorem minimum?_replicate [Min α] {n : Nat} {a : α} (w : min a a = a) :
(replicate n a).minimum? = if n = 0 then none else some a := by
induction n with
| zero => rfl
| succ n ih => cases n <;> simp_all [replicate_succ, minimum?_cons]
@[simp] theorem minimum?_replicate_of_pos [Min α] {n : Nat} {a : α} (w : min a a = a) (h : 0 < n) :
(replicate n a).minimum? = some a := by
simp [minimum?_replicate, Nat.ne_of_gt h, w]
/-! ### maximum? -/
@[simp] theorem maximum?_nil [Max α] : ([] : List α).maximum? = none := rfl
-- We don't put `@[simp]` on `maximum?_cons`,
-- because the definition in terms of `foldl` is not useful for proofs.
theorem maximum?_cons [Max α] {xs : List α} : (x :: xs).maximum? = foldl max x xs := rfl
@[simp] theorem maximum?_eq_none_iff {xs : List α} [Max α] : xs.maximum? = none xs = [] := by
cases xs <;> simp [maximum?]
theorem maximum?_mem [Max α] (min_eq_or : a b : α, max a b = a max a b = b) :
{xs : List α} xs.maximum? = some a a xs
| nil => by simp
| cons x xs => by
rw [maximum?]; rintro
induction xs generalizing x with simp at *
| cons y xs ih =>
rcases ih (max x y) with h | h <;> simp [h]
simp [ or_assoc, min_eq_or x y]
-- See also `Init.Data.List.Nat.Basic` for specialisations of the next two results to `Nat`.
theorem maximum?_le_iff [Max α] [LE α]
(max_le_iff : a b c : α, max b c a b a c a) :
{xs : List α} xs.maximum? = some a x, a x b xs, b x
| nil => by simp
| cons x xs => by
rw [maximum?]; rintro y
induction xs generalizing x with
| nil => simp
| cons y xs ih => simp [ih, max_le_iff, and_assoc]
-- This could be refactored by designing appropriate typeclasses to replace `le_refl`, `max_eq_or`,
-- and `le_min_iff`.
theorem maximum?_eq_some_iff [Max α] [LE α] [anti : Antisymm ((· : α) ·)]
(le_refl : a : α, a a)
(max_eq_or : a b : α, max a b = a max a b = b)
(max_le_iff : a b c : α, max b c a b a c a) {xs : List α} :
xs.maximum? = some a a xs b xs, b a := by
refine fun h => maximum?_mem max_eq_or h, (maximum?_le_iff max_le_iff h _).1 (le_refl _), ?_
intro h₁, h₂
cases xs with
| nil => simp at h₁
| cons x xs =>
exact congrArg some <| anti.1
(h₂ _ (maximum?_mem max_eq_or (xs := x::xs) rfl))
((maximum?_le_iff max_le_iff (xs := x::xs) rfl _).1 (le_refl _) _ h₁)
theorem maximum?_replicate [Max α] {n : Nat} {a : α} (w : max a a = a) :
(replicate n a).maximum? = if n = 0 then none else some a := by
induction n with
| zero => rfl
| succ n ih => cases n <;> simp_all [replicate_succ, maximum?_cons]
@[simp] theorem maximum?_replicate_of_pos [Max α] {n : Nat} {a : α} (w : max a a = a) (h : 0 < n) :
(replicate n a).maximum? = some a := by
simp [maximum?_replicate, Nat.ne_of_gt h, w]
end List

View File

@@ -0,0 +1,69 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.TakeDrop
/-!
# Lemmas about `List.mapM` and `List.forM`.
-/
namespace List
open Nat
/-! ## Monadic operations -/
-- We may want to replace these `simp` attributes with explicit equational lemmas,
-- as we already have for all the non-monadic functions.
attribute [simp] mapA forA filterAuxM firstM anyM allM findM? findSomeM?
-- Previously `mapM.loop`, `filterMapM.loop`, `forIn.loop`, `forIn'.loop`
-- had attribute `@[simp]`.
-- We don't currently provide simp lemmas,
-- as this is an internal implementation and they don't seem to be needed.
/-! ### mapM -/
/-- Alternate (non-tail-recursive) form of mapM for proofs. -/
def mapM' [Monad m] (f : α m β) : List α m (List β)
| [] => pure []
| a :: l => return ( f a) :: ( l.mapM' f)
@[simp] theorem mapM'_nil [Monad m] {f : α m β} : mapM' f [] = pure [] := rfl
@[simp] theorem mapM'_cons [Monad m] {f : α m β} :
mapM' f (a :: l) = return (( f a) :: ( l.mapM' f)) :=
rfl
theorem mapM'_eq_mapM [Monad m] [LawfulMonad m] (f : α m β) (l : List α) :
mapM' f l = mapM f l := by simp [go, mapM] where
go : l acc, mapM.loop f l acc = return acc.reverse ++ ( mapM' f l)
| [], acc => by simp [mapM.loop, mapM']
| a::l, acc => by simp [go l, mapM.loop, mapM']
@[simp] theorem mapM_nil [Monad m] (f : α m β) : [].mapM f = pure [] := rfl
@[simp] theorem mapM_cons [Monad m] [LawfulMonad m] (f : α m β) :
(a :: l).mapM f = (return ( f a) :: ( l.mapM f)) := by simp [ mapM'_eq_mapM, mapM']
@[simp] theorem mapM_append [Monad m] [LawfulMonad m] (f : α m β) {l₁ l₂ : List α} :
(l₁ ++ l₂).mapM f = (return ( l₁.mapM f) ++ ( l₂.mapM f)) := by induction l₁ <;> simp [*]
/-! ### forM -/
-- We use `List.forM` as the simp normal form, rather that `ForM.forM`.
-- As such we need to replace `List.forM_nil` and `List.forM_cons`:
@[simp] theorem forM_nil' [Monad m] : ([] : List α).forM f = (pure .unit : m PUnit) := rfl
@[simp] theorem forM_cons' [Monad m] :
(a::as).forM f = (f a >>= fun _ => as.forM f : m PUnit) :=
List.forM_cons _ _ _
@[simp] theorem forM_append [Monad m] [LawfulMonad m] (l₁ l₂ : List α) (f : α m PUnit) :
(l₁ ++ l₂).forM f = (do l₁.forM f; l₂.forM f) := by
induction l₁ <;> simp [*]
end List

View File

@@ -0,0 +1,11 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Nat.Basic
import Init.Data.List.Nat.Pairwise
import Init.Data.List.Nat.Range
import Init.Data.List.Nat.Sublist
import Init.Data.List.Nat.TakeDrop

View File

@@ -0,0 +1,133 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Count
import Init.Data.List.MinMax
import Init.Data.Nat.Lemmas
/-!
# Miscellaneous `List` lemmas, that require more `Nat` lemmas than are available in `Init.Data.List.Lemmas`.
In particular, `omega` is available here.
-/
open Nat
namespace List
/-! ### filter -/
theorem length_filter_lt_length_iff_exists (l) :
length (filter p l) < length l x l, ¬p x := by
simpa [length_eq_countP_add_countP p l, countP_eq_length_filter] using
countP_pos (fun x => ¬p x) (l := l)
/-! ### reverse -/
theorem getElem_eq_getElem_reverse {l : List α} {i} (h : i < l.length) :
l[i] = l.reverse[l.length - 1 - i]'(by simpa using Nat.sub_one_sub_lt_of_lt h) := by
rw [getElem_reverse]
congr
omega
/-! ### leftpad -/
/-- The length of the List returned by `List.leftpad n a l` is equal
to the larger of `n` and `l.length` -/
@[simp]
theorem leftpad_length (n : Nat) (a : α) (l : List α) :
(leftpad n a l).length = max n l.length := by
simp only [leftpad, length_append, length_replicate, Nat.sub_add_eq_max]
/-! ### eraseIdx -/
theorem mem_eraseIdx_iff_getElem {x : α} :
{l} {k}, x eraseIdx l k i h, i k l[i]'h = x
| [], _ => by
simp only [eraseIdx, not_mem_nil, false_iff]
rintro i, h, -
exact Nat.not_lt_zero _ h
| a::l, 0 => by simp [mem_iff_getElem, Nat.succ_lt_succ_iff]
| a::l, k+1 => by
rw [ Nat.or_exists_add_one]
simp [mem_eraseIdx_iff_getElem, @eq_comm _ a, succ_inj', Nat.succ_lt_succ_iff]
theorem mem_eraseIdx_iff_getElem? {x : α} {l} {k} : x eraseIdx l k i k, l[i]? = some x := by
simp only [mem_eraseIdx_iff_getElem, getElem_eq_iff, exists_and_left]
refine exists_congr fun i => and_congr_right' ?_
constructor
· rintro _, h; exact h
· rintro h;
obtain h', - := getElem?_eq_some.1 h
exact h', h
/-! ### minimum? -/
-- A specialization of `minimum?_eq_some_iff` to Nat.
theorem minimum?_eq_some_iff' {xs : List Nat} :
xs.minimum? = some a (a xs b xs, a b) :=
minimum?_eq_some_iff
(le_refl := Nat.le_refl)
(min_eq_or := fun _ _ => by omega)
(le_min_iff := fun _ _ _ => by omega)
-- This could be generalized,
-- but will first require further work on order typeclasses in the core repository.
theorem minimum?_cons' {a : Nat} {l : List Nat} :
(a :: l).minimum? = some (match l.minimum? with
| none => a
| some m => min a m) := by
rw [minimum?_eq_some_iff']
split <;> rename_i h m
· simp_all
· rw [minimum?_eq_some_iff'] at m
obtain m, le := m
rw [Nat.min_def]
constructor
· split
· exact mem_cons_self a l
· exact mem_cons_of_mem a m
· intro b m
cases List.mem_cons.1 m with
| inl => split <;> omega
| inr h =>
specialize le b h
split <;> omega
/-! ### maximum? -/
-- A specialization of `maximum?_eq_some_iff` to Nat.
theorem maximum?_eq_some_iff' {xs : List Nat} :
xs.maximum? = some a (a xs b xs, b a) :=
maximum?_eq_some_iff
(le_refl := Nat.le_refl)
(max_eq_or := fun _ _ => by omega)
(max_le_iff := fun _ _ _ => by omega)
-- This could be generalized,
-- but will first require further work on order typeclasses in the core repository.
theorem maximum?_cons' {a : Nat} {l : List Nat} :
(a :: l).maximum? = some (match l.maximum? with
| none => a
| some m => max a m) := by
rw [maximum?_eq_some_iff']
split <;> rename_i h m
· simp_all
· rw [maximum?_eq_some_iff'] at m
obtain m, le := m
rw [Nat.max_def]
constructor
· split
· exact mem_cons_of_mem a m
· exact mem_cons_self a l
· intro b m
cases List.mem_cons.1 m with
| inl => split <;> omega
| inr h =>
specialize le b h
split <;> omega
end List

View File

@@ -0,0 +1,73 @@
/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, James Gallicchio
-/
prelude
import Init.Data.Fin.Lemmas
import Init.Data.List.Nat.TakeDrop
import Init.Data.List.Pairwise
/-!
# Lemmas about `List.Pairwise`
-/
namespace List
/-- Given a list `is` of monotonically increasing indices into `l`, getting each index
produces a sublist of `l`. -/
theorem map_getElem_sublist {l : List α} {is : List (Fin l.length)} (h : is.Pairwise (· < ·)) :
is.map (l[·]) <+ l := by
suffices n l', l' = l.drop n ( i is, n i) map (l[·]) is <+ l'
from this 0 l (by simp) (by simp)
rintro n l' rfl his
induction is generalizing n with
| nil => simp
| cons hd tl IH =>
simp only [Fin.getElem_fin, map_cons]
have := IH h.of_cons (hd+1) (pairwise_cons.mp h).1
specialize his hd (.head _)
have := (drop_eq_getElem_cons ..).symm this.cons₂ (get l hd)
have := Sublist.append (nil_sublist (take hd l |>.drop n)) this
rwa [nil_append, (drop_append_of_le_length ?_), take_append_drop] at this
simp [Nat.min_eq_left (Nat.le_of_lt hd.isLt), his]
@[deprecated map_getElem_sublist (since := "2024-07-30")]
theorem map_get_sublist {l : List α} {is : List (Fin l.length)} (h : is.Pairwise (·.val < ·.val)) :
is.map (get l) <+ l := by
simpa using map_getElem_sublist h
/-- Given a sublist `l' <+ l`, there exists an increasing list of indices `is` such that
`l' = is.map fun i => l[i]`. -/
theorem sublist_eq_map_getElem {l l' : List α} (h : l' <+ l) : is : List (Fin l.length),
l' = is.map (l[·]) is.Pairwise (· < ·) := by
induction h with
| slnil => exact [], by simp
| cons _ _ IH =>
let is, IH := IH
refine is.map (·.succ), ?_
simpa [Function.comp_def, pairwise_map]
| cons₂ _ _ IH =>
rcases IH with is,IH
refine 0, by simp [Nat.zero_lt_succ] :: is.map (·.succ), ?_
simp [Function.comp_def, pairwise_map, IH, get_eq_getElem]
@[deprecated sublist_eq_map_getElem (since := "2024-07-30")]
theorem sublist_eq_map_get (h : l' <+ l) : is : List (Fin l.length),
l' = map (get l) is is.Pairwise (· < ·) := by
simpa using sublist_eq_map_getElem h
theorem pairwise_iff_getElem : Pairwise R l
(i j : Nat) (_hi : i < l.length) (_hj : j < l.length) (_hij : i < j), R l[i] l[j] := by
rw [pairwise_iff_forall_sublist]
constructor <;> intro h
· intros i j hi hj h'
apply h
simpa [h'] using map_getElem_sublist (is := [i, hi, j, hj])
· intros a b h'
have is, h', hij := sublist_eq_map_getElem h'
rcases is with | a', | b', <;> simp at h'
rcases h' with rfl, rfl
apply h; simpa using hij
end List

View File

@@ -0,0 +1,378 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Nat.TakeDrop
import Init.Data.List.Range
import Init.Data.List.Pairwise
/-!
# Lemmas about `List.range` and `List.enum`
-/
namespace List
open Nat
/-! ## Ranges and enumeration -/
/-! ### range' -/
theorem range'_succ (s n step) : range' s (n + 1) step = s :: range' (s + step) n step := by
simp [range', Nat.add_succ, Nat.mul_succ]
@[simp] theorem range'_one {s step : Nat} : range' s 1 step = [s] := rfl
@[simp] theorem length_range' (s step) : n : Nat, length (range' s n step) = n
| 0 => rfl
| _ + 1 => congrArg succ (length_range' _ _ _)
@[simp] theorem range'_eq_nil : range' s n step = [] n = 0 := by
rw [ length_eq_zero, length_range']
theorem mem_range' : {n}, m range' s n step i < n, m = s + step * i
| 0 => by simp [range', Nat.not_lt_zero]
| n + 1 => by
have h (i) : i n i = 0 j, i = succ j j < n := by
cases i <;> simp [Nat.succ_le, Nat.succ_inj']
simp [range', mem_range', Nat.lt_succ, h]; simp only [ exists_and_right, and_assoc]
rw [exists_comm]; simp [Nat.mul_succ, Nat.add_assoc, Nat.add_comm]
@[simp] 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
theorem pairwise_lt_range' s n (step := 1) (pos : 0 < step := by simp) :
Pairwise (· < ·) (range' s n step) :=
match s, n, step, pos with
| _, 0, _, _ => Pairwise.nil
| s, n + 1, step, pos => by
simp only [range'_succ, pairwise_cons]
constructor
· intros n m
rw [mem_range'] at m
omega
· exact pairwise_lt_range' (s + step) n step pos
theorem pairwise_le_range' s n (step := 1) :
Pairwise (· ·) (range' s n step) :=
match s, n, step with
| _, 0, _ => Pairwise.nil
| s, n + 1, step => by
simp only [range'_succ, pairwise_cons]
constructor
· intros n m
rw [mem_range'] at m
omega
· exact pairwise_le_range' (s + step) n step
theorem nodup_range' (s n : Nat) (step := 1) (h : 0 < step := by simp) : Nodup (range' s n step) :=
(pairwise_lt_range' s n step h).imp Nat.ne_of_lt
@[simp]
theorem map_add_range' (a) : s n step, map (a + ·) (range' s n step) = range' (a + s) n step
| _, 0, _ => rfl
| s, n + 1, step => by simp [range', map_add_range' _ (s + step) n step, Nat.add_assoc]
theorem map_sub_range' (a s n : Nat) (h : a s) :
map (· - a) (range' s n step) = range' (s - a) n step := by
conv => lhs; rw [ Nat.add_sub_cancel' h]
rw [ map_add_range', map_map, (?_ : __ = _), map_id]
funext x; apply Nat.add_sub_cancel_left
theorem range'_append : s m n step : Nat,
range' s m step ++ range' (s + step * m) n step = range' s (n + m) step
| s, 0, n, step => rfl
| s, m + 1, n, step => by
simpa [range', Nat.mul_succ, Nat.add_assoc, Nat.add_comm]
using range'_append (s + step) m n step
@[simp] theorem range'_append_1 (s m n : Nat) :
range' s m ++ range' (s + m) n = range' s (n + m) := by simpa using range'_append s m n 1
theorem range'_sublist_right {s m n : Nat} : range' s m step <+ range' s n step m n :=
fun h => by simpa only [length_range'] using h.length_le,
fun h => by rw [ Nat.sub_add_cancel h, range'_append]; apply sublist_append_left
theorem range'_subset_right {s m n : Nat} (step0 : 0 < step) :
range' s m step range' s n step m n := by
refine fun h => Nat.le_of_not_lt fun hn => ?_, fun h => (range'_sublist_right.2 h).subset
have i, h', e := mem_range'.1 <| h <| mem_range'.2 _, hn, rfl
exact Nat.ne_of_gt h' (Nat.eq_of_mul_eq_mul_left step0 (Nat.add_left_cancel e))
theorem range'_subset_right_1 {s m n : Nat} : range' s m range' s n m n :=
range'_subset_right (by decide)
theorem getElem?_range' (s step) :
{m n : Nat}, m < n (range' s n step)[m]? = some (s + step * m)
| 0, n + 1, _ => by simp [range'_succ]
| m + 1, n + 1, h => by
simp only [range'_succ, getElem?_cons_succ]
exact (getElem?_range' (s + step) step (Nat.lt_of_add_lt_add_right h)).trans <| by
simp [Nat.mul_succ, Nat.add_assoc, Nat.add_comm]
@[simp] theorem getElem_range' {n m step} (i) (H : i < (range' n m step).length) :
(range' n m step)[i] = n + step * i :=
(getElem?_eq_some.1 <| getElem?_range' n step (by simpa using H)).2
theorem range'_concat (s n : Nat) : range' s (n + 1) step = range' s n step ++ [s + step * n] := by
rw [Nat.add_comm n 1]; exact (range'_append s n 1 step).symm
theorem range'_1_concat (s n : Nat) : range' s (n + 1) = range' s n ++ [s + n] := by
simp [range'_concat]
/-! ### range -/
theorem range_loop_range' : s n : Nat, range.loop s (range' s n) = range' 0 (n + s)
| 0, n => rfl
| s + 1, n => by rw [ Nat.add_assoc, Nat.add_right_comm n s 1]; exact range_loop_range' s (n + 1)
theorem range_eq_range' (n : Nat) : range n = range' 0 n :=
(range_loop_range' n 0).trans <| by rw [Nat.zero_add]
theorem range_succ_eq_map (n : Nat) : range (n + 1) = 0 :: map succ (range n) := by
rw [range_eq_range', range_eq_range', range', Nat.add_comm, map_add_range']
congr; exact funext (Nat.add_comm 1)
theorem reverse_range' : s n : Nat, reverse (range' s n) = map (s + n - 1 - ·) (range n)
| s, 0 => rfl
| s, n + 1 => by
rw [range'_1_concat, reverse_append, range_succ_eq_map,
show s + (n + 1) - 1 = s + n from rfl, map, map_map]
simp [reverse_range', Nat.sub_right_comm, Nat.sub_sub]
theorem range'_eq_map_range (s n : Nat) : range' s n = map (s + ·) (range n) := by
rw [range_eq_range', map_add_range']; rfl
@[simp] theorem length_range (n : Nat) : length (range n) = n := by
simp only [range_eq_range', length_range']
@[simp] theorem range_eq_nil {n : Nat} : range n = [] n = 0 := by
rw [ length_eq_zero, length_range]
@[simp]
theorem range_sublist {m n : Nat} : range m <+ range n m n := by
simp only [range_eq_range', range'_sublist_right]
@[simp]
theorem range_subset {m n : Nat} : range m range n m n := by
simp only [range_eq_range', range'_subset_right, lt_succ_self]
@[simp]
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]
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
theorem pairwise_lt_range (n : Nat) : Pairwise (· < ·) (range n) := by
simp (config := {decide := true}) only [range_eq_range', pairwise_lt_range']
theorem pairwise_le_range (n : Nat) : Pairwise (· ·) (range n) :=
Pairwise.imp Nat.le_of_lt (pairwise_lt_range _)
theorem getElem?_range {m n : Nat} (h : m < n) : (range n)[m]? = some m := by
simp [range_eq_range', getElem?_range' _ _ h]
@[simp] theorem getElem_range {n : Nat} (m) (h : m < (range n).length) : (range n)[m] = m := by
simp [range_eq_range']
theorem range_succ (n : Nat) : range (succ n) = range n ++ [n] := by
simp only [range_eq_range', range'_1_concat, Nat.zero_add]
theorem range_add (a b : Nat) : range (a + b) = range a ++ (range b).map (a + ·) := by
rw [ range'_eq_map_range]
simpa [range_eq_range', Nat.add_comm] using (range'_append_1 0 a b).symm
theorem take_range (m n : Nat) : take m (range n) = range (min m n) := by
apply List.ext_getElem
· simp
· simp (config := { contextual := true }) [ getElem_take, Nat.lt_min]
theorem nodup_range (n : Nat) : Nodup (range n) := by
simp (config := {decide := true}) only [range_eq_range', nodup_range']
/-! ### iota -/
theorem iota_eq_reverse_range' : n : Nat, iota n = reverse (range' 1 n)
| 0 => rfl
| n + 1 => by simp [iota, range'_concat, iota_eq_reverse_range' n, reverse_append, Nat.add_comm]
@[simp] theorem length_iota (n : Nat) : length (iota n) = n := by simp [iota_eq_reverse_range']
@[simp]
theorem mem_iota {m n : Nat} : m iota n 1 m m n := by
simp [iota_eq_reverse_range', Nat.add_comm, Nat.lt_succ]
theorem pairwise_gt_iota (n : Nat) : Pairwise (· > ·) (iota n) := by
simpa only [iota_eq_reverse_range', pairwise_reverse] using pairwise_lt_range' 1 n
theorem nodup_iota (n : Nat) : Nodup (iota n) :=
(pairwise_gt_iota n).imp Nat.ne_of_gt
/-! ### enumFrom -/
@[simp]
theorem enumFrom_singleton (x : α) (n : Nat) : enumFrom n [x] = [(n, x)] :=
rfl
theorem mk_add_mem_enumFrom_iff_getElem? {n i : Nat} {x : α} {l : List α} :
(n + i, x) enumFrom n l l[i]? = some x := by
simp [mem_iff_get?]
theorem mk_mem_enumFrom_iff_le_and_getElem?_sub {n i : Nat} {x : α} {l : List α} :
(i, x) enumFrom n l n i l[i - n]? = x := by
if h : n i then
rcases Nat.exists_eq_add_of_le h with i, rfl
simp [mk_add_mem_enumFrom_iff_getElem?, Nat.add_sub_cancel_left]
else
have : k, n + k i := by rintro k rfl; simp at h
simp [h, mem_iff_get?, this]
theorem le_fst_of_mem_enumFrom {x : Nat × α} {n : Nat} {l : List α} (h : x enumFrom n l) :
n x.1 :=
(mk_mem_enumFrom_iff_le_and_getElem?_sub.1 h).1
theorem fst_lt_add_of_mem_enumFrom {x : Nat × α} {n : Nat} {l : List α} (h : x enumFrom n l) :
x.1 < n + length l := by
rcases mem_iff_get.1 h with i, rfl
simpa using i.isLt
theorem map_enumFrom (f : α β) (n : Nat) (l : List α) :
map (Prod.map id f) (enumFrom n l) = enumFrom n (map f l) := by
induction l generalizing n <;> simp_all
@[simp]
theorem enumFrom_map_fst (n) :
(l : List α), map Prod.fst (enumFrom n l) = range' n l.length
| [] => rfl
| _ :: _ => congrArg (cons _) (enumFrom_map_fst _ _)
@[simp]
theorem enumFrom_map_snd : (n) (l : List α), map Prod.snd (enumFrom n l) = l
| _, [] => rfl
| _, _ :: _ => congrArg (cons _) (enumFrom_map_snd _ _)
theorem snd_mem_of_mem_enumFrom {x : Nat × α} {n : Nat} {l : List α} (h : x enumFrom n l) : x.2 l :=
enumFrom_map_snd n l mem_map_of_mem _ h
theorem snd_eq_of_mem_enumFrom {x : Nat × α} {n : Nat} {l : List α} (h : x enumFrom n l) :
x.2 = l[x.1 - n]'(by have := le_fst_of_mem_enumFrom h; have := fst_lt_add_of_mem_enumFrom h; omega) := by
induction l generalizing n with
| nil => cases h
| cons hd tl ih =>
cases h with
| head h => simp
| tail h m =>
specialize ih m
have : x.1 - n = x.1 - (n + 1) + 1 := by
have := le_fst_of_mem_enumFrom m
omega
simp [this, ih]
theorem mem_enumFrom {x : α} {i j : Nat} {xs : List α} (h : (i, x) xs.enumFrom j) :
j i i < j + xs.length
x = xs[i - j]'(by have := le_fst_of_mem_enumFrom h; have := fst_lt_add_of_mem_enumFrom h; omega) :=
le_fst_of_mem_enumFrom h, fst_lt_add_of_mem_enumFrom h, snd_eq_of_mem_enumFrom h
theorem enumFrom_cons' (n : Nat) (x : α) (xs : List α) :
enumFrom n (x :: xs) = (n, x) :: (enumFrom n xs).map (Prod.map (· + 1) id) := by
rw [enumFrom_cons, Nat.add_comm, map_fst_add_enumFrom_eq_enumFrom]
theorem enumFrom_map (n : Nat) (l : List α) (f : α β) :
enumFrom n (l.map f) = (enumFrom n l).map (Prod.map id f) := by
induction l with
| nil => rfl
| cons hd tl IH =>
rw [map_cons, enumFrom_cons', enumFrom_cons', map_cons, map_map, IH, map_map]
rfl
theorem enumFrom_append (xs ys : List α) (n : Nat) :
enumFrom n (xs ++ ys) = enumFrom n xs ++ enumFrom (n + xs.length) ys := by
induction xs generalizing ys n with
| nil => simp
| cons x xs IH =>
rw [cons_append, enumFrom_cons, IH, cons_append, enumFrom_cons, length, Nat.add_right_comm,
Nat.add_assoc]
theorem enumFrom_eq_zip_range' (l : List α) {n : Nat} : l.enumFrom n = (range' n l.length).zip l :=
zip_of_prod (enumFrom_map_fst _ _) (enumFrom_map_snd _ _)
@[simp]
theorem unzip_enumFrom_eq_prod (l : List α) {n : Nat} :
(l.enumFrom n).unzip = (range' n l.length, l) := by
simp only [enumFrom_eq_zip_range', unzip_zip, length_range']
/-! ### enum -/
theorem enum_cons : (a::as).enum = (0, a) :: as.enumFrom 1 := rfl
theorem enum_cons' (x : α) (xs : List α) :
enum (x :: xs) = (0, x) :: (enum xs).map (Prod.map (· + 1) id) :=
enumFrom_cons' _ _ _
@[simp]
theorem enum_eq_nil {l : List α} : List.enum l = [] l = [] := enumFrom_eq_nil
@[simp] theorem enum_singleton (x : α) : enum [x] = [(0, x)] := rfl
@[simp] theorem enum_length : (enum l).length = l.length :=
enumFrom_length
@[simp]
theorem getElem?_enum (l : List α) (n : Nat) : (enum l)[n]? = l[n]?.map fun a => (n, a) := by
rw [enum, getElem?_enumFrom, Nat.zero_add]
@[simp]
theorem getElem_enum (l : List α) (i : Nat) (h : i < l.enum.length) :
l.enum[i] = (i, l[i]'(by simpa [enum_length] using h)) := by
simp [enum]
theorem mk_mem_enum_iff_getElem? {i : Nat} {x : α} {l : List α} : (i, x) enum l l[i]? = x := by
simp [enum, mk_mem_enumFrom_iff_le_and_getElem?_sub]
theorem mem_enum_iff_getElem? {x : Nat × α} {l : List α} : x enum l l[x.1]? = some x.2 :=
mk_mem_enum_iff_getElem?
theorem fst_lt_of_mem_enum {x : Nat × α} {l : List α} (h : x enum l) : x.1 < length l := by
simpa using fst_lt_add_of_mem_enumFrom h
theorem snd_mem_of_mem_enum {x : Nat × α} {l : List α} (h : x enum l) : x.2 l :=
snd_mem_of_mem_enumFrom h
theorem snd_eq_of_mem_enum {x : Nat × α} {l : List α} (h : x enum l) :
x.2 = l[x.1]'(fst_lt_of_mem_enum h) :=
snd_eq_of_mem_enumFrom h
theorem mem_enum {x : α} {i : Nat} {xs : List α} (h : (i, x) xs.enum) :
i < xs.length x = xs[i]'(fst_lt_of_mem_enum h) :=
by simpa using mem_enumFrom h
theorem map_enum (f : α β) (l : List α) : map (Prod.map id f) (enum l) = enum (map f l) :=
map_enumFrom f 0 l
@[simp] theorem enum_map_fst (l : List α) : map Prod.fst (enum l) = range l.length := by
simp only [enum, enumFrom_map_fst, range_eq_range']
@[simp]
theorem enum_map_snd (l : List α) : map Prod.snd (enum l) = l :=
enumFrom_map_snd _ _
theorem enum_map (l : List α) (f : α β) : (l.map f).enum = l.enum.map (Prod.map id f) :=
enumFrom_map _ _ _
theorem enum_append (xs ys : List α) : enum (xs ++ ys) = enum xs ++ enumFrom xs.length ys := by
simp [enum, enumFrom_append]
theorem enum_eq_zip_range (l : List α) : l.enum = (range l.length).zip l :=
zip_of_prod (enum_map_fst _) (enum_map_snd _)
@[simp]
theorem unzip_enum_eq_prod (l : List α) : l.enum.unzip = (range l.length, l) := by
simp only [enum_eq_zip_range, unzip_zip, length_range]
end List

View File

@@ -0,0 +1,129 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Sublist
import Init.Data.List.Nat.Basic
import Init.Data.List.Nat.TakeDrop
import Init.Data.Nat.Lemmas
/-!
# Further lemmas about `List.IsSuffix` / `List.IsPrefix` / `List.IsInfix`.
These are in a separate file from most of the lemmas about `List.IsSuffix`
as they required importing more lemmas about natural numbers, and use `omega`.
-/
namespace List
theorem IsSuffix.getElem {x y : List α} (h : x <:+ y) {n} (hn : n < x.length) :
x[n] = y[y.length - x.length + n]'(by have := h.length_le; omega) := by
rw [getElem_eq_getElem_reverse, h.reverse.getElem, getElem_reverse]
congr
have := h.length_le
omega
theorem isSuffix_iff : l₁ <:+ l₂
l₁.length l₂.length i (h : i < l₁.length), l₂[i + l₂.length - l₁.length]? = some l₁[i] := by
suffices l₁.length l₂.length l₁ <:+ l₂
l₁.length l₂.length i (h : i < l₁.length), l₂[i + l₂.length - l₁.length]? = some l₁[i] by
constructor
· intro h
exact this.mp h.length_le, h
· intro h
exact (this.mpr h).2
simp only [and_congr_right_iff]
intro le
rw [ reverse_prefix, isPrefix_iff]
simp only [length_reverse]
constructor
· intro w i h
specialize w (l₁.length - 1 - i) (by omega)
rw [getElem?_reverse (by omega)] at w
have p : l₂.length - 1 - (l₁.length - 1 - i) = i + l₂.length - l₁.length := by omega
rw [p] at w
rw [w, getElem_reverse]
congr
omega
· intro w i h
rw [getElem?_reverse]
specialize w (l₁.length - 1 - i) (by omega)
have p : l₁.length - 1 - i + l₂.length - l₁.length = l₂.length - 1 - i := by omega
rw [p] at w
rw [w, getElem_reverse]
exact Nat.lt_of_lt_of_le h le
theorem isInfix_iff : l₁ <:+: l₂
k, l₁.length + k l₂.length i (h : i < l₁.length), l₂[i + k]? = some l₁[i] := by
constructor
· intro h
obtain t, p, s := infix_iff_suffix_prefix.mp h
refine t.length - l₁.length, by have := p.length_le; have := s.length_le; omega, ?_
rw [isSuffix_iff] at p
obtain p', p := p
rw [isPrefix_iff] at s
intro i h
rw [s _ (by omega)]
specialize p i (by omega)
rw [Nat.add_sub_assoc (by omega)] at p
rw [ getElem?_eq_getElem, p]
· rintro k, le, w
refine l₂.take k, l₂.drop (k + l₁.length), ?_
ext1 i
rw [getElem?_append]
split
· rw [getElem?_append]
split
· rw [getElem?_take]; simp_all; omega
· simp_all
have p : i = (i - k) + k := by omega
rw [p, w _ (by omega), getElem?_eq_getElem]
· congr 2
omega
· omega
· rw [getElem?_drop]
congr
simp_all
omega
theorem suffix_iff_eq_append : l₁ <:+ l₂ take (length l₂ - length l₁) l₂ ++ l₁ = l₂ :=
by rintro r, rfl; simp only [length_append, Nat.add_sub_cancel_right, take_left], fun e =>
_, e
theorem prefix_take_iff {x y : List α} {n : Nat} : x <+: y.take n x <+: y x.length n := by
constructor
· intro h
constructor
· exact List.IsPrefix.trans h <| List.take_prefix n y
· replace h := h.length_le
rw [length_take, Nat.le_min] at h
exact h.left
· intro hp, hl
have hl' := hp.length_le
rw [List.prefix_iff_eq_take] at *
rw [hp, List.take_take]
simp [Nat.min_eq_left, hl, hl']
theorem suffix_iff_eq_drop : l₁ <:+ l₂ l₁ = drop (length l₂ - length l₁) l₂ :=
fun h => append_cancel_left <| (suffix_iff_eq_append.1 h).trans (take_append_drop _ _).symm,
fun e => e.symm drop_suffix _ _
theorem prefix_take_le_iff {L : List α} (hm : m < L.length) :
L.take m <+: L.take n m n := by
simp only [prefix_iff_eq_take, length_take]
induction m generalizing L n with
| zero => simp [Nat.min_eq_left, eq_self_iff_true, Nat.zero_le, take]
| succ m IH =>
cases L with
| nil => simp_all
| cons l ls =>
cases n with
| zero =>
simp
| succ n =>
simp only [length_cons, Nat.succ_eq_add_one, Nat.add_lt_add_iff_right] at hm
simp [ @IH n ls hm, Nat.min_eq_left, Nat.le_of_lt hm]
end List

View File

@@ -0,0 +1,544 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Zip
import Init.Data.List.Sublist
import Init.Data.Nat.Lemmas
/-!
# Further lemmas about `List.take`, `List.drop`, `List.zip` and `List.zipWith`.
These are in a separate file from most of the list lemmas
as they required importing more lemmas about natural numbers, and use `omega`.
-/
namespace List
open Nat
/-! ### take -/
@[simp] theorem length_take : (i : Nat) (l : List α), length (take i l) = min i (length l)
| 0, l => by simp [Nat.zero_min]
| succ n, [] => by simp [Nat.min_zero]
| succ n, _ :: l => by simp [Nat.succ_min_succ, length_take]
theorem length_take_le (n) (l : List α) : length (take n l) n := by simp [Nat.min_le_left]
theorem length_take_le' (n) (l : List α) : length (take n l) l.length :=
by simp [Nat.min_le_right]
theorem length_take_of_le (h : n length l) : length (take n l) = n := by simp [Nat.min_eq_left h]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the big list to the small list. -/
theorem getElem_take (L : List α) {i j : Nat} (hi : i < L.length) (hj : i < j) :
L[i] = (L.take j)[i]'(length_take .. Nat.lt_min.mpr hj, hi) :=
getElem_of_eq (take_append_drop j L).symm _ getElem_append ..
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the small list to the big list. -/
theorem getElem_take' (L : List α) {j i : Nat} {h : i < (L.take j).length} :
(L.take j)[i] =
L[i]'(Nat.lt_of_lt_of_le h (length_take_le' _ _)) := by
rw [length_take, Nat.lt_min] at h; rw [getElem_take L _ h.1]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the big list to the small list. -/
@[deprecated getElem_take (since := "2024-06-12")]
theorem get_take (L : List α) {i j : Nat} (hi : i < L.length) (hj : i < j) :
get L i, hi = get (L.take j) i, length_take .. Nat.lt_min.mpr hj, hi := by
simp [getElem_take _ hi hj]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the small list to the big list. -/
@[deprecated getElem_take (since := "2024-06-12")]
theorem get_take' (L : List α) {j i} :
get (L.take j) i =
get L i.1, Nat.lt_of_lt_of_le i.2 (length_take_le' _ _) := by
simp [getElem_take']
theorem getElem?_take_eq_none {l : List α} {n m : Nat} (h : n m) :
(l.take n)[m]? = none :=
getElem?_eq_none <| Nat.le_trans (length_take_le _ _) h
@[deprecated getElem?_take_eq_none (since := "2024-06-12")]
theorem get?_take_eq_none {l : List α} {n m : Nat} (h : n m) :
(l.take n).get? m = none := by
simp [getElem?_take_eq_none h]
theorem getElem?_take {l : List α} {n m : Nat} :
(l.take n)[m]? = if m < n then l[m]? else none := by
split
· next h => exact getElem?_take_of_lt h
· next h => exact getElem?_take_eq_none (Nat.le_of_not_lt h)
@[deprecated getElem?_take (since := "2024-06-12")]
theorem get?_take_eq_if {l : List α} {n m : Nat} :
(l.take n).get? m = if m < n then l.get? m else none := by
simp [getElem?_take]
theorem head?_take {l : List α} {n : Nat} :
(l.take n).head? = if n = 0 then none else l.head? := by
simp [head?_eq_getElem?, getElem?_take]
split
· rw [if_neg (by omega)]
· rw [if_pos (by omega)]
theorem head_take {l : List α} {n : Nat} (h : l.take n []) :
(l.take n).head h = l.head (by simp_all) := by
apply Option.some_inj.1
rw [ head?_eq_head, head?_eq_head, head?_take, if_neg]
simp_all
theorem getLast?_take {l : List α} : (l.take n).getLast? = if n = 0 then none else l[n - 1]?.or l.getLast? := by
rw [getLast?_eq_getElem?, getElem?_take, length_take]
split
· rw [if_neg (by omega)]
rw [Nat.min_def]
split
· rw [getElem?_eq_getElem (by omega)]
simp
· rw [ getLast?_eq_getElem?, getElem?_eq_none (by omega)]
simp
· rw [if_pos]
omega
theorem getLast_take {l : List α} (h : l.take n []) :
(l.take n).getLast h = l[n - 1]?.getD (l.getLast (by simp_all)) := by
rw [getLast_eq_getElem, getElem_take']
simp [length_take, Nat.min_def]
simp at h
split
· rw [getElem?_eq_getElem (by omega)]
simp
· rw [getElem?_eq_none (by omega), getLast_eq_getElem]
simp
theorem take_take : (n m) (l : List α), take n (take m l) = take (min n m) l
| n, 0, l => by rw [Nat.min_zero, take_zero, take_nil]
| 0, m, l => by rw [Nat.zero_min, take_zero, take_zero]
| succ n, succ m, nil => by simp only [take_nil]
| succ n, succ m, a :: l => by
simp only [take, succ_min_succ, take_take n m l]
theorem take_set_of_lt (a : α) {n m : Nat} (l : List α) (h : m < n) :
(l.set n a).take m = l.take m :=
List.ext_getElem? fun i => by
rw [getElem?_take, getElem?_take]
split
· next h' => rw [getElem?_set_ne (by omega)]
· rfl
@[simp] theorem take_replicate (a : α) : n m : Nat, take n (replicate m a) = replicate (min n m) a
| n, 0 => by simp [Nat.min_zero]
| 0, m => by simp [Nat.zero_min]
| succ n, succ m => by simp [replicate_succ, succ_min_succ, take_replicate]
@[simp] theorem drop_replicate (a : α) : n m : Nat, drop n (replicate m a) = replicate (m - n) a
| n, 0 => by simp
| 0, m => by simp
| succ n, succ m => by simp [replicate_succ, succ_sub_succ, drop_replicate]
/-- Taking the first `n` elements in `l₁ ++ l₂` is the same as appending the first `n` elements
of `l₁` to the first `n - l₁.length` elements of `l₂`. -/
theorem take_append_eq_append_take {l₁ l₂ : List α} {n : Nat} :
take n (l₁ ++ l₂) = take n l₁ ++ take (n - l₁.length) l₂ := by
induction l₁ generalizing n
· simp
· cases n
· simp [*]
· simp only [cons_append, take_succ_cons, length_cons, succ_eq_add_one, cons.injEq,
append_cancel_left_eq, true_and, *]
congr 1
omega
theorem take_append_of_le_length {l₁ l₂ : List α} {n : Nat} (h : n l₁.length) :
(l₁ ++ l₂).take n = l₁.take n := by
simp [take_append_eq_append_take, Nat.sub_eq_zero_of_le h]
/-- Taking the first `l₁.length + i` elements in `l₁ ++ l₂` is the same as appending the first
`i` elements of `l₂` to `l₁`. -/
theorem take_append {l₁ l₂ : List α} (i : Nat) :
take (l₁.length + i) (l₁ ++ l₂) = l₁ ++ take i l₂ := by
rw [take_append_eq_append_take, take_of_length_le (Nat.le_add_right _ _), Nat.add_sub_cancel_left]
@[simp]
theorem take_eq_take :
{l : List α} {m n : Nat}, l.take m = l.take n min m l.length = min n l.length
| [], m, n => by simp [Nat.min_zero]
| _ :: xs, 0, 0 => by simp
| x :: xs, m + 1, 0 => by simp [Nat.zero_min, succ_min_succ]
| x :: xs, 0, n + 1 => by simp [Nat.zero_min, succ_min_succ]
| x :: xs, m + 1, n + 1 => by simp [succ_min_succ, take_eq_take]
theorem take_add (l : List α) (m n : Nat) : l.take (m + n) = l.take m ++ (l.drop m).take n := by
suffices take (m + n) (take m l ++ drop m l) = take m l ++ take n (drop m l) by
rw [take_append_drop] at this
assumption
rw [take_append_eq_append_take, take_of_length_le, append_right_inj]
· simp only [take_eq_take, length_take, length_drop]
omega
apply Nat.le_trans (m := m)
· apply length_take_le
· apply Nat.le_add_right
theorem dropLast_take {n : Nat} {l : List α} (h : n < l.length) :
(l.take n).dropLast = l.take (n - 1) := by
simp only [dropLast_eq_take, length_take, Nat.le_of_lt h, Nat.min_eq_left, take_take, sub_le]
theorem map_eq_append_split {f : α β} {l : List α} {s₁ s₂ : List β}
(h : map f l = s₁ ++ s₂) : l₁ l₂, l = l₁ ++ l₂ map f l₁ = s₁ map f l₂ = s₂ := by
have := h
rw [ take_append_drop (length s₁) l] at this
rw [map_append] at this
refine _, _, rfl, append_inj this ?_
rw [length_map, length_take, Nat.min_eq_left]
rw [ length_map l f, h, length_append]
apply Nat.le_add_right
theorem take_prefix_take_left (l : List α) {m n : Nat} (h : m n) : take m l <+: take n l := by
rw [isPrefix_iff]
intro i w
rw [getElem?_take_of_lt, getElem_take', getElem?_eq_getElem]
simp only [length_take] at w
exact Nat.lt_of_lt_of_le (Nat.lt_of_lt_of_le w (Nat.min_le_left _ _)) h
theorem take_sublist_take_left (l : List α) {m n : Nat} (h : m n) : take m l <+ take n l :=
(take_prefix_take_left l h).sublist
theorem take_subset_take_left (l : List α) {m n : Nat} (h : m n) : take m l take n l :=
(take_sublist_take_left l h).subset
/-! ### drop -/
theorem lt_length_drop (L : List α) {i j : Nat} (h : i + j < L.length) : j < (L.drop i).length := by
have A : i < L.length := Nat.lt_of_le_of_lt (Nat.le.intro rfl) h
rw [(take_append_drop i L).symm] at h
simpa only [Nat.le_of_lt A, Nat.min_eq_left, Nat.add_lt_add_iff_left, length_take,
length_append] using h
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the big list to the small list. -/
theorem getElem_drop (L : List α) {i j : Nat} (h : i + j < L.length) :
L[i + j] = (L.drop i)[j]'(lt_length_drop L h) := by
have : i L.length := Nat.le_trans (Nat.le_add_right _ _) (Nat.le_of_lt h)
rw [getElem_of_eq (take_append_drop i L).symm h, getElem_append_right'] <;>
simp [Nat.min_eq_left this, Nat.add_sub_cancel_left, Nat.le_add_right]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the big list to the small list. -/
@[deprecated getElem_drop (since := "2024-06-12")]
theorem get_drop (L : List α) {i j : Nat} (h : i + j < L.length) :
get L i + j, h = get (L.drop i) j, lt_length_drop L h := by
simp [getElem_drop]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the small list to the big list. -/
theorem getElem_drop' (L : List α) {i : Nat} {j : Nat} {h : j < (L.drop i).length} :
(L.drop i)[j] = L[i + j]'(by
rw [Nat.add_comm]
exact Nat.add_lt_of_lt_sub (length_drop i L h)) := by
rw [getElem_drop]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the small list to the big list. -/
@[deprecated getElem_drop' (since := "2024-06-12")]
theorem get_drop' (L : List α) {i j} :
get (L.drop i) j = get L i + j, by
rw [Nat.add_comm]
exact Nat.add_lt_of_lt_sub (length_drop i L j.2) := by
simp [getElem_drop']
@[simp]
theorem getElem?_drop (L : List α) (i j : Nat) : (L.drop i)[j]? = L[i + j]? := by
ext
simp only [getElem?_eq_some, getElem_drop', Option.mem_def]
constructor <;> intro h, ha
· exact _, ha
· refine ?_, ha
rw [length_drop]
rw [Nat.add_comm] at h
apply Nat.lt_sub_of_add_lt h
@[deprecated getElem?_drop (since := "2024-06-12")]
theorem get?_drop (L : List α) (i j : Nat) : get? (L.drop i) j = get? L (i + j) := by
simp
theorem head?_drop (l : List α) (n : Nat) :
(l.drop n).head? = l[n]? := by
rw [head?_eq_getElem?, getElem?_drop, Nat.add_zero]
theorem head_drop {l : List α} {n : Nat} (h : l.drop n []) :
(l.drop n).head h = l[n]'(by simp_all) := by
have w : n < l.length := length_lt_of_drop_ne_nil h
simpa [head?_eq_head, getElem?_eq_getElem, h, w] using head?_drop l n
theorem getLast?_drop {l : List α} : (l.drop n).getLast? = if l.length n then none else l.getLast? := by
rw [getLast?_eq_getElem?, getElem?_drop]
rw [length_drop]
split
· rw [getElem?_eq_none (by omega)]
· rw [getLast?_eq_getElem?]
congr
omega
theorem getLast_drop {l : List α} (h : l.drop n []) :
(l.drop n).getLast h = l.getLast (ne_nil_of_length_pos (by simp at h; omega)) := by
simp only [ne_eq, drop_eq_nil_iff_le] at h
apply Option.some_inj.1
simp only [ getLast?_eq_getLast, getLast?_drop, ite_eq_right_iff]
omega
theorem drop_length_cons {l : List α} (h : l []) (a : α) :
(a :: l).drop l.length = [l.getLast h] := by
induction l generalizing a with
| nil =>
cases h rfl
| cons y l ih =>
simp only [drop, length]
by_cases h₁ : l = []
· simp [h₁]
rw [getLast_cons h₁]
exact ih h₁ y
/-- Dropping the elements up to `n` in `l₁ ++ l₂` is the same as dropping the elements up to `n`
in `l₁`, dropping the elements up to `n - l₁.length` in `l₂`, and appending them. -/
theorem drop_append_eq_append_drop {l₁ l₂ : List α} {n : Nat} :
drop n (l₁ ++ l₂) = drop n l₁ ++ drop (n - l₁.length) l₂ := by
induction l₁ generalizing n
· simp
· cases n
· simp [*]
· simp only [cons_append, drop_succ_cons, length_cons, succ_eq_add_one, append_cancel_left_eq, *]
congr 1
omega
theorem drop_append_of_le_length {l₁ l₂ : List α} {n : Nat} (h : n l₁.length) :
(l₁ ++ l₂).drop n = l₁.drop n ++ l₂ := by
simp [drop_append_eq_append_drop, Nat.sub_eq_zero_of_le h]
/-- Dropping the elements up to `l₁.length + i` in `l₁ + l₂` is the same as dropping the elements
up to `i` in `l₂`. -/
@[simp]
theorem drop_append {l₁ l₂ : List α} (i : Nat) : drop (l₁.length + i) (l₁ ++ l₂) = drop i l₂ := by
rw [drop_append_eq_append_drop, drop_eq_nil_of_le] <;>
simp [Nat.add_sub_cancel_left, Nat.le_add_right]
theorem set_eq_take_append_cons_drop {l : List α} {n : Nat} {a : α} :
l.set n a = if n < l.length then l.take n ++ a :: l.drop (n + 1) else l := by
split <;> rename_i h
· ext1 m
by_cases h' : m < n
· rw [getElem?_append_left (by simp [length_take]; omega), getElem?_set_ne (by omega),
getElem?_take_of_lt h']
· by_cases h'' : m = n
· subst h''
rw [getElem?_set_eq _, getElem?_append_right, length_take,
Nat.min_eq_left (by omega), Nat.sub_self, getElem?_cons_zero]
rw [length_take]
exact Nat.min_le_left m l.length
· have h''' : n < m := by omega
rw [getElem?_set_ne (by omega), getElem?_append_right, length_take,
Nat.min_eq_left (by omega)]
· obtain k, rfl := Nat.exists_eq_add_of_lt h'''
have p : n + k + 1 - n = k + 1 := by omega
rw [p]
rw [getElem?_cons_succ, getElem?_drop]
congr 1
omega
· rw [length_take]
exact Nat.le_trans (Nat.min_le_left _ _) (by omega)
· rw [set_eq_of_length_le]
omega
theorem exists_of_set {n : Nat} {a' : α} {l : List α} (h : n < l.length) :
l₁ l₂, l = l₁ ++ l[n] :: l₂ l₁.length = n l.set n a' = l₁ ++ a' :: l₂ := by
refine l.take n, l.drop (n + 1), by simp, length_take_of_le (Nat.le_of_lt h), ?_
simp [set_eq_take_append_cons_drop, h]
theorem drop_set_of_lt (a : α) {n m : Nat} (l : List α)
(hnm : n < m) : drop m (l.set n a) = l.drop m :=
ext_getElem? fun k => by simpa only [getElem?_drop] using getElem?_set_ne (by omega)
theorem drop_take : (m n : Nat) (l : List α), drop n (take m l) = take (m - n) (drop n l)
| 0, _, _ => by simp
| _, 0, _ => by simp
| _, _, [] => by simp
| m+1, n+1, h :: t => by
simp [take_succ_cons, drop_succ_cons, drop_take m n t]
congr 1
omega
theorem take_reverse {α} {xs : List α} {n : Nat} :
xs.reverse.take n = (xs.drop (xs.length - n)).reverse := by
by_cases h : n xs.length
· induction xs generalizing n <;>
simp only [reverse_cons, drop, reverse_nil, Nat.zero_sub, length, take_nil]
next xs_hd xs_tl xs_ih =>
cases Nat.lt_or_eq_of_le h with
| inl h' =>
have h' := Nat.le_of_succ_le_succ h'
rw [take_append_of_le_length, xs_ih h']
rw [show xs_tl.length + 1 - n = succ (xs_tl.length - n) from _, drop]
· rwa [succ_eq_add_one, Nat.sub_add_comm]
· rwa [length_reverse]
| inr h' =>
subst h'
rw [length, Nat.sub_self, drop]
suffices xs_tl.length + 1 = (xs_tl.reverse ++ [xs_hd]).length by
rw [this, take_length, reverse_cons]
rw [length_append, length_reverse]
rfl
· have w : xs.length - n = 0 := by omega
rw [take_of_length_le, w, drop_zero]
simp
omega
theorem drop_reverse {α} {xs : List α} {n : Nat} :
xs.reverse.drop n = (xs.take (xs.length - n)).reverse := by
by_cases h : n xs.length
· conv =>
rhs
rw [ reverse_reverse xs]
rw [ reverse_reverse xs] at h
generalize xs.reverse = xs' at h
rw [take_reverse]
· simp only [length_reverse, reverse_reverse] at *
congr
omega
· have w : xs.length - n = 0 := by omega
rw [drop_of_length_le, w, take_zero, reverse_nil]
simp
omega
theorem reverse_take {l : List α} {n : Nat} :
(l.take n).reverse = l.reverse.drop (l.length - n) := by
by_cases h : n l.length
· rw [drop_reverse]
congr
omega
· have w : l.length - n = 0 := by omega
rw [w, drop_zero, take_of_length_le]
omega
theorem reverse_drop {l : List α} {n : Nat} :
(l.drop n).reverse = l.reverse.take (l.length - n) := by
by_cases h : n l.length
· rw [take_reverse]
congr
omega
· have w : l.length - n = 0 := by omega
rw [w, take_zero, drop_of_length_le, reverse_nil]
omega
/-! ### rotateLeft -/
@[simp] theorem rotateLeft_replicate (n) (a : α) : rotateLeft (replicate m a) n = replicate m a := by
cases n with
| zero => simp
| succ n =>
suffices 1 < m m - (n + 1) % m + min ((n + 1) % m) m = m by
simpa [rotateLeft]
intro h
rw [Nat.min_eq_left (Nat.le_of_lt (Nat.mod_lt _ (by omega)))]
have : (n + 1) % m < m := Nat.mod_lt _ (by omega)
omega
/-! ### rotateRight -/
@[simp] theorem rotateRight_replicate (n) (a : α) : rotateRight (replicate m a) n = replicate m a := by
cases n with
| zero => simp
| succ n =>
suffices 1 < m m - (m - (n + 1) % m) + min (m - (n + 1) % m) m = m by
simpa [rotateRight]
intro h
have : (n + 1) % m < m := Nat.mod_lt _ (by omega)
rw [Nat.min_eq_left (by omega)]
omega
/-! ### zipWith -/
@[simp] theorem length_zipWith (f : α β γ) (l₁ l₂) :
length (zipWith f l₁ l₂) = min (length l₁) (length l₂) := by
induction l₁ generalizing l₂ <;> cases l₂ <;>
simp_all [succ_min_succ, Nat.zero_min, Nat.min_zero]
theorem lt_length_left_of_zipWith {f : α β γ} {i : Nat} {l : List α} {l' : List β}
(h : i < (zipWith f l l').length) : i < l.length := by rw [length_zipWith] at h; omega
theorem lt_length_right_of_zipWith {f : α β γ} {i : Nat} {l : List α} {l' : List β}
(h : i < (zipWith f l l').length) : i < l'.length := by rw [length_zipWith] at h; omega
@[simp]
theorem getElem_zipWith {f : α β γ} {l : List α} {l' : List β}
{i : Nat} {h : i < (zipWith f l l').length} :
(zipWith f l l')[i] =
f (l[i]'(lt_length_left_of_zipWith h))
(l'[i]'(lt_length_right_of_zipWith h)) := by
rw [ Option.some_inj, getElem?_eq_getElem, getElem?_zipWith_eq_some]
exact
l[i]'(lt_length_left_of_zipWith h), l'[i]'(lt_length_right_of_zipWith h),
by rw [getElem?_eq_getElem], by rw [getElem?_eq_getElem]; exact rfl, rfl
theorem zipWith_eq_zipWith_take_min : (l₁ : List α) (l₂ : List β),
zipWith f l₁ l₂ = zipWith f (l₁.take (min l₁.length l₂.length)) (l₂.take (min l₁.length l₂.length))
| [], _ => by simp
| _, [] => by simp
| a :: l₁, b :: l₂ => by simp [succ_min_succ, zipWith_eq_zipWith_take_min l₁ l₂]
theorem reverse_zipWith (h : l.length = l'.length) :
(zipWith f l l').reverse = zipWith f l.reverse l'.reverse := by
induction l generalizing l' with
| nil => simp
| cons hd tl hl =>
cases l' with
| nil => simp
| cons hd' tl' =>
simp only [Nat.add_right_cancel_iff, length] at h
have : tl.reverse.length = tl'.reverse.length := by simp [h]
simp [hl h, zipWith_append _ _ _ _ _ this]
@[deprecated reverse_zipWith (since := "2024-07-28")] abbrev zipWith_distrib_reverse := @reverse_zipWith
@[simp] theorem zipWith_replicate {a : α} {b : β} {m n : Nat} :
zipWith f (replicate m a) (replicate n b) = replicate (min m n) (f a b) := by
rw [zipWith_eq_zipWith_take_min]
simp
/-! ### zip -/
@[simp] theorem length_zip (l₁ : List α) (l₂ : List β) :
length (zip l₁ l₂) = min (length l₁) (length l₂) := by
simp [zip]
theorem lt_length_left_of_zip {i : Nat} {l : List α} {l' : List β} (h : i < (zip l l').length) :
i < l.length :=
lt_length_left_of_zipWith h
theorem lt_length_right_of_zip {i : Nat} {l : List α} {l' : List β} (h : i < (zip l l').length) :
i < l'.length :=
lt_length_right_of_zipWith h
@[simp]
theorem getElem_zip {l : List α} {l' : List β} {i : Nat} {h : i < (zip l l').length} :
(zip l l')[i] =
(l[i]'(lt_length_left_of_zip h), l'[i]'(lt_length_right_of_zip h)) :=
getElem_zipWith (h := h)
theorem zip_eq_zip_take_min : (l₁ : List α) (l₂ : List β),
zip l₁ l₂ = zip (l₁.take (min l₁.length l₂.length)) (l₂.take (min l₁.length l₂.length))
| [], _ => by simp
| _, [] => by simp
| a :: l₁, b :: l₂ => by simp [succ_min_succ, zip_eq_zip_take_min l₁ l₂]
@[simp] theorem zip_replicate {a : α} {b : β} {m n : Nat} :
zip (replicate m a) (replicate n b) = replicate (min m n) (a, b) := by
rw [zip_eq_zip_take_min]
simp
end List

View File

@@ -0,0 +1,307 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Sublist
import Init.Data.List.Attach
/-!
# Lemmas about `List.Pairwise` and `List.Nodup`.
-/
namespace List
open Nat
/-! ## Pairwise and Nodup -/
/-! ### Pairwise -/
theorem Pairwise.sublist : l₁ <+ l₂ l₂.Pairwise R l₁.Pairwise R
| .slnil, h => h
| .cons _ s, .cons _ h₂ => h₂.sublist s
| .cons₂ _ s, .cons h₁ h₂ => (h₂.sublist s).cons fun _ h => h₁ _ (s.subset h)
theorem Pairwise.imp {α R S} (H : {a b}, R a b S a b) :
{l : List α}, l.Pairwise R l.Pairwise S
| _, .nil => .nil
| _, .cons h₁ h₂ => .cons (H h₁ ·) (h₂.imp H)
theorem rel_of_pairwise_cons (p : (a :: l).Pairwise R) : {a'}, a' l R a a' :=
(pairwise_cons.1 p).1 _
theorem Pairwise.of_cons (p : (a :: l).Pairwise R) : Pairwise R l :=
(pairwise_cons.1 p).2
theorem Pairwise.tail : {l : List α} (_p : Pairwise R l), Pairwise R l.tail
| [], h => h
| _ :: _, h => h.of_cons
theorem Pairwise.imp_of_mem {S : α α Prop}
(H : {a b}, a l b l R a b S a b) (p : Pairwise R l) : Pairwise S l := by
induction p with
| nil => constructor
| @cons a l r _ ih =>
constructor
· exact fun x h => H (mem_cons_self ..) (mem_cons_of_mem _ h) <| r x h
· exact ih fun m m' => H (mem_cons_of_mem _ m) (mem_cons_of_mem _ m')
theorem Pairwise.and (hR : Pairwise R l) (hS : Pairwise S l) :
l.Pairwise fun a b => R a b S a b := by
induction hR with
| nil => simp only [Pairwise.nil]
| cons R1 _ IH =>
simp only [Pairwise.nil, pairwise_cons] at hS
exact fun b bl => R1 b bl, hS.1 b bl, IH hS.2
theorem pairwise_and_iff : l.Pairwise (fun a b => R a b S a b) Pairwise R l Pairwise S l :=
fun h => h.imp fun h => h.1, h.imp fun h => h.2, fun hR, hS => hR.and hS
theorem Pairwise.imp₂ (H : a b, R a b S a b T a b)
(hR : Pairwise R l) (hS : l.Pairwise S) : l.Pairwise T :=
(hR.and hS).imp fun h₁, h₂ => H _ _ h₁ h₂
theorem Pairwise.iff_of_mem {S : α α Prop} {l : List α}
(H : {a b}, a l b l (R a b S a b)) : Pairwise R l Pairwise S l :=
Pairwise.imp_of_mem fun m m' => (H m m').1, Pairwise.imp_of_mem fun m m' => (H m m').2
theorem Pairwise.iff {S : α α Prop} (H : a b, R a b S a b) {l : List α} :
Pairwise R l Pairwise S l :=
Pairwise.iff_of_mem fun _ _ => H ..
theorem pairwise_of_forall {l : List α} (H : x y, R x y) : Pairwise R l := by
induction l <;> simp [*]
theorem Pairwise.and_mem {l : List α} :
Pairwise R l Pairwise (fun x y => x l y l R x y) l :=
Pairwise.iff_of_mem <| by simp (config := { contextual := true })
theorem Pairwise.imp_mem {l : List α} :
Pairwise R l Pairwise (fun x y => x l y l R x y) l :=
Pairwise.iff_of_mem <| by simp (config := { contextual := true })
theorem Pairwise.forall_of_forall_of_flip (h₁ : x l, R x x) (h₂ : Pairwise R l)
(h₃ : l.Pairwise (flip R)) : x, x l y, y l R x y := by
induction l with
| nil => exact forall_mem_nil _
| cons a l ih =>
rw [pairwise_cons] at h₂ h₃
simp only [mem_cons]
rintro x (rfl | hx) y (rfl | hy)
· exact h₁ _ (l.mem_cons_self _)
· exact h₂.1 _ hy
· exact h₃.1 _ hx
· exact ih (fun x hx => h₁ _ <| mem_cons_of_mem _ hx) h₂.2 h₃.2 hx hy
theorem pairwise_singleton (R) (a : α) : Pairwise R [a] := by simp
theorem pairwise_pair {a b : α} : Pairwise R [a, b] R a b := by simp
theorem pairwise_map {l : List α} :
(l.map f).Pairwise R l.Pairwise fun a b => R (f a) (f b) := by
induction l
· simp
· simp only [map, pairwise_cons, forall_mem_map, *]
theorem Pairwise.of_map {S : β β Prop} (f : α β) (H : a b : α, S (f a) (f b) R a b)
(p : Pairwise S (map f l)) : Pairwise R l :=
(pairwise_map.1 p).imp (H _ _)
theorem Pairwise.map {S : β β Prop} (f : α β) (H : a b : α, R a b S (f a) (f b))
(p : Pairwise R l) : Pairwise S (map f l) :=
pairwise_map.2 <| p.imp (H _ _)
theorem pairwise_filterMap (f : β Option α) {l : List β} :
Pairwise R (filterMap f l) Pairwise (fun a a' : β => b f a, b' f a', R b b') l := by
let _S (a a' : β) := b f a, b' f a', R b b'
simp only [Option.mem_def]
induction l with
| nil => simp only [filterMap, Pairwise.nil]
| cons a l IH => ?_
match e : f a with
| none =>
rw [filterMap_cons_none e, pairwise_cons]
simp only [e, false_implies, implies_true, true_and, IH]
| some b =>
rw [filterMap_cons_some e]
simpa [IH, e] using fun _ =>
fun h a ha b hab => h _ _ ha hab, fun h a b ha hab => h _ ha _ hab
theorem Pairwise.filterMap {S : β β Prop} (f : α Option β)
(H : a a' : α, R a a' b f a, b' f a', S b b') {l : List α} (p : Pairwise R l) :
Pairwise S (filterMap f l) :=
(pairwise_filterMap _).2 <| p.imp (H _ _)
@[deprecated Pairwise.filterMap (since := "2024-07-29")] abbrev Pairwise.filter_map := @Pairwise.filterMap
theorem pairwise_filter (p : α Prop) [DecidablePred p] {l : List α} :
Pairwise R (filter p l) Pairwise (fun x y => p x p y R x y) l := by
rw [ filterMap_eq_filter, pairwise_filterMap]
simp
theorem Pairwise.filter (p : α Bool) : Pairwise R l Pairwise R (filter p l) :=
Pairwise.sublist (filter_sublist _)
theorem pairwise_append {l₁ l₂ : List α} :
(l₁ ++ l₂).Pairwise R l₁.Pairwise R l₂.Pairwise R a l₁, b l₂, R a b := by
induction l₁ <;> simp [*, or_imp, forall_and, and_assoc, and_left_comm]
theorem pairwise_append_comm {R : α α Prop} (s : {x y}, R x y R y x) {l₁ l₂ : List α} :
Pairwise R (l₁ ++ l₂) Pairwise R (l₂ ++ l₁) := by
have (l₁ l₂ : List α) (H : x : α, x l₁ y : α, y l₂ R x y)
(x : α) (xm : x l₂) (y : α) (ym : y l₁) : R x y := s (H y ym x xm)
simp only [pairwise_append, and_left_comm]; rw [Iff.intro (this l₁ l₂) (this l₂ l₁)]
theorem pairwise_middle {R : α α Prop} (s : {x y}, R x y R y x) {a : α} {l₁ l₂ : List α} :
Pairwise R (l₁ ++ a :: l₂) Pairwise R (a :: (l₁ ++ l₂)) := by
show Pairwise R (l₁ ++ ([a] ++ l₂)) Pairwise R ([a] ++ l₁ ++ l₂)
rw [ append_assoc, pairwise_append, @pairwise_append _ _ ([a] ++ l₁), pairwise_append_comm s]
simp only [mem_append, or_comm]
theorem pairwise_join {L : List (List α)} :
Pairwise R (join L)
( l L, Pairwise R l) Pairwise (fun l₁ l₂ => x l₁, y l₂, R x y) L := by
induction L with
| nil => simp
| cons l L IH =>
simp only [join, pairwise_append, IH, mem_join, exists_imp, and_imp, forall_mem_cons,
pairwise_cons, and_assoc, and_congr_right_iff]
rw [and_comm, and_congr_left_iff]
intros; exact fun h a b c d e => h c d e a b, fun h c d e a b => h a b c d e
theorem pairwise_bind {R : β β Prop} {l : List α} {f : α List β} :
List.Pairwise R (l.bind f)
( a l, Pairwise R (f a)) Pairwise (fun a₁ a₂ => x f a₁, y f a₂, R x y) l := by
simp [List.bind, pairwise_join, pairwise_map]
theorem pairwise_reverse {l : List α} :
l.reverse.Pairwise R l.Pairwise (fun a b => R b a) := by
induction l <;> simp [*, pairwise_append, and_comm]
@[simp] theorem pairwise_replicate {n : Nat} {a : α} :
(replicate n a).Pairwise R n 1 R a a := by
induction n with
| zero => simp
| succ n ih =>
simp only [replicate_succ, pairwise_cons, mem_replicate, ne_eq, and_imp,
forall_eq_apply_imp_iff, ih]
constructor
· rintro h, h' | h'
· by_cases w : n = 0
· left
subst w
simp
· right
exact h w
· right
exact h'
· rintro (h | h)
· obtain rfl := eq_zero_of_le_zero (le_of_lt_succ h)
simp
· exact fun _ => h, Or.inr h
theorem Pairwise.drop {l : List α} {n : Nat} (h : List.Pairwise R l) : List.Pairwise R (l.drop n) :=
h.sublist (drop_sublist _ _)
theorem Pairwise.take {l : List α} {n : Nat} (h : List.Pairwise R l) : List.Pairwise R (l.take n) :=
h.sublist (take_sublist _ _)
theorem pairwise_iff_forall_sublist : l.Pairwise R ( {a b}, [a,b] <+ l R a b) := by
induction l with
| nil => simp
| cons hd tl IH =>
rw [List.pairwise_cons]
constructor <;> intro h
· intro
| a, b, .cons _ hab => exact IH.mp h.2 hab
| _, b, .cons₂ _ hab => refine h.1 _ (hab.subset ?_); simp
· constructor
· intro x hx
apply h
rw [List.cons_sublist_cons, List.singleton_sublist]
exact hx
· apply IH.mpr
intro a b hab
apply h; exact hab.cons _
theorem Pairwise.rel_of_mem_take_of_mem_drop
{l : List α} (h : l.Pairwise R) (hx : x l.take n) (hy : y l.drop n) : R x y := by
apply pairwise_iff_forall_sublist.mp h
rw [ take_append_drop n l, sublist_append_iff]
refine [x], [y], rfl, by simpa, by simpa
theorem Pairwise.rel_of_mem_append
{l₁ l₂ : List α} (h : (l₁ ++ l₂).Pairwise R) (hx : x l₁) (hy : y l₂) : R x y := by
apply pairwise_iff_forall_sublist.mp h
rw [sublist_append_iff]
exact [x], [y], rfl, by simpa, by simpa
theorem pairwise_of_forall_mem_list {l : List α} {r : α α Prop} (h : a l, b l, r a b) :
l.Pairwise r := by
rw [pairwise_iff_forall_sublist]
intro a b hab
apply h <;> (apply hab.subset; simp)
theorem pairwise_pmap {p : β Prop} {f : b, p b α} {l : List β} (h : x l, p x) :
Pairwise R (l.pmap f h)
Pairwise (fun b₁ b₂ => (h₁ : p b₁) (h₂ : p b₂), R (f b₁ h₁) (f b₂ h₂)) l := by
induction l with
| nil => simp
| cons a l ihl =>
obtain _, hl : p a b, b l p b := by simpa using h
simp only [ihl hl, pairwise_cons, exists₂_imp, pmap, and_congr_left_iff, mem_pmap]
refine fun _ => fun H b hb _ hpb => H _ _ hb rfl, ?_
rintro H _ b hb rfl
exact H b hb _ _
theorem Pairwise.pmap {l : List α} (hl : Pairwise R l) {p : α Prop} {f : a, p a β}
(h : x l, p x) {S : β β Prop}
(hS : x (hx : p x) y (hy : p y), R x y S (f x hx) (f y hy)) :
Pairwise S (l.pmap f h) := by
refine (pairwise_pmap h).2 (Pairwise.imp_of_mem ?_ hl)
intros; apply hS; assumption
/-! ### Nodup -/
@[simp]
theorem nodup_nil : @Nodup α [] :=
Pairwise.nil
@[simp]
theorem nodup_cons {a : α} {l : List α} : Nodup (a :: l) a l Nodup l := by
simp only [Nodup, pairwise_cons, forall_mem_ne]
theorem Nodup.sublist : l₁ <+ l₂ Nodup l₂ Nodup l₁ :=
Pairwise.sublist
theorem Sublist.nodup : l₁ <+ l₂ Nodup l₂ Nodup l₁ :=
Nodup.sublist
theorem getElem?_inj {xs : List α}
(h₀ : i < xs.length) (h₁ : Nodup xs) (h₂ : xs[i]? = xs[j]?) : i = j := by
induction xs generalizing i j with
| nil => cases h₀
| cons x xs ih =>
match i, j with
| 0, 0 => rfl
| i+1, j+1 =>
cases h₁ with
| cons ha h₁ =>
simp only [getElem?_cons_succ] at h₂
exact congrArg (· + 1) (ih (Nat.lt_of_succ_lt_succ h₀) h₁ h₂)
| i+1, 0 => ?_
| 0, j+1 => ?_
all_goals
simp only [get?_eq_getElem?, getElem?_cons_zero, getElem?_cons_succ] at h₂
cases h₁; rename_i h' h
have := h x ?_ rfl; cases this
rw [mem_iff_get?]
simp only [get?_eq_getElem?]
exact _, h₂; exact _ , h₂.symm
@[simp] theorem nodup_replicate {n : Nat} {a : α} :
(replicate n a).Nodup n 1 := by simp [Nodup]
end List

View File

@@ -0,0 +1,465 @@
/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Jeremy Avigad, Mario Carneiro
-/
prelude
import Init.Data.List.Pairwise
import Init.Data.List.Erase
/-!
# List Permutations
This file introduces the `List.Perm` relation, which is true if two lists are permutations of one
another.
## Notation
The notation `~` is used for permutation equivalence.
-/
open Nat
namespace List
open Perm (swap)
@[simp, refl] protected theorem Perm.refl : l : List α, l ~ l
| [] => .nil
| x :: xs => (Perm.refl xs).cons x
protected theorem Perm.rfl {l : List α} : l ~ l := .refl _
theorem Perm.of_eq (h : l₁ = l₂) : l₁ ~ l₂ := h .rfl
protected theorem Perm.symm {l₁ l₂ : List α} (h : l₁ ~ l₂) : l₂ ~ l₁ := by
induction h with
| nil => exact nil
| cons _ _ ih => exact cons _ ih
| swap => exact swap ..
| trans _ _ ih₁ ih₂ => exact trans ih₂ ih₁
theorem perm_comm {l₁ l₂ : List α} : l₁ ~ l₂ l₂ ~ l₁ := Perm.symm, Perm.symm
theorem Perm.swap' (x y : α) {l₁ l₂ : List α} (p : l₁ ~ l₂) : y :: x :: l₁ ~ x :: y :: l₂ :=
(swap ..).trans <| p.cons _ |>.cons _
/--
Similar to `Perm.recOn`, but the `swap` case is generalized to `Perm.swap'`,
where the tail of the lists are not necessarily the same.
-/
@[elab_as_elim] theorem Perm.recOnSwap'
{motive : (l₁ : List α) (l₂ : List α) l₁ ~ l₂ Prop} {l₁ l₂ : List α} (p : l₁ ~ l₂)
(nil : motive [] [] .nil)
(cons : x {l₁ l₂}, (h : l₁ ~ l₂) motive l₁ l₂ h motive (x :: l₁) (x :: l₂) (.cons x h))
(swap' : x y {l₁ l₂}, (h : l₁ ~ l₂) motive l₁ l₂ h
motive (y :: x :: l₁) (x :: y :: l₂) (.swap' _ _ h))
(trans : {l₁ l₂ l₃}, (h₁ : l₁ ~ l₂) (h₂ : l₂ ~ l₃) motive l₁ l₂ h₁ motive l₂ l₃ h₂
motive l₁ l₃ (.trans h₁ h₂)) : motive l₁ l₂ p :=
have motive_refl l : motive l l (.refl l) :=
List.recOn l nil fun x xs ih => cons x (.refl xs) ih
Perm.recOn p nil cons (fun x y l => swap' x y (.refl l) (motive_refl l)) trans
theorem Perm.eqv (α) : Equivalence (@Perm α) := .refl, .symm, .trans
instance isSetoid (α) : Setoid (List α) := .mk Perm (Perm.eqv α)
theorem Perm.mem_iff {a : α} {l₁ l₂ : List α} (p : l₁ ~ l₂) : a l₁ a l₂ := by
induction p with
| nil => rfl
| cons _ _ ih => simp only [mem_cons, ih]
| swap => simp only [mem_cons, or_left_comm]
| trans _ _ ih₁ ih₂ => simp only [ih₁, ih₂]
theorem Perm.subset {l₁ l₂ : List α} (p : l₁ ~ l₂) : l₁ l₂ := fun _ => p.mem_iff.mp
theorem Perm.append_right {l₁ l₂ : List α} (t₁ : List α) (p : l₁ ~ l₂) : l₁ ++ t₁ ~ l₂ ++ t₁ := by
induction p with
| nil => rfl
| cons _ _ ih => exact cons _ ih
| swap => exact swap ..
| trans _ _ ih₁ ih₂ => exact trans ih₁ ih₂
theorem Perm.append_left {t₁ t₂ : List α} : l : List α, t₁ ~ t₂ l ++ t₁ ~ l ++ t₂
| [], p => p
| x :: xs, p => (p.append_left xs).cons x
theorem Perm.append {l₁ l₂ t₁ t₂ : List α} (p₁ : l₁ ~ l₂) (p₂ : t₁ ~ t₂) : l₁ ++ t₁ ~ l₂ ++ t₂ :=
(p₁.append_right t₁).trans (p₂.append_left l₂)
theorem Perm.append_cons (a : α) {h₁ h₂ t₁ t₂ : List α} (p₁ : h₁ ~ h₂) (p₂ : t₁ ~ t₂) :
h₁ ++ a :: t₁ ~ h₂ ++ a :: t₂ := p₁.append (p₂.cons a)
@[simp] theorem perm_middle {a : α} : {l₁ l₂ : List α}, l₁ ++ a :: l₂ ~ a :: (l₁ ++ l₂)
| [], _ => .refl _
| b :: _, _ => (Perm.cons _ perm_middle).trans (swap a b _)
@[simp] theorem perm_append_singleton (a : α) (l : List α) : l ++ [a] ~ a :: l :=
perm_middle.trans <| by rw [append_nil]
theorem perm_append_comm : {l₁ l₂ : List α}, l₁ ++ l₂ ~ l₂ ++ l₁
| [], l₂ => by simp
| a :: t, l₂ => (perm_append_comm.cons _).trans perm_middle.symm
theorem perm_append_comm_assoc (l₁ l₂ l₃ : List α) :
Perm (l₁ ++ (l₂ ++ l₃)) (l₂ ++ (l₁ ++ l₃)) := by
simpa only [List.append_assoc] using perm_append_comm.append_right _
theorem concat_perm (l : List α) (a : α) : concat l a ~ a :: l := by simp
theorem Perm.length_eq {l₁ l₂ : List α} (p : l₁ ~ l₂) : length l₁ = length l₂ := by
induction p with
| nil => rfl
| cons _ _ ih => simp only [length_cons, ih]
| swap => rfl
| trans _ _ ih₁ ih₂ => simp only [ih₁, ih₂]
theorem Perm.eq_nil {l : List α} (p : l ~ []) : l = [] := eq_nil_of_length_eq_zero p.length_eq
theorem Perm.nil_eq {l : List α} (p : [] ~ l) : [] = l := p.symm.eq_nil.symm
@[simp] theorem perm_nil {l₁ : List α} : l₁ ~ [] l₁ = [] :=
fun p => p.eq_nil, fun e => e .rfl
@[simp] theorem nil_perm {l₁ : List α} : [] ~ l₁ l₁ = [] := perm_comm.trans perm_nil
@[simp]
theorem not_perm_nil_cons (x : α) (l : List α) : ¬[] ~ x :: l := (nomatch ·.symm.eq_nil)
@[simp]
theorem not_perm_cons_nil {l : List α} {a : α} : ¬(Perm (a::l) []) :=
fun h => by simpa using h.length_eq
theorem Perm.isEmpty_eq {l l' : List α} (h : Perm l l') : l.isEmpty = l'.isEmpty := by
cases l <;> cases l' <;> simp_all
@[simp] theorem reverse_perm : l : List α, reverse l ~ l
| [] => .nil
| a :: l => reverse_cons .. (perm_append_singleton _ _).trans ((reverse_perm l).cons a)
theorem perm_cons_append_cons {l l₁ l₂ : List α} (a : α) (p : l ~ l₁ ++ l₂) :
a :: l ~ l₁ ++ a :: l₂ := (p.cons a).trans perm_middle.symm
@[simp] theorem perm_replicate {n : Nat} {a : α} {l : List α} :
l ~ replicate n a l = replicate n a := by
refine fun p => eq_replicate.2 ?_, fun h => h .rfl
exact p.length_eq.trans <| length_replicate .., fun _b m => eq_of_mem_replicate <| p.subset m
@[simp] theorem replicate_perm {n : Nat} {a : α} {l : List α} :
replicate n a ~ l replicate n a = l := (perm_comm.trans perm_replicate).trans eq_comm
@[simp] theorem perm_singleton {a : α} {l : List α} : l ~ [a] l = [a] := perm_replicate (n := 1)
@[simp] theorem singleton_perm {a : α} {l : List α} : [a] ~ l [a] = l := replicate_perm (n := 1)
theorem Perm.eq_singleton (h : l ~ [a]) : l = [a] := perm_singleton.mp h
theorem Perm.singleton_eq (h : [a] ~ l) : [a] = l := singleton_perm.mp h
theorem singleton_perm_singleton {a b : α} : [a] ~ [b] a = b := by simp
theorem perm_cons_erase [DecidableEq α] {a : α} {l : List α} (h : a l) : l ~ a :: l.erase a :=
let _l₁, _l₂, _, e₁, e₂ := exists_erase_eq h
e₂ e₁ perm_middle
theorem Perm.filterMap (f : α Option β) {l₁ l₂ : List α} (p : l₁ ~ l₂) :
filterMap f l₁ ~ filterMap f l₂ := by
induction p with
| nil => simp
| cons x _p IH => cases h : f x <;> simp [h, filterMap_cons, IH, Perm.cons]
| swap x y l₂ => cases hx : f x <;> cases hy : f y <;> simp [hx, hy, filterMap_cons, swap]
| trans _p₁ _p₂ IH₁ IH₂ => exact IH₁.trans IH₂
theorem Perm.map (f : α β) {l₁ l₂ : List α} (p : l₁ ~ l₂) : map f l₁ ~ map f l₂ :=
filterMap_eq_map f p.filterMap _
theorem Perm.pmap {p : α Prop} (f : a, p a β) {l₁ l₂ : List α} (p : l₁ ~ l₂) {H₁ H₂} :
pmap f l₁ H₁ ~ pmap f l₂ H₂ := by
induction p with
| nil => simp
| cons x _p IH => simp [IH, Perm.cons]
| swap x y => simp [swap]
| trans _p₁ p₂ IH₁ IH₂ => exact IH₁.trans (IH₂ (H₁ := fun a m => H₂ a (p₂.subset m)))
theorem Perm.filter (p : α Bool) {l₁ l₂ : List α} (s : l₁ ~ l₂) :
filter p l₁ ~ filter p l₂ := by rw [ filterMap_eq_filter]; apply s.filterMap
theorem filter_append_perm (p : α Bool) (l : List α) :
filter p l ++ filter (fun x => !p x) l ~ l := by
induction l with
| nil => rfl
| cons x l ih =>
by_cases h : p x <;> simp [h]
· exact ih.cons x
· exact Perm.trans (perm_append_comm.trans (perm_append_comm.cons _)) (ih.cons x)
theorem exists_perm_sublist {l₁ l₂ l₂' : List α} (s : l₁ <+ l₂) (p : l₂ ~ l₂') :
l₁', l₁' ~ l₁ l₁' <+ l₂' := by
induction p generalizing l₁ with
| nil => exact [], sublist_nil.mp s .rfl, nil_sublist _
| cons x _ IH =>
match s with
| .cons _ s => let l₁', p', s' := IH s; exact l₁', p', s'.cons _
| .cons₂ _ s => let l₁', p', s' := IH s; exact x :: l₁', p'.cons x, s'.cons₂ _
| swap x y l' =>
match s with
| .cons _ (.cons _ s) => exact _, .rfl, (s.cons _).cons _
| .cons _ (.cons₂ _ s) => exact x :: _, .rfl, (s.cons _).cons₂ _
| .cons₂ _ (.cons _ s) => exact y :: _, .rfl, (s.cons₂ _).cons _
| .cons₂ _ (.cons₂ _ s) => exact x :: y :: _, .swap .., (s.cons₂ _).cons₂ _
| trans _ _ IH₁ IH₂ =>
let m₁, pm, sm := IH₁ s
let r₁, pr, sr := IH₂ sm
exact r₁, pr.trans pm, sr
theorem Perm.sizeOf_eq_sizeOf [SizeOf α] {l₁ l₂ : List α} (h : l₁ ~ l₂) :
sizeOf l₁ = sizeOf l₂ := by
induction h with
| nil => rfl
| cons _ _ h_sz₁₂ => simp [h_sz₁₂]
| swap => simp [Nat.add_left_comm]
| trans _ _ h_sz₁₂ h_sz₂₃ => simp [h_sz₁₂, h_sz₂₃]
theorem Sublist.exists_perm_append {l₁ l₂ : List α} : l₁ <+ l₂ l, l₂ ~ l₁ ++ l
| Sublist.slnil => nil, .rfl
| Sublist.cons a s =>
let l, p := Sublist.exists_perm_append s
a :: l, (p.cons a).trans perm_middle.symm
| Sublist.cons₂ a s =>
let l, p := Sublist.exists_perm_append s
l, p.cons a
theorem Perm.countP_eq (p : α Bool) {l₁ l₂ : List α} (s : l₁ ~ l₂) :
countP p l₁ = countP p l₂ := by
simp only [countP_eq_length_filter]
exact (s.filter _).length_eq
theorem Perm.countP_congr {l₁ l₂ : List α} (s : l₁ ~ l₂) {p p' : α Bool}
(hp : x l₁, p x = p' x) : l₁.countP p = l₂.countP p' := by
rw [ s.countP_eq p']
clear s
induction l₁ with
| nil => rfl
| cons y s hs =>
simp only [mem_cons, forall_eq_or_imp] at hp
simp only [countP_cons, hs hp.2, hp.1]
theorem countP_eq_countP_filter_add (l : List α) (p q : α Bool) :
l.countP p = (l.filter q).countP p + (l.filter fun a => !q a).countP p :=
countP_append .. Perm.countP_eq _ (filter_append_perm _ _).symm
theorem Perm.count_eq [DecidableEq α] {l₁ l₂ : List α} (p : l₁ ~ l₂) (a) :
count a l₁ = count a l₂ := p.countP_eq _
theorem Perm.foldl_eq' {f : β α β} {l₁ l₂ : List α} (p : l₁ ~ l₂)
(comm : x l₁, y l₁, (z), f (f z x) y = f (f z y) x)
(init) : foldl f init l₁ = foldl f init l₂ := by
induction p using recOnSwap' generalizing init with
| nil => simp
| cons x _p IH =>
simp only [foldl]
apply IH; intros; apply comm <;> exact .tail _ _
| swap' x y _p IH =>
simp only [foldl]
rw [comm x (.tail _ <| .head _) y (.head _)]
apply IH; intros; apply comm <;> exact .tail _ (.tail _ _)
| trans p₁ _p₂ IH₁ IH₂ =>
refine (IH₁ comm init).trans (IH₂ ?_ _)
intros; apply comm <;> apply p₁.symm.subset <;> assumption
theorem Perm.rec_heq {β : List α Sort _} {f : a l, β l β (a :: l)} {b : β []} {l l' : List α}
(hl : l ~ l') (f_congr : {a l l' b b'}, l ~ l' HEq b b' HEq (f a l b) (f a l' b'))
(f_swap : {a a' l b}, HEq (f a (a' :: l) (f a' l b)) (f a' (a :: l) (f a l b))) :
HEq (@List.rec α β b f l) (@List.rec α β b f l') := by
induction hl with
| nil => rfl
| cons a h ih => exact f_congr h ih
| swap a a' l => exact f_swap
| trans _h₁ _h₂ ih₁ ih₂ => exact ih₁.trans ih₂
/-- Lemma used to destruct perms element by element. -/
theorem perm_inv_core {a : α} {l₁ l₂ r₁ r₂ : List α} :
l₁ ++ a :: r₁ ~ l₂ ++ a :: r₂ l₁ ++ r₁ ~ l₂ ++ r₂ := by
-- Necessary generalization for `induction`
suffices s₁ s₂ (_ : s₁ ~ s₂) {l₁ l₂ r₁ r₂},
l₁ ++ a :: r₁ = s₁ l₂ ++ a :: r₂ = s₂ l₁ ++ r₁ ~ l₂ ++ r₂ from (this _ _ · rfl rfl)
intro s₁ s₂ p
induction p using Perm.recOnSwap' with intro l₁ l₂ r₁ r₂ e₁ e₂
| nil =>
simp at e₁
| cons x p IH =>
cases l₁ <;> cases l₂ <;>
dsimp at e₁ e₂ <;> injections <;> subst_vars
· exact p
· exact p.trans perm_middle
· exact perm_middle.symm.trans p
· exact (IH rfl rfl).cons _
| swap' x y p IH =>
obtain _ | y, _ | z, l₁ := l₁
<;> obtain _ | u, _ | v, l₂ := l₂
<;> dsimp at e₁ e₂ <;> injections <;> subst_vars
<;> try exact p.cons _
· exact (p.trans perm_middle).cons u
· exact ((p.trans perm_middle).cons _).trans (swap _ _ _)
· exact (perm_middle.symm.trans p).cons y
· exact (swap _ _ _).trans ((perm_middle.symm.trans p).cons u)
· exact (IH rfl rfl).swap' _ _
| trans p₁ p₂ IH₁ IH₂ =>
subst e₁ e₂
obtain l₂, r₂, rfl := append_of_mem (a := a) (p₁.subset (by simp))
exact (IH₁ rfl rfl).trans (IH₂ rfl rfl)
theorem Perm.cons_inv {a : α} {l₁ l₂ : List α} : a :: l₁ ~ a :: l₂ l₁ ~ l₂ :=
perm_inv_core (l₁ := []) (l₂ := [])
@[simp] theorem perm_cons (a : α) {l₁ l₂ : List α} : a :: l₁ ~ a :: l₂ l₁ ~ l₂ :=
.cons_inv, .cons a
theorem perm_append_left_iff {l₁ l₂ : List α} : l, l ++ l₁ ~ l ++ l₂ l₁ ~ l₂
| [] => .rfl
| a :: l => (perm_cons a).trans (perm_append_left_iff l)
theorem perm_append_right_iff {l₁ l₂ : List α} (l) : l₁ ++ l ~ l₂ ++ l l₁ ~ l₂ := by
refine fun p => ?_, .append_right _
exact (perm_append_left_iff _).1 <| perm_append_comm.trans <| p.trans perm_append_comm
section DecidableEq
variable [DecidableEq α]
theorem Perm.erase (a : α) {l₁ l₂ : List α} (p : l₁ ~ l₂) : l₁.erase a ~ l₂.erase a :=
if h₁ : a l₁ then
have h₂ : a l₂ := p.subset h₁
.cons_inv <| (perm_cons_erase h₁).symm.trans <| p.trans (perm_cons_erase h₂)
else by
have h₂ : a l₂ := mt p.mem_iff.2 h₁
rw [erase_of_not_mem h₁, erase_of_not_mem h₂]; exact p
theorem cons_perm_iff_perm_erase {a : α} {l₁ l₂ : List α} :
a :: l₁ ~ l₂ a l₂ l₁ ~ l₂.erase a := by
refine fun h => ?_, fun m, h => (h.cons a).trans (perm_cons_erase m).symm
have : a l₂ := h.subset (mem_cons_self a l₁)
exact this, (h.trans <| perm_cons_erase this).cons_inv
theorem perm_iff_count {l₁ l₂ : List α} : l₁ ~ l₂ a, count a l₁ = count a l₂ := by
refine Perm.count_eq, fun H => ?_
induction l₁ generalizing l₂ with
| nil =>
match l₂ with
| nil => rfl
| cons b l₂ =>
specialize H b
simp at H
| cons a l₁ IH =>
have : a l₂ := count_pos_iff_mem.mp (by rw [ H]; simp)
refine ((IH fun b => ?_).cons a).trans (perm_cons_erase this).symm
specialize H b
rw [(perm_cons_erase this).count_eq] at H
by_cases h : b = a <;> simpa [h, count_cons, Nat.succ_inj'] using H
theorem isPerm_iff : {l₁ l₂ : List α}, l₁.isPerm l₂ l₁ ~ l₂
| [], [] => by simp [isPerm, isEmpty]
| [], _ :: _ => by simp [isPerm, isEmpty, Perm.nil_eq]
| a :: l₁, l₂ => by simp [isPerm, isPerm_iff, cons_perm_iff_perm_erase]
instance decidablePerm (l₁ l₂ : List α) : Decidable (l₁ ~ l₂) := decidable_of_iff _ isPerm_iff
protected theorem Perm.insert (a : α) {l₁ l₂ : List α} (p : l₁ ~ l₂) :
l₁.insert a ~ l₂.insert a := by
if h : a l₁ then
simp [h, p.subset h, p]
else
have := p.cons a
simpa [h, mt p.mem_iff.2 h] using this
theorem perm_insert_swap (x y : α) (l : List α) :
List.insert x (List.insert y l) ~ List.insert y (List.insert x l) := by
by_cases xl : x l <;> by_cases yl : y l <;> simp [xl, yl]
if xy : x = y then simp [xy] else
simp [List.insert, xl, yl, xy, Ne.symm xy]
constructor
end DecidableEq
theorem Perm.pairwise_iff {R : α α Prop} (S : {x y}, R x y R y x) :
{l₁ l₂ : List α} (_p : l₁ ~ l₂), Pairwise R l₁ Pairwise R l₂ :=
suffices {l₁ l₂}, l₁ ~ l₂ Pairwise R l₁ Pairwise R l₂
from fun p => this p, this p.symm
fun {l₁ l₂} p d => by
induction d generalizing l₂ with
| nil => rw [ p.nil_eq]; constructor
| cons h _ IH =>
have : _ l₂ := p.subset (mem_cons_self _ _)
obtain s₂, t₂, rfl := append_of_mem this
have p' := (p.trans perm_middle).cons_inv
refine (pairwise_middle S).2 (pairwise_cons.2 fun b m => ?_, IH p')
exact h _ (p'.symm.subset m)
theorem Pairwise.perm {R : α α Prop} {l l' : List α} (hR : l.Pairwise R) (hl : l ~ l')
(hsymm : {x y}, R x y R y x) : l'.Pairwise R := (hl.pairwise_iff hsymm).mp hR
theorem Perm.pairwise {R : α α Prop} {l l' : List α} (hl : l ~ l') (hR : l.Pairwise R)
(hsymm : {x y}, R x y R y x) : l'.Pairwise R := hR.perm hl hsymm
/--
If two lists are sorted by an antisymmetric relation, and permutations of each other,
they must be equal.
-/
theorem Perm.eq_of_sorted : {l₁ l₂ : List α}
(_ : a b, a l₁ b l₂ le a b le b a a = b)
(_ : l₁.Pairwise le) (_ : l₂.Pairwise le) (_ : l₁ ~ l₂), l₁ = l₂
| [], [], _, _, _, _ => rfl
| [], b :: l₂, _, _, _, h => by simp_all
| a :: l₁, [], _, _, _, h => by simp_all
| a :: l₁, b :: l₂, w, h₁, h₂, h => by
have am : a b :: l₂ := h.subset (mem_cons_self _ _)
have bm : b a :: l₁ := h.symm.subset (mem_cons_self _ _)
have ab : a = b := by
simp only [mem_cons] at am
rcases am with rfl | am
· rfl
· simp only [mem_cons] at bm
rcases bm with rfl | bm
· rfl
· exact w _ _ (mem_cons_self _ _) (mem_cons_self _ _)
(rel_of_pairwise_cons h₁ bm) (rel_of_pairwise_cons h₂ am)
subst ab
simp only [perm_cons] at h
have := Perm.eq_of_sorted
(fun x y hx hy => w x y (mem_cons_of_mem a hx) (mem_cons_of_mem a hy))
h₁.tail h₂.tail h
simp_all
theorem Nodup.perm {l l' : List α} (hR : l.Nodup) (hl : l ~ l') : l'.Nodup :=
Pairwise.perm hR hl (by intro x y h h'; simp_all)
theorem Perm.nodup {l l' : List α} (hl : l ~ l') (hR : l.Nodup) : l'.Nodup := hR.perm hl
theorem Perm.nodup_iff {l₁ l₂ : List α} : l₁ ~ l₂ (Nodup l₁ Nodup l₂) :=
Perm.pairwise_iff <| @Ne.symm α
theorem Perm.join {l₁ l₂ : List (List α)} (h : l₁ ~ l₂) : l₁.join ~ l₂.join := by
induction h with
| nil => rfl
| cons _ _ ih => simp only [join_cons, perm_append_left_iff, ih]
| swap => simp only [join_cons, append_assoc, perm_append_right_iff]; exact perm_append_comm ..
| trans _ _ ih₁ ih₂ => exact trans ih₁ ih₂
theorem Perm.bind_right {l₁ l₂ : List α} (f : α List β) (p : l₁ ~ l₂) : l₁.bind f ~ l₂.bind f :=
(p.map _).join
theorem Perm.eraseP (f : α Bool) {l₁ l₂ : List α}
(H : Pairwise (fun a b => f a f b False) l₁) (p : l₁ ~ l₂) : eraseP f l₁ ~ eraseP f l₂ := by
induction p with
| nil => simp
| cons a p IH =>
if h : f a then simp [h, p]
else simp [h]; exact IH (pairwise_cons.1 H).2
| swap a b l =>
by_cases h₁ : f a <;> by_cases h₂ : f b <;> simp [h₁, h₂]
· cases (pairwise_cons.1 H).1 _ (mem_cons.2 (Or.inl rfl)) h₂ h₁
· apply swap
| trans p₁ _ IH₁ IH₂ =>
refine (IH₁ H).trans (IH₂ ((p₁.pairwise_iff ?_).1 H))
exact fun h h₁ h₂ => h h₂ h₁
end List

View File

@@ -0,0 +1,57 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.Pairwise
/-!
# Lemmas about `List.range` and `List.enum`
Most of the results are deferred to `Data.Init.List.Nat.Range`, where more results about
natural arithmetic are available.
-/
namespace List
open Nat
/-! ## Ranges and enumeration -/
/-! ### enumFrom -/
@[simp]
theorem enumFrom_eq_nil {n : Nat} {l : List α} : List.enumFrom n l = [] l = [] := by
cases l <;> simp
@[simp] theorem enumFrom_length : {n} {l : List α}, (enumFrom n l).length = l.length
| _, [] => rfl
| _, _ :: _ => congrArg Nat.succ enumFrom_length
@[simp]
theorem getElem?_enumFrom :
n (l : List α) m, (enumFrom n l)[m]? = l[m]?.map fun a => (n + m, a)
| n, [], m => rfl
| n, a :: l, 0 => by simp
| n, a :: l, m + 1 => by
simp only [enumFrom_cons, getElem?_cons_succ]
exact (getElem?_enumFrom (n + 1) l m).trans <| by rw [Nat.add_right_comm]; rfl
@[simp]
theorem getElem_enumFrom (l : List α) (n) (i : Nat) (h : i < (l.enumFrom n).length) :
(l.enumFrom n)[i] = (n + i, l[i]'(by simpa [enumFrom_length] using h)) := by
simp only [enumFrom_length] at h
rw [getElem_eq_getElem?]
simp only [getElem?_enumFrom, getElem?_eq_getElem h]
simp
theorem map_fst_add_enumFrom_eq_enumFrom (l : List α) (n k : Nat) :
map (Prod.map (· + n) id) (enumFrom k l) = enumFrom (n + k) l :=
ext_getElem? fun i by simp [(· ·), Nat.add_comm, Nat.add_left_comm]; rfl
theorem map_fst_add_enum_eq_enumFrom (l : List α) (n : Nat) :
map (Prod.map (· + n) id) (enum l) = enumFrom n l :=
map_fst_add_enumFrom_eq_enumFrom l _ _
end List

View File

@@ -0,0 +1,9 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Sort.Basic
import Init.Data.List.Sort.Impl
import Init.Data.List.Sort.Lemmas

View File

@@ -0,0 +1,81 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Impl
/-!
# Definition of `merge` and `mergeSort`.
These definitions are intended for verification purposes,
and are replaced at runtime by efficient versions in `Init.Data.List.Sort.Impl`.
-/
namespace List
/--
`O(min |l| |r|)`. Merge two lists using `le` as a switch.
This version is not tail-recursive,
but it is replaced at runtime by `mergeTR` using a `@[csimp]` lemma.
-/
def merge (le : α α Bool) : List α List α List α
| [], ys => ys
| xs, [] => xs
| x :: xs, y :: ys =>
if le x y then
x :: merge le xs (y :: ys)
else
y :: merge le (x :: xs) ys
@[simp] theorem merge_nil_left (ys : List α) : merge le [] ys = ys := by simp [merge]
@[simp] theorem merge_nil_right (xs : List α) : merge le xs [] = xs := by
induction xs with
| nil => simp [merge]
| cons x xs ih => simp [merge, ih]
/--
Split a list in two equal parts. If the length is odd, the first part will be one element longer.
-/
def splitInTwo (l : { l : List α // l.length = n }) :
{ l : List α // l.length = (n+1)/2 } × { l : List α // l.length = n/2 } :=
let r := splitAt ((n+1)/2) l.1
(r.1, by simp [r, splitAt_eq, l.2]; omega, r.2, by simp [r, splitAt_eq, l.2]; omega)
/--
Simplified implementation of stable merge sort.
This function is designed for reasoning about the algorithm, and is not efficient.
(It particular it uses the non tail-recursive `merge` function,
and so can not be run on large lists, but also makes unnecessary traversals of lists.)
It is replaced at runtime in the compiler by `mergeSortTR₂` using a `@[csimp]` lemma.
Because we want the sort to be stable,
it is essential that we split the list in two contiguous sublists.
-/
def mergeSort (le : α α Bool) : List α List α
| [] => []
| [a] => [a]
| a :: b :: xs =>
let lr := splitInTwo a :: b :: xs, rfl
have := by simpa using lr.2.2
have := by simpa using lr.1.2
merge le (mergeSort le lr.1) (mergeSort le lr.2)
termination_by l => l.length
/--
Given an ordering relation `le : αα → Bool`,
construct the reverse lexicographic ordering on `Nat × α`.
which first compares the second components using `le`,
but if these are equivalent (in the sense `le a.2 b.2 && le b.2 a.2`)
then compares the first components using `≤`.
This function is only used in stating the stability properties of `mergeSort`.
-/
def enumLE (le : α α Bool) (a b : Nat × α) : Bool :=
if le a.2 b.2 then if le b.2 a.2 then a.1 b.1 else true else false
end List

View File

@@ -0,0 +1,237 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Sort.Lemmas
/-!
# Replacing `merge` and `mergeSort` at runtime with tail-recursive and faster versions.
We replace `merge` with `mergeTR` using a `@[csimp]` lemma.
We replace `mergeSort` in two steps:
* first with `mergeSortTR`, which while not tail-recursive itself (it can't be),
uses `mergeTR` internally.
* second with `mergeSortTR₂`, which achieves an ~20% speed-up over `mergeSortTR`
by avoiding some unnecessary list reversals.
There is no public API in this file; it solely exists to implement the `@[csimp]` lemmas
affecting runtime behaviour.
## Future work
The current runtime implementation could be further improved in a number of ways, e.g.:
* only walking the list once during splitting,
* using insertion sort for small chunks rather than splitting all the way down to singletons,
* identifying already sorted or reverse sorted chunks and skipping them.
Because the theory developed for `mergeSort` is independent of the runtime implementation,
as long as such improvements are carefully validated by benchmarking,
they can be done without changing the theory, as long as a `@[csimp]` lemma is provided.
-/
open List
namespace List.MergeSort.Internal
/--
`O(min |l| |r|)`. Merge two lists using `le` as a switch.
-/
def mergeTR (le : α α Bool) (l₁ l₂ : List α) : List α :=
go l₁ l₂ []
where go : List α List α List α List α
| [], l₂, acc => reverseAux acc l₂
| l₁, [], acc => reverseAux acc l₁
| x :: xs, y :: ys, acc =>
if le x y then
go xs (y :: ys) (x :: acc)
else
go (x :: xs) ys (y :: acc)
theorem mergeTR_go_eq : mergeTR.go le l₁ l₂ acc = acc.reverse ++ merge le l₁ l₂ := by
induction l₁ generalizing l₂ acc with
| nil => simp [mergeTR.go, merge, reverseAux_eq]
| cons x l₁ ih₁ =>
induction l₂ generalizing acc with
| nil => simp [mergeTR.go, merge, reverseAux_eq]
| cons y l₂ ih₂ =>
simp [mergeTR.go, merge]
split <;> simp [ih₁, ih₂]
@[csimp] theorem merge_eq_mergeTR : @merge = @mergeTR := by
funext
simp [mergeTR, mergeTR_go_eq]
/--
Variant of `splitAt`, that does not reverse the first list, i.e
`splitRevAt n l = ((l.take n).reverse, l.drop n)`.
This exists solely as an optimization for `mergeSortTR` and `mergeSortTR₂`,
and should not be used elsewhere.
-/
def splitRevAt (n : Nat) (l : List α) : List α × List α := go l n [] where
/-- Auxiliary for `splitAtRev`: `splitAtRev.go xs n acc = ((take n xs).reverse ++ acc, drop n xs)`. -/
go : List α Nat List α List α × List α
| x :: xs, n+1, acc => go xs n (x :: acc)
| xs, _, acc => (acc, xs)
theorem splitRevAt_go (xs : List α) (n : Nat) (acc : List α) :
splitRevAt.go xs n acc = ((take n xs).reverse ++ acc, drop n xs) := by
induction xs generalizing n acc with
| nil => simp [splitRevAt.go]
| cons x xs ih =>
cases n with
| zero => simp [splitRevAt.go]
| succ n =>
rw [splitRevAt.go, ih n (x :: acc), take_succ_cons, reverse_cons, drop_succ_cons,
append_assoc, singleton_append]
theorem splitRevAt_eq (n : Nat) (l : List α) : splitRevAt n l = ((l.take n).reverse, l.drop n) := by
rw [splitRevAt, splitRevAt_go, append_nil]
/--
An intermediate speed-up for `mergeSort`.
This version uses the tail-recurive `mergeTR` function as a subroutine.
This is not the final version we use at runtime, as `mergeSortTR₂` is faster.
This definition is useful as an intermediate step in proving the `@[csimp]` lemma for `mergeSortTR₂`.
-/
def mergeSortTR (le : α α Bool) (l : List α) : List α :=
run l, rfl
where run : {n : Nat} { l : List α // l.length = n } List α
| 0, [], _ => []
| 1, [a], _ => [a]
| n+2, xs =>
let (l, r) := splitInTwo xs
mergeTR le (run l) (run r)
/--
Split a list in two equal parts, reversing the first part.
If the length is odd, the first part will be one element longer.
-/
def splitRevInTwo (l : { l : List α // l.length = n }) :
{ l : List α // l.length = (n+1)/2 } × { l : List α // l.length = n/2 } :=
let r := splitRevAt ((n+1)/2) l.1
(r.1, by simp [r, splitRevAt_eq, l.2]; omega, r.2, by simp [r, splitRevAt_eq, l.2]; omega)
/--
Split a list in two equal parts, reversing the first part.
If the length is odd, the second part will be one element longer.
-/
def splitRevInTwo' (l : { l : List α // l.length = n }) :
{ l : List α // l.length = n/2 } × { l : List α // l.length = (n+1)/2 } :=
let r := splitRevAt (n/2) l.1
(r.1, by simp [r, splitRevAt_eq, l.2]; omega, r.2, by simp [r, splitRevAt_eq, l.2]; omega)
/--
Faster version of `mergeSortTR`, which avoids unnecessary list reversals.
-/
-- Per the benchmark in `tests/bench/mergeSort/`
-- (which averages over 4 use cases: already sorted lists, reverse sorted lists, almost sorted lists, and random lists),
-- for lists of length 10^6, `mergeSortTR₂` is about 20% faster than `mergeSortTR`.
def mergeSortTR₂ (le : α α Bool) (l : List α) : List α :=
run l, rfl
where
run : {n : Nat} { l : List α // l.length = n } List α
| 0, [], _ => []
| 1, [a], _ => [a]
| n+2, xs =>
let (l, r) := splitRevInTwo xs
mergeTR le (run' l) (run r)
run' : {n : Nat} { l : List α // l.length = n } List α
| 0, [], _ => []
| 1, [a], _ => [a]
| n+2, xs =>
let (l, r) := splitRevInTwo' xs
mergeTR le (run' r) (run l)
theorem splitRevInTwo'_fst (l : { l : List α // l.length = n }) :
(splitRevInTwo' l).1 = (splitInTwo l.1.reverse, by simpa using l.2).2.1, by have := l.2; simp; omega := by
simp only [splitRevInTwo', splitRevAt_eq, reverse_take, splitInTwo_snd]
congr
have := l.2
omega
theorem splitRevInTwo'_snd (l : { l : List α // l.length = n }) :
(splitRevInTwo' l).2 = (splitInTwo l.1.reverse, by simpa using l.2).1.1.reverse, by have := l.2; simp; omega := by
simp only [splitRevInTwo', splitRevAt_eq, reverse_take, splitInTwo_fst, reverse_reverse]
congr 2
have := l.2
simp
omega
theorem splitRevInTwo_fst (l : { l : List α // l.length = n }) :
(splitRevInTwo l).1 = (splitInTwo l).1.1.reverse, by have := l.2; simp; omega := by
simp only [splitRevInTwo, splitRevAt_eq, reverse_take, splitInTwo_fst]
theorem splitRevInTwo_snd (l : { l : List α // l.length = n }) :
(splitRevInTwo l).2 = (splitInTwo l).2.1, by have := l.2; simp; omega := by
simp only [splitRevInTwo, splitRevAt_eq, reverse_take, splitInTwo_snd]
theorem mergeSortTR_run_eq_mergeSort : {n : Nat} (l : { l : List α // l.length = n }) mergeSortTR.run le l = mergeSort le l.1
| 0, [], _
| 1, [a], _ => by simp [mergeSortTR.run, mergeSort]
| n+2, a :: b :: l, h => by
cases h
simp only [mergeSortTR.run, mergeSortTR.run, mergeSort]
rw [merge_eq_mergeTR]
rw [mergeSortTR_run_eq_mergeSort, mergeSortTR_run_eq_mergeSort]
-- We don't make this a `@[csimp]` lemma because `mergeSort_eq_mergeSortTR₂` is faster.
theorem mergeSort_eq_mergeSortTR : @mergeSort = @mergeSortTR := by
funext
rw [mergeSortTR, mergeSortTR_run_eq_mergeSort]
-- This mutual block is unfortunately quite slow to elaborate.
set_option maxHeartbeats 400000 in
mutual
theorem mergeSortTR₂_run_eq_mergeSort : {n : Nat} (l : { l : List α // l.length = n }) mergeSortTR₂.run le l = mergeSort le l.1
| 0, [], _
| 1, [a], _ => by simp [mergeSortTR₂.run, mergeSort]
| n+2, a :: b :: l, h => by
cases h
simp only [mergeSortTR₂.run, mergeSort]
rw [splitRevInTwo_fst, splitRevInTwo_snd]
rw [mergeSortTR₂_run_eq_mergeSort, mergeSortTR₂_run'_eq_mergeSort]
rw [merge_eq_mergeTR]
rw [reverse_reverse]
termination_by n => n
theorem mergeSortTR₂_run'_eq_mergeSort : {n : Nat} (l : { l : List α // l.length = n }) (w : l' = l.1.reverse) mergeSortTR₂.run' le l = mergeSort le l'
| 0, [], _, w
| 1, [a], _, w => by simp_all [mergeSortTR₂.run', mergeSort]
| n+2, a :: b :: l, h, w => by
cases h
simp only [mergeSortTR₂.run', mergeSort]
rw [splitRevInTwo'_fst, splitRevInTwo'_snd]
rw [mergeSortTR₂_run_eq_mergeSort, mergeSortTR₂_run'_eq_mergeSort _ rfl]
rw [ merge_eq_mergeTR]
have w' := congrArg length w
simp at w'
cases l' with
| nil => simp at w'
| cons x l' =>
cases l' with
| nil => simp at w'; omega
| cons y l' =>
rw [mergeSort]
congr 2
· dsimp at w
simp only [w]
simp only [splitInTwo_fst, splitInTwo_snd, reverse_take, take_reverse]
congr 1
rw [w, length_reverse]
simp
· dsimp at w
simp only [w]
simp only [reverse_cons, append_assoc, singleton_append, splitInTwo_snd, length_cons]
congr 1
simp at w'
omega
termination_by n => n
end
@[csimp] theorem mergeSort_eq_mergeSortTR₂ : @mergeSort = @mergeSortTR₂ := by
funext
rw [mergeSortTR₂, mergeSortTR₂_run_eq_mergeSort]
end List.MergeSort.Internal

View File

@@ -0,0 +1,399 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.List.Perm
import Init.Data.List.Sort.Basic
import Init.Data.Bool
/-!
# Basic properties of `mergeSort`.
* `mergeSort_sorted`: `mergeSort` produces a sorted list.
* `mergeSort_perm`: `mergeSort` is a permutation of the input list.
* `mergeSort_of_sorted`: `mergeSort` does not change a sorted list.
* `mergeSort_cons`: proves `mergeSort le (x :: xs) = l₁ ++ x :: l₂` for some `l₁, l₂`
so that `mergeSort le xs = l₁ ++ l₂`, and no `a ∈ l₁` satisfies `le a x`.
* `mergeSort_stable`: if `c` is a sorted sublist of `l`, then `c` is still a sublist of `mergeSort le l`.
-/
namespace List
-- We enable this instance locally so we can write `Sorted le` instead of `Sorted (le · ·)` everywhere.
attribute [local instance] boolRelToRel
variable {le : α α Bool}
/-! ### splitInTwo -/
@[simp] theorem splitInTwo_fst (l : { l : List α // l.length = n }) : (splitInTwo l).1 = l.1.take ((n+1)/2), by simp [splitInTwo, splitAt_eq, l.2]; omega := by
simp [splitInTwo, splitAt_eq]
@[simp] theorem splitInTwo_snd (l : { l : List α // l.length = n }) : (splitInTwo l).2 = l.1.drop ((n+1)/2), by simp [splitInTwo, splitAt_eq, l.2]; omega := by
simp [splitInTwo, splitAt_eq]
theorem splitInTwo_fst_append_splitInTwo_snd (l : { l : List α // l.length = n }) : (splitInTwo l).1.1 ++ (splitInTwo l).2.1 = l.1 := by
simp
theorem splitInTwo_cons_cons_enumFrom_fst (i : Nat) (l : List α) :
(splitInTwo (i, a) :: (i+1, b) :: l.enumFrom (i+2), rfl).1.1 =
(splitInTwo a :: b :: l, rfl).1.1.enumFrom i := by
simp only [length_cons, splitInTwo_fst, enumFrom_length]
ext1 j
rw [getElem?_take, getElem?_enumFrom, getElem?_take]
split
· rw [getElem?_cons, getElem?_cons, getElem?_cons, getElem?_cons]
split
· simp; omega
· split
· simp; omega
· simp only [getElem?_enumFrom]
congr
ext <;> simp; omega
· simp
theorem splitInTwo_cons_cons_enumFrom_snd (i : Nat) (l : List α) :
(splitInTwo (i, a) :: (i+1, b) :: l.enumFrom (i+2), rfl).2.1 =
(splitInTwo a :: b :: l, rfl).2.1.enumFrom (i+(l.length+3)/2) := by
simp only [length_cons, splitInTwo_snd, enumFrom_length]
ext1 j
rw [getElem?_drop, getElem?_enumFrom, getElem?_drop]
rw [getElem?_cons, getElem?_cons, getElem?_cons, getElem?_cons]
split
· simp; omega
· split
· simp; omega
· simp only [getElem?_enumFrom]
congr
ext <;> simp; omega
theorem splitInTwo_fst_sorted (l : { l : List α // l.length = n }) (h : Pairwise le l.1) : Pairwise le (splitInTwo l).1.1 := by
rw [splitInTwo_fst]
exact h.take
theorem splitInTwo_snd_sorted (l : { l : List α // l.length = n }) (h : Pairwise le l.1) : Pairwise le (splitInTwo l).2.1 := by
rw [splitInTwo_snd]
exact h.drop
theorem splitInTwo_fst_le_splitInTwo_snd {l : { l : List α // l.length = n }} (h : Pairwise le l.1) :
a b, a (splitInTwo l).1.1 b (splitInTwo l).2.1 le a b := by
rw [splitInTwo_fst, splitInTwo_snd]
intro a b ma mb
exact h.rel_of_mem_take_of_mem_drop ma mb
/-! ### enumLE -/
theorem enumLE_trans (trans : a b c, le a b le b c le a c)
(a b c : Nat × α) : enumLE le a b enumLE le b c enumLE le a c := by
simp only [enumLE]
split <;> split <;> split <;> rename_i ab₂ ba₂ bc₂
· simp_all
intro ab₁
intro h
refine trans _ _ _ ab₂ bc₂, ?_
rcases h with (cd₂ | bc₁)
· exact Or.inl (Decidable.byContradiction
(fun ca₂ => by simp_all [trans _ _ _ (by simpa using ca₂) ab₂]))
· exact Or.inr (Nat.le_trans ab₁ bc₁)
· simp_all
· simp_all
intro h
refine trans _ _ _ ab₂ bc₂, ?_
left
rcases h with (cb₂ | _)
· exact (Decidable.byContradiction
(fun ca₂ => by simp_all [trans _ _ _ (by simpa using ca₂) ab₂]))
· exact (Decidable.byContradiction
(fun ca₂ => by simp_all [trans _ _ _ bc₂ (by simpa using ca₂)]))
· simp_all
· simp_all
· simp_all
· simp_all
· simp_all
theorem enumLE_total (total : a b, !le a b le b a)
(a b : Nat × α) : !enumLE le a b enumLE le b a := by
simp only [enumLE]
split <;> split
· simpa using Nat.le_of_lt
· simp
· simp
· simp_all [total a.2 b.2]
/-! ### merge -/
theorem merge_stable : (xs ys) (_ : x y, x xs y ys x.1 y.1),
(merge (enumLE le) xs ys).map (·.2) = merge le (xs.map (·.2)) (ys.map (·.2))
| [], ys, _ => by simp [merge]
| xs, [], _ => by simp [merge]
| (i, x) :: xs, (j, y) :: ys, h => by
simp only [merge, enumLE, map_cons]
split <;> rename_i w
· rw [if_pos (by simp [h _ _ (mem_cons_self ..) (mem_cons_self ..)])]
simp only [map_cons, cons.injEq, true_and]
rw [merge_stable, map_cons]
exact fun x' y' mx my => h x' y' (mem_cons_of_mem (i, x) mx) my
· simp only [reduceIte, map_cons, cons.injEq, true_and]
rw [merge_stable, map_cons]
exact fun x' y' mx my => h x' y' mx (mem_cons_of_mem (j, y) my)
/--
The elements of `merge le xs ys` are exactly the elements of `xs` and `ys`.
-/
-- We subsequently prove that `mergeSort_perm : merge le xs ys ~ xs ++ ys`.
theorem mem_merge {a : α} {xs ys : List α} : a merge le xs ys a xs a ys := by
induction xs generalizing ys with
| nil => simp [merge]
| cons x xs ih =>
induction ys with
| nil => simp [merge]
| cons y ys ih =>
simp only [merge]
split <;> rename_i h
· simp_all [or_assoc]
· simp only [mem_cons, or_assoc, Bool.not_eq_true, ih, or_assoc]
apply or_congr_left
simp only [or_comm (a := a = y), or_assoc]
/--
If the ordering relation `le` is transitive and total (i.e. `le a b le b a` for all `a, b`)
then the `merge` of two sorted lists is sorted.
-/
theorem merge_sorted
(trans : (a b c : α), le a b le b c le a c)
(total : (a b : α), !le a b le b a)
(l₁ l₂ : List α) (h₁ : l₁.Pairwise le) (h₂ : l₂.Pairwise le) : (merge le l₁ l₂).Pairwise le := by
induction l₁ generalizing l₂ with
| nil => simpa only [merge]
| cons x l₁ ih₁ =>
induction l₂ with
| nil => simpa only [merge]
| cons y l₂ ih₂ =>
simp only [merge]
split <;> rename_i h
· apply Pairwise.cons
· intro z m
rw [mem_merge, mem_cons] at m
rcases m with (m|rfl|m)
· exact rel_of_pairwise_cons h₁ m
· exact h
· exact trans _ _ _ h (rel_of_pairwise_cons h₂ m)
· exact ih₁ _ h₁.tail h₂
· apply Pairwise.cons
· intro z m
rw [mem_merge, mem_cons] at m
rcases m with (rfl|m|m)
· exact total _ _ (by simpa using h)
· exact trans _ _ _ (total _ _ (by simpa using h)) (rel_of_pairwise_cons h₁ m)
· exact rel_of_pairwise_cons h₂ m
· exact ih₂ h₂.tail
theorem merge_of_le : {xs ys : List α} (_ : a b, a xs b ys le a b),
merge le xs ys = xs ++ ys
| [], ys, _
| xs, [], _ => by simp [merge]
| x :: xs, y :: ys, h => by
simp only [merge, cons_append]
rw [if_pos, merge_of_le]
· intro a b ma mb
exact h a b (mem_cons_of_mem _ ma) mb
· exact h x y (mem_cons_self _ _) (mem_cons_self _ _)
variable (le) in
theorem merge_perm_append : {xs ys : List α}, merge le xs ys ~ xs ++ ys
| [], ys => by simp [merge]
| xs, [] => by simp [merge]
| x :: xs, y :: ys => by
simp only [merge]
split
· exact merge_perm_append.cons x
· exact (merge_perm_append.cons y).trans
((Perm.swap x y _).trans (perm_middle.symm.cons x))
/-! ### mergeSort -/
variable (le) in
theorem mergeSort_perm : (l : List α), mergeSort le l ~ l
| [] => by simp [mergeSort]
| [a] => by simp [mergeSort]
| a :: b :: xs => by
simp only [mergeSort]
have : (splitInTwo a :: b :: xs, rfl).1.1.length < xs.length + 1 + 1 := by simp [splitInTwo_fst]; omega
have : (splitInTwo a :: b :: xs, rfl).2.1.length < xs.length + 1 + 1 := by simp [splitInTwo_snd]; omega
exact (merge_perm_append le).trans
(((mergeSort_perm _).append (mergeSort_perm _)).trans
(Perm.of_eq (splitInTwo_fst_append_splitInTwo_snd _)))
termination_by l => l.length
@[simp] theorem mem_mergeSort {a : α} {l : List α} : a mergeSort le l a l :=
(mergeSort_perm le l).mem_iff
/--
The result of `mergeSort` is sorted,
as long as the comparison function is transitive (`le a b → le b c → le a c`)
and total in the sense that `le a b le b a`.
The comparison function need not be irreflexive, i.e. `le a b` and `le b a` is allowed even when `a ≠ b`.
-/
theorem mergeSort_sorted
(trans : (a b c : α), le a b le b c le a c)
(total : (a b : α), !le a b le b a) :
(l : List α) (mergeSort le l).Pairwise le
| [] => by simp [mergeSort]
| [a] => by simp [mergeSort]
| a :: b :: xs => by
have : (splitInTwo a :: b :: xs, rfl).1.1.length < xs.length + 1 + 1 := by simp [splitInTwo_fst]; omega
have : (splitInTwo a :: b :: xs, rfl).2.1.length < xs.length + 1 + 1 := by simp [splitInTwo_snd]; omega
rw [mergeSort]
apply merge_sorted @trans @total
apply mergeSort_sorted trans total
apply mergeSort_sorted trans total
termination_by l => l.length
/--
If the input list is already sorted, then `mergeSort` does not change the list.
-/
theorem mergeSort_of_sorted : {l : List α} (_ : Pairwise le l), mergeSort le l = l
| [], _ => by simp [mergeSort]
| [a], _ => by simp [mergeSort]
| a :: b :: xs, h => by
have : (splitInTwo a :: b :: xs, rfl).1.1.length < xs.length + 1 + 1 := by simp [splitInTwo_fst]; omega
have : (splitInTwo a :: b :: xs, rfl).2.1.length < xs.length + 1 + 1 := by simp [splitInTwo_snd]; omega
rw [mergeSort]
rw [mergeSort_of_sorted (splitInTwo_fst_sorted a :: b :: xs, rfl h)]
rw [mergeSort_of_sorted (splitInTwo_snd_sorted a :: b :: xs, rfl h)]
rw [merge_of_le (splitInTwo_fst_le_splitInTwo_snd h)]
rw [splitInTwo_fst_append_splitInTwo_snd]
termination_by l => l.length
/--
This merge sort algorithm is stable,
in the sense that breaking ties in the ordering function using the position in the list
has no effect on the output.
That is, elements which are equal with respect to the ordering function will remain
in the same order in the output list as they were in the input list.
See also:
* `mergeSort_stable`: if `c <+ l` and `c.Pairwise le`, then `c <+ mergeSort le l`.
* `mergeSort_stable_pair`: if `[a, b] <+ l` and `le a b`, then `[a, b] <+ mergeSort le l`)
-/
theorem mergeSort_enum {l : List α} :
(mergeSort (enumLE le) (l.enum)).map (·.2) = mergeSort le l :=
go 0 l
where go : (i : Nat) (l : List α),
(mergeSort (enumLE le) (l.enumFrom i)).map (·.2) = mergeSort le l
| _, []
| _, [a] => by simp [mergeSort]
| _, a :: b :: xs => by
have : (splitInTwo a :: b :: xs, rfl).1.1.length < xs.length + 1 + 1 := by simp [splitInTwo_fst]; omega
have : (splitInTwo a :: b :: xs, rfl).2.1.length < xs.length + 1 + 1 := by simp [splitInTwo_snd]; omega
simp only [mergeSort, enumFrom]
rw [splitInTwo_cons_cons_enumFrom_fst]
rw [splitInTwo_cons_cons_enumFrom_snd]
rw [merge_stable]
· rw [go, go]
· simp only [mem_mergeSort, Prod.forall]
intros j x k y mx my
have := mem_enumFrom mx
have := mem_enumFrom my
simp_all
omega
termination_by _ l => l.length
theorem mergeSort_cons {le : α α Bool}
(trans : (a b c : α), le a b le b c le a c)
(total : (a b : α), !le a b le b a)
(a : α) (l : List α) :
l₁ l₂, mergeSort le (a :: l) = l₁ ++ a :: l₂ mergeSort le l = l₁ ++ l₂
b, b l₁ !le a b := by
rw [ mergeSort_enum]
rw [enum_cons]
have nd : Nodup ((a :: l).enum.map (·.1)) := by rw [enum_map_fst]; exact nodup_range _
have m₁ : (0, a) mergeSort (enumLE le) ((a :: l).enum) :=
mem_mergeSort.mpr (mem_cons_self _ _)
obtain l₁, l₂, h := append_of_mem m₁
have s := mergeSort_sorted (enumLE_trans trans) (enumLE_total total) ((a :: l).enum)
rw [h] at s
have p := mergeSort_perm (enumLE le) ((a :: l).enum)
rw [h] at p
refine l₁.map (·.2), l₂.map (·.2), ?_, ?_, ?_
· simpa using congrArg (·.map (·.2)) h
· rw [ mergeSort_enum.go 1, map_append]
congr 1
have q : mergeSort (enumLE le) (enumFrom 1 l) ~ l₁ ++ l₂ :=
(mergeSort_perm (enumLE le) (enumFrom 1 l)).trans
(p.symm.trans perm_middle).cons_inv
apply Perm.eq_of_sorted (le := enumLE le)
· rintro i, a j, b ha hb
simp only [mem_mergeSort] at ha
simp only [ q.mem_iff, mem_mergeSort] at hb
simp only [enumLE]
simp only [Bool.if_false_right, Bool.and_eq_true, Prod.mk.injEq, and_imp]
intro ab h ba h'
simp only [Bool.decide_eq_true] at ba
replace h : i j := by simpa [ab, ba] using h
replace h' : j i := by simpa [ab, ba] using h'
cases Nat.le_antisymm h h'
constructor
· rfl
· have := mem_enumFrom ha
have := mem_enumFrom hb
simp_all
· exact mergeSort_sorted (enumLE_trans trans) (enumLE_total total) ..
· exact s.sublist ((sublist_cons_self (0, a) l₂).append_left l₁)
· exact q
· intro b m
simp only [mem_map, Prod.exists, exists_eq_right] at m
obtain j, m := m
replace p := p.map (·.1)
have nd' := nd.perm p.symm
rw [map_append] at nd'
have j0 := nd'.rel_of_mem_append
(mem_map_of_mem (·.1) m) (mem_map_of_mem _ (mem_cons_self _ _))
simp only [ne_eq] at j0
have r := s.rel_of_mem_append m (mem_cons_self _ _)
simp_all [enumLE]
/--
Another statement of stability of merge sort.
If `c` is a sorted sublist of `l`,
then `c` is still a sublist of `mergeSort le l`.
-/
theorem mergeSort_stable
(trans : (a b c : α), le a b le b c le a c)
(total : (a b : α), !le a b le b a) :
{c : List α} (_ : c.Pairwise le) (_ : c <+ l),
c <+ mergeSort le l
| _, _, .slnil => nil_sublist _
| c, hc, @Sublist.cons _ _ l a h => by
obtain l₁, l₂, h₁, h₂, - := mergeSort_cons trans total a l
rw [h₁]
have h' := mergeSort_stable trans total hc h
rw [h₂] at h'
exact h'.middle a
| _, _, @Sublist.cons₂ _ l₁ l₂ a h => by
rename_i hc
obtain l₃, l₄, h₁, h₂, h₃ := mergeSort_cons trans total a l₂
rw [h₁]
have h' := mergeSort_stable trans total hc.tail h
rw [h₂] at h'
simp only [Bool.not_eq_true', tail_cons] at h₃ h'
exact
sublist_append_of_sublist_right (Sublist.cons₂ a
((fun w => Sublist.of_sublist_append_right w h') fun b m₁ m₃ =>
(Bool.eq_not_self true).mp ((rel_of_pairwise_cons hc m₁).symm.trans (h₃ b m₃))))
/--
Another statement of stability of merge sort.
If a pair `[a, b]` is a sublist of `l` and `le a b`,
then `[a, b]` is still a sublist of `mergeSort le l`.
-/
theorem mergeSort_stable_pair
(trans : (a b c : α), le a b le b c le a c)
(total : (a b : α), !le a b le b a)
(hab : le a b) (h : [a, b] <+ l) : [a, b] <+ mergeSort le l :=
mergeSort_stable trans total (pairwise_pair.mpr hab) h

File diff suppressed because it is too large Load Diff

View File

@@ -5,500 +5,459 @@ Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, M
-/
prelude
import Init.Data.List.Lemmas
import Init.Data.Nat.Lemmas
/-!
# Further lemmas about `List.take`, `List.drop`, `List.zip` and `List.zipWith`.
These are in a separate file from most of the list lemmas
as they required importing more lemmas about natural numbers, and use `omega`.
# Lemmas about `List.zip`, `List.zipWith`, `List.zipWithAll`, and `List.unzip`.
-/
namespace List
open Nat
/-! ### take -/
/-! ### take and drop
@[simp] theorem length_take : (i : Nat) (l : List α), length (take i l) = min i (length l)
| 0, l => by simp [Nat.zero_min]
| succ n, [] => by simp [Nat.min_zero]
| succ n, _ :: l => by simp [Nat.succ_min_succ, length_take]
Further results on `List.take` and `List.drop`, which rely on stronger automation in `Nat`,
are given in `Init.Data.List.TakeDrop`.
-/
theorem length_take_le (n) (l : List α) : length (take n l) n := by simp [Nat.min_le_left]
theorem length_take_le' (n) (l : List α) : length (take n l) l.length :=
by simp [Nat.min_le_right]
theorem length_take_of_le (h : n length l) : length (take n l) = n := by simp [Nat.min_eq_left h]
theorem take_take : (n m) (l : List α), take n (take m l) = take (min n m) l
| n, 0, l => by rw [Nat.min_zero, take_zero, take_nil]
| 0, m, l => by rw [Nat.zero_min, take_zero, take_zero]
| succ n, succ m, nil => by simp only [take_nil]
| succ n, succ m, a :: l => by
simp only [take, succ_min_succ, take_take n m l]
@[simp] theorem take_replicate (a : α) : n m : Nat, take n (replicate m a) = replicate (min n m) a
| n, 0 => by simp [Nat.min_zero]
| 0, m => by simp [Nat.zero_min]
| succ n, succ m => by simp [replicate_succ, succ_min_succ, take_replicate]
@[simp] theorem drop_replicate (a : α) : n m : Nat, drop n (replicate m a) = replicate (m - n) a
| n, 0 => by simp
| 0, m => by simp
| succ n, succ m => by simp [replicate_succ, succ_sub_succ, drop_replicate]
/-- Taking the first `n` elements in `l₁ ++ l₂` is the same as appending the first `n` elements
of `l₁` to the first `n - l₁.length` elements of `l₂`. -/
theorem take_append_eq_append_take {l₁ l₂ : List α} {n : Nat} :
take n (l₁ ++ l₂) = take n l₁ ++ take (n - l₁.length) l₂ := by
induction l₁ generalizing n
· simp
· cases n
· simp [*]
· simp only [cons_append, take_cons_succ, length_cons, succ_eq_add_one, cons.injEq,
append_cancel_left_eq, true_and, *]
congr 1
omega
theorem take_append_of_le_length {l₁ l₂ : List α} {n : Nat} (h : n l₁.length) :
(l₁ ++ l₂).take n = l₁.take n := by
simp [take_append_eq_append_take, Nat.sub_eq_zero_of_le h]
/-- Taking the first `l₁.length + i` elements in `l₁ ++ l₂` is the same as appending the first
`i` elements of `l₂` to `l₁`. -/
theorem take_append {l₁ l₂ : List α} (i : Nat) :
take (l₁.length + i) (l₁ ++ l₂) = l₁ ++ take i l₂ := by
rw [take_append_eq_append_take, take_all_of_le (Nat.le_add_right _ _), Nat.add_sub_cancel_left]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the big list to the small list. -/
theorem getElem_take (L : List α) {i j : Nat} (hi : i < L.length) (hj : i < j) :
L[i] = (L.take j)[i]'(length_take .. Nat.lt_min.mpr hj, hi) :=
getElem_of_eq (take_append_drop j L).symm _ getElem_append ..
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the small list to the big list. -/
theorem getElem_take' (L : List α) {j i : Nat} {h : i < (L.take j).length} :
(L.take j)[i] =
L[i]'(Nat.lt_of_lt_of_le h (length_take_le' _ _)) := by
rw [length_take, Nat.lt_min] at h; rw [getElem_take L _ h.1]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the big list to the small list. -/
@[deprecated getElem_take (since := "2024-06-12")]
theorem get_take (L : List α) {i j : Nat} (hi : i < L.length) (hj : i < j) :
get L i, hi = get (L.take j) i, length_take .. Nat.lt_min.mpr hj, hi := by
simp [getElem_take _ hi hj]
/-- The `i`-th element of a list coincides with the `i`-th element of any of its prefixes of
length `> i`. Version designed to rewrite from the small list to the big list. -/
@[deprecated getElem_take (since := "2024-06-12")]
theorem get_take' (L : List α) {j i} :
get (L.take j) i =
get L i.1, Nat.lt_of_lt_of_le i.2 (length_take_le' _ _) := by
simp [getElem_take']
theorem getElem?_take_eq_none {l : List α} {n m : Nat} (h : n m) :
(l.take n)[m]? = none :=
getElem?_eq_none <| Nat.le_trans (length_take_le _ _) h
@[deprecated getElem?_take_eq_none (since := "2024-06-12")]
theorem get?_take_eq_none {l : List α} {n m : Nat} (h : n m) :
(l.take n).get? m = none := by
simp [getElem?_take_eq_none h]
theorem getElem?_take_eq_if {l : List α} {n m : Nat} :
(l.take n)[m]? = if m < n then l[m]? else none := by
split
· next h => exact getElem?_take h
· next h => exact getElem?_take_eq_none (Nat.le_of_not_lt h)
@[deprecated getElem?_take_eq_if (since := "2024-06-12")]
theorem get?_take_eq_if {l : List α} {n m : Nat} :
(l.take n).get? m = if m < n then l.get? m else none := by
simp [getElem?_take_eq_if]
theorem head?_take {l : List α} {n : Nat} :
(l.take n).head? = if n = 0 then none else l.head? := by
simp [head?_eq_getElem?, getElem?_take_eq_if]
split
· rw [if_neg (by omega)]
· rw [if_pos (by omega)]
theorem head_take {l : List α} {n : Nat} (h : l.take n []) :
(l.take n).head h = l.head (by simp_all) := by
apply Option.some_inj.1
rw [ head?_eq_head, head?_eq_head, head?_take, if_neg]
simp_all
theorem getLast?_take {l : List α} : (l.take n).getLast? = if n = 0 then none else l[n - 1]?.or l.getLast? := by
rw [getLast?_eq_getElem?, getElem?_take_eq_if, length_take]
split
· rw [if_neg (by omega)]
rw [Nat.min_def]
split
· rw [getElem?_eq_getElem (by omega)]
simp
· rw [ getLast?_eq_getElem?, getElem?_eq_none (by omega)]
simp
· rw [if_pos]
omega
theorem getLast_take {l : List α} (h : l.take n []) :
(l.take n).getLast h = l[n - 1]?.getD (l.getLast (by simp_all)) := by
rw [getLast_eq_getElem, getElem_take']
simp [length_take, Nat.min_def]
simp at h
split
· rw [getElem?_eq_getElem (by omega)]
simp
· rw [getElem?_eq_none (by omega), getLast_eq_getElem]
simp
theorem take_cons {l : List α} (h : 0 < n) : take n (a :: l) = a :: take (n - 1) l := by
cases n with
| zero => exact absurd h (Nat.lt_irrefl _)
| succ n => rfl
@[simp]
theorem take_eq_take :
{l : List α} {m n : Nat}, l.take m = l.take n min m l.length = min n l.length
| [], m, n => by simp [Nat.min_zero]
| _ :: xs, 0, 0 => by simp
| x :: xs, m + 1, 0 => by simp [Nat.zero_min, succ_min_succ]
| x :: xs, 0, n + 1 => by simp [Nat.zero_min, succ_min_succ]
| x :: xs, m + 1, n + 1 => by simp [succ_min_succ, take_eq_take]; omega
theorem drop_one : l : List α, drop 1 l = tail l
| [] | _ :: _ => rfl
theorem take_add (l : List α) (m n : Nat) : l.take (m + n) = l.take m ++ (l.drop m).take n := by
suffices take (m + n) (take m l ++ drop m l) = take m l ++ take n (drop m l) by
rw [take_append_drop] at this
assumption
rw [take_append_eq_append_take, take_all_of_le, append_right_inj]
· simp only [take_eq_take, length_take, length_drop]
omega
apply Nat.le_trans (m := m)
· apply length_take_le
· apply Nat.le_add_right
@[simp] theorem take_append_drop : (n : Nat) (l : List α), take n l ++ drop n l = l
| 0, _ => rfl
| _+1, [] => rfl
| n+1, x :: xs => congrArg (cons x) <| take_append_drop n xs
theorem dropLast_take {n : Nat} {l : List α} (h : n < l.length) :
(l.take n).dropLast = l.take n.pred := by
simp only [dropLast_eq_take, length_take, Nat.le_of_lt h, take_take, pred_le, Nat.min_eq_left]
@[simp] theorem length_drop : (i : Nat) (l : List α), length (drop i l) = length l - i
| 0, _ => rfl
| succ i, [] => Eq.symm (Nat.zero_sub (succ i))
| succ i, x :: l => calc
length (drop (succ i) (x :: l)) = length l - i := length_drop i l
_ = succ (length l) - succ i := (Nat.succ_sub_succ_eq_sub (length l) i).symm
theorem map_eq_append_split {f : α β} {l : List α} {s₁ s₂ : List β}
(h : map f l = s₁ ++ s₂) : l₁ l₂, l = l₁ ++ l₂ map f l₁ = s₁ map f l₂ = s₂ := by
have := h
rw [ take_append_drop (length s₁) l] at this
rw [map_append] at this
refine _, _, rfl, append_inj this ?_
rw [length_map, length_take, Nat.min_eq_left]
rw [ length_map l f, h, length_append]
apply Nat.le_add_right
theorem drop_of_length_le {l : List α} (h : l.length i) : drop i l = [] :=
length_eq_zero.1 (length_drop .. Nat.sub_eq_zero_of_le h)
/-! ### drop -/
theorem length_lt_of_drop_ne_nil {l : List α} {n} (h : drop n l []) : n < l.length :=
gt_of_not_le (mt drop_of_length_le h)
theorem drop_length_cons {l : List α} (h : l []) (a : α) :
(a :: l).drop l.length = [l.getLast h] := by
induction l generalizing a with
| nil =>
cases h rfl
| cons y l ih =>
simp only [drop, length]
by_cases h₁ : l = []
· simp [h₁]
rw [getLast_cons' _ h₁]
exact ih h₁ y
theorem take_of_length_le {l : List α} (h : l.length i) : take i l = l := by
have := take_append_drop i l
rw [drop_of_length_le h, append_nil] at this; exact this
/-- Dropping the elements up to `n` in `l₁ ++ l₂` is the same as dropping the elements up to `n`
in `l₁`, dropping the elements up to `n - l₁.length` in `l₂`, and appending them. -/
theorem drop_append_eq_append_drop {l₁ l₂ : List α} {n : Nat} :
drop n (l₁ ++ l₂) = drop n l₁ ++ drop (n - l₁.length) l₂ := by
induction l₁ generalizing n
· simp
· cases n
· simp [*]
· simp only [cons_append, drop_succ_cons, length_cons, succ_eq_add_one, append_cancel_left_eq, *]
congr 1
omega
theorem lt_length_of_take_ne_self {l : List α} {n} (h : l.take n l) : n < l.length :=
gt_of_not_le (mt take_of_length_le h)
theorem drop_append_of_le_length {l₁ l₂ : List α} {n : Nat} (h : n l₁.length) :
(l₁ ++ l₂).drop n = l₁.drop n ++ l₂ := by
simp [drop_append_eq_append_drop, Nat.sub_eq_zero_of_le h]
@[deprecated drop_of_length_le (since := "2024-07-07")] abbrev drop_length_le := @drop_of_length_le
@[deprecated take_of_length_le (since := "2024-07-07")] abbrev take_length_le := @take_of_length_le
@[simp] theorem drop_length (l : List α) : drop l.length l = [] := drop_of_length_le (Nat.le_refl _)
/-- Dropping the elements up to `l₁.length + i` in `l₁ + l₂` is the same as dropping the elements
up to `i` in `l₂`. -/
@[simp]
theorem drop_append {l₁ l₂ : List α} (i : Nat) : drop (l₁.length + i) (l₁ ++ l₂) = drop i l₂ := by
rw [drop_append_eq_append_drop, drop_eq_nil_of_le] <;>
simp [Nat.add_sub_cancel_left, Nat.le_add_right]
theorem lt_length_drop (L : List α) {i j : Nat} (h : i + j < L.length) : j < (L.drop i).length := by
have A : i < L.length := Nat.lt_of_le_of_lt (Nat.le.intro rfl) h
rw [(take_append_drop i L).symm] at h
simpa only [Nat.le_of_lt A, Nat.min_eq_left, Nat.add_lt_add_iff_left, length_take,
length_append] using h
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the big list to the small list. -/
theorem getElem_drop (L : List α) {i j : Nat} (h : i + j < L.length) :
L[i + j] = (L.drop i)[j]'(lt_length_drop L h) := by
have : i L.length := Nat.le_trans (Nat.le_add_right _ _) (Nat.le_of_lt h)
rw [getElem_of_eq (take_append_drop i L).symm h, getElem_append_right'] <;>
simp [Nat.min_eq_left this, Nat.add_sub_cancel_left, Nat.le_add_right]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the big list to the small list. -/
@[deprecated getElem_drop (since := "2024-06-12")]
theorem get_drop (L : List α) {i j : Nat} (h : i + j < L.length) :
get L i + j, h = get (L.drop i) j, lt_length_drop L h := by
simp [getElem_drop]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the small list to the big list. -/
theorem getElem_drop' (L : List α) {i : Nat} {j : Nat} {h : j < (L.drop i).length} :
(L.drop i)[j] = L[i + j]'(by
rw [Nat.add_comm]
exact Nat.add_lt_of_lt_sub (length_drop i L h)) := by
rw [getElem_drop]
/-- The `i + j`-th element of a list coincides with the `j`-th element of the list obtained by
dropping the first `i` elements. Version designed to rewrite from the small list to the big list. -/
@[deprecated getElem_drop' (since := "2024-06-12")]
theorem get_drop' (L : List α) {i j} :
get (L.drop i) j = get L i + j, by
rw [Nat.add_comm]
exact Nat.add_lt_of_lt_sub (length_drop i L j.2) := by
simp [getElem_drop']
@[simp] theorem take_length (l : List α) : take l.length l = l := take_of_length_le (Nat.le_refl _)
@[simp]
theorem getElem?_drop (L : List α) (i j : Nat) : (L.drop i)[j]? = L[i + j]? := by
ext
simp only [getElem?_eq_some, getElem_drop', Option.mem_def]
constructor <;> intro h, ha
· exact _, ha
· refine ?_, ha
rw [length_drop]
rw [Nat.add_comm] at h
apply Nat.lt_sub_of_add_lt h
theorem getElem_cons_drop : (l : List α) (i : Nat) (h : i < l.length),
l[i] :: drop (i + 1) l = drop i l
| _::_, 0, _ => rfl
| _::_, i+1, _ => getElem_cons_drop _ i _
@[deprecated getElem?_drop (since := "2024-06-12")]
theorem get?_drop (L : List α) (i j : Nat) : get? (L.drop i) j = get? L (i + j) := by
@[deprecated getElem_cons_drop (since := "2024-06-12")]
theorem get_cons_drop (l : List α) (i) : get l i :: drop (i + 1) l = drop i l := by
simp
theorem head?_drop (l : List α) (n : Nat) :
(l.drop n).head? = l[n]? := by
rw [head?_eq_getElem?, getElem?_drop, Nat.add_zero]
theorem drop_eq_getElem_cons {n} {l : List α} (h) : drop n l = l[n] :: drop (n + 1) l :=
(getElem_cons_drop _ n h).symm
theorem head_drop {l : List α} {n : Nat} (h : l.drop n []) :
(l.drop n).head h = l[n]'(by simp_all) := by
have w : n < l.length := length_lt_of_drop_ne_nil h
simpa [head?_eq_head, getElem?_eq_getElem, h, w] using head?_drop l n
@[deprecated drop_eq_getElem_cons (since := "2024-06-12")]
theorem drop_eq_get_cons {n} {l : List α} (h) : drop n l = get l n, h :: drop (n + 1) l := by
simp [drop_eq_getElem_cons]
theorem getLast?_drop {l : List α} : (l.drop n).getLast? = if l.length n then none else l.getLast? := by
rw [getLast?_eq_getElem?, getElem?_drop]
rw [length_drop]
split
· rw [getElem?_eq_none (by omega)]
· rw [getLast?_eq_getElem?]
congr
omega
@[simp]
theorem getElem?_take_of_lt {l : List α} {n m : Nat} (h : m < n) : (l.take n)[m]? = l[m]? := by
induction n generalizing l m with
| zero =>
exact absurd h (Nat.not_lt_of_le m.zero_le)
| succ _ hn =>
cases l with
| nil => simp only [take_nil]
| cons hd tl =>
cases m
· simp
· simpa using hn (Nat.lt_of_succ_lt_succ h)
theorem getLast_drop {l : List α} (h : l.drop n []) :
(l.drop n).getLast h = l.getLast (ne_nil_of_length_pos (by simp at h; omega)) := by
simp only [ne_eq, drop_eq_nil_iff_le] at h
apply Option.some_inj.1
simp only [ getLast?_eq_getLast, getLast?_drop, ite_eq_right_iff]
omega
@[deprecated getElem?_take_of_lt (since := "2024-06-12")]
theorem get?_take {l : List α} {n m : Nat} (h : m < n) : (l.take n).get? m = l.get? m := by
simp [getElem?_take_of_lt, h]
theorem set_eq_take_append_cons_drop {l : List α} {n : Nat} {a : α} :
l.set n a = if n < l.length then l.take n ++ a :: l.drop (n + 1) else l := by
split <;> rename_i h
· ext1 m
by_cases h' : m < n
· rw [getElem?_append (by simp [length_take]; omega), getElem?_set_ne (by omega),
getElem?_take h']
· by_cases h'' : m = n
· subst h''
rw [getElem?_set_eq (by simp; omega), getElem?_append_right, length_take,
Nat.min_eq_left (by omega), Nat.sub_self, getElem?_cons_zero]
rw [length_take]
exact Nat.min_le_left m l.length
· have h''' : n < m := by omega
rw [getElem?_set_ne (by omega), getElem?_append_right, length_take,
Nat.min_eq_left (by omega)]
· obtain k, rfl := Nat.exists_eq_add_of_lt h'''
have p : n + k + 1 - n = k + 1 := by omega
rw [p]
rw [getElem?_cons_succ, getElem?_drop]
congr 1
omega
· rw [length_take]
exact Nat.le_trans (Nat.min_le_left _ _) (by omega)
· rw [set_eq_of_length_le]
omega
@[simp]
theorem getElem?_take_of_succ {l : List α} {n : Nat} : (l.take (n + 1))[n]? = l[n]? :=
getElem?_take_of_lt (Nat.lt_succ_self n)
theorem exists_of_set {n : Nat} {a' : α} {l : List α} (h : n < l.length) :
l₁ l₂, l = l₁ ++ l[n] :: l₂ l₁.length = n l.set n a' = l₁ ++ a' :: l₂ := by
refine l.take n, l.drop (n + 1), by simp, length_take_of_le (Nat.le_of_lt h), ?_
simp [set_eq_take_append_cons_drop, h]
@[simp] theorem drop_drop (n : Nat) : (m) (l : List α), drop n (drop m l) = drop (n + m) l
| m, [] => by simp
| 0, l => by simp
| m + 1, a :: l =>
calc
drop n (drop (m + 1) (a :: l)) = drop n (drop m l) := rfl
_ = drop (n + m) l := drop_drop n m l
_ = drop (n + (m + 1)) (a :: l) := rfl
theorem drop_set_of_lt (a : α) {n m : Nat} (l : List α)
(hnm : n < m) : drop m (l.set n a) = l.drop m :=
ext_getElem? fun k => by simpa only [getElem?_drop] using getElem?_set_ne (by omega)
theorem drop_take : (m n : Nat) (l : List α), drop n (take m l) = take (m - n) (drop n l)
theorem take_drop : (m n : Nat) (l : List α), take n (drop m l) = drop m (take (m + n) l)
| 0, _, _ => by simp
| _, 0, _ => by simp
| _, _, [] => by simp
| m+1, n+1, h :: t => by
simp [take_succ_cons, drop_succ_cons, drop_take m n t]
congr 1
omega
| _+1, _, _ :: _ => by simpa [Nat.succ_add, take_succ_cons, drop_succ_cons] using take_drop ..
theorem take_reverse {α} {xs : List α} (n : Nat) (h : n xs.length) :
xs.reverse.take n = (xs.drop (xs.length - n)).reverse := by
induction xs generalizing n <;>
simp only [reverse_cons, drop, reverse_nil, Nat.zero_sub, length, take_nil]
next xs_hd xs_tl xs_ih =>
cases Nat.lt_or_eq_of_le h with
| inl h' =>
have h' := Nat.le_of_succ_le_succ h'
rw [take_append_of_le_length, xs_ih _ h']
rw [show xs_tl.length + 1 - n = succ (xs_tl.length - n) from _, drop]
· rwa [succ_eq_add_one, Nat.sub_add_comm]
· rwa [length_reverse]
| inr h' =>
subst h'
rw [length, Nat.sub_self, drop]
suffices xs_tl.length + 1 = (xs_tl.reverse ++ [xs_hd]).length by
rw [this, take_length, reverse_cons]
rw [length_append, length_reverse]
rfl
@[deprecated drop_drop (since := "2024-06-15")]
theorem drop_add (m n) (l : List α) : drop (m + n) l = drop m (drop n l) := by
simp [drop_drop]
@[deprecated (since := "2024-06-15")] abbrev reverse_take := @take_reverse
@[simp]
theorem tail_drop (l : List α) (n : Nat) : (l.drop n).tail = l.drop (n + 1) := by
induction l generalizing n with
| nil => simp
| cons hd tl hl =>
cases n
· simp
· simp [hl]
@[simp]
theorem drop_tail (l : List α) (n : Nat) : l.tail.drop n = l.drop (n + 1) := by
rw [ drop_drop, drop_one]
@[simp]
theorem drop_eq_nil_iff_le {l : List α} {k : Nat} : l.drop k = [] l.length k := by
refine fun h => ?_, drop_eq_nil_of_le
induction k generalizing l with
| zero =>
simp only [drop] at h
simp [h]
| succ k hk =>
cases l
· simp
· simp only [drop] at h
simpa [Nat.succ_le_succ_iff] using hk h
@[simp]
theorem take_eq_nil_iff {l : List α} {k : Nat} : l.take k = [] k = 0 l = [] := by
cases l <;> cases k <;> simp [Nat.succ_ne_zero]
theorem drop_eq_nil_of_eq_nil : {as : List α} {i}, as = [] as.drop i = []
| _, _, rfl => drop_nil
theorem ne_nil_of_drop_ne_nil {as : List α} {i : Nat} (h: as.drop i []) : as [] :=
mt drop_eq_nil_of_eq_nil h
theorem take_eq_nil_of_eq_nil : {as : List α} {i}, as = [] as.take i = []
| _, _, rfl => take_nil
theorem ne_nil_of_take_ne_nil {as : List α} {i : Nat} (h : as.take i []) : as [] :=
mt take_eq_nil_of_eq_nil h
theorem set_take {l : List α} {n m : Nat} {a : α} :
(l.set m a).take n = (l.take n).set m a := by
induction n generalizing l m with
| zero => simp
| succ _ hn =>
cases l with
| nil => simp
| cons hd tl => cases m <;> simp_all
theorem drop_set {l : List α} {n m : Nat} {a : α} :
(l.set m a).drop n = if m < n then l.drop n else (l.drop n).set (m - n) a := by
induction n generalizing l m with
| zero => simp
| succ _ hn =>
cases l with
| nil => simp
| cons hd tl =>
cases m
· simp_all
· simp only [hn, set_cons_succ, drop_succ_cons, succ_lt_succ_iff]
congr 2
exact (Nat.add_sub_add_right ..).symm
theorem set_drop {l : List α} {n m : Nat} {a : α} :
(l.drop n).set m a = (l.set (n + m) a).drop n := by
rw [drop_set, if_neg, add_sub_self_left n m]
exact (Nat.not_lt).2 (le_add_right n m)
theorem take_concat_get (l : List α) (i : Nat) (h : i < l.length) :
(l.take i).concat l[i] = l.take (i+1) :=
Eq.symm <| (append_left_inj _).1 <| (take_append_drop (i+1) l).trans <| by
rw [concat_eq_append, append_assoc, singleton_append, get_drop_eq_drop, take_append_drop]
@[deprecated take_succ_cons (since := "2024-07-25")]
theorem take_cons_succ : (a::as).take (i+1) = a :: as.take i := rfl
@[deprecated take_of_length_le (since := "2024-07-25")]
theorem take_all_of_le {n} {l : List α} (h : length l n) : take n l = l :=
take_of_length_le h
theorem drop_left : l₁ l₂ : List α, drop (length l₁) (l₁ ++ l₂) = l₂
| [], _ => rfl
| _ :: l₁, l₂ => drop_left l₁ l₂
@[simp]
theorem drop_left' {l₁ l₂ : List α} {n} (h : length l₁ = n) : drop n (l₁ ++ l₂) = l₂ := by
rw [ h]; apply drop_left
theorem take_left : l₁ l₂ : List α, take (length l₁) (l₁ ++ l₂) = l₁
| [], _ => rfl
| a :: l₁, l₂ => congrArg (cons a) (take_left l₁ l₂)
@[simp]
theorem take_left' {l₁ l₂ : List α} {n} (h : length l₁ = n) : take n (l₁ ++ l₂) = l₁ := by
rw [ h]; apply take_left
theorem take_succ {l : List α} {n : Nat} : l.take (n + 1) = l.take n ++ l[n]?.toList := by
induction l generalizing n with
| nil =>
simp only [take_nil, Option.toList, getElem?_nil, append_nil]
| cons hd tl hl =>
cases n
· simp only [take, Option.toList, getElem?_cons_zero, nil_append]
· simp only [take, hl, getElem?_cons_succ, cons_append]
@[deprecated (since := "2024-07-25")]
theorem drop_sizeOf_le [SizeOf α] (l : List α) (n : Nat) : sizeOf (l.drop n) sizeOf l := by
induction l generalizing n with
| nil => rw [drop_nil]; apply Nat.le_refl
| cons _ _ lih =>
induction n with
| zero => apply Nat.le_refl
| succ n =>
exact Trans.trans (lih _) (Nat.le_add_left _ _)
theorem dropLast_eq_take (l : List α) : l.dropLast = l.take (l.length - 1) := by
cases l with
| nil => simp [dropLast]
| cons x l =>
induction l generalizing x <;> simp_all [dropLast]
@[simp] theorem map_take (f : α β) :
(L : List α) (i : Nat), (L.take i).map f = (L.map f).take i
| [], i => by simp
| _, 0 => by simp
| h :: t, n + 1 => by dsimp; rw [map_take f t n]
@[simp] theorem map_drop (f : α β) :
(L : List α) (i : Nat), (L.drop i).map f = (L.map f).drop i
| [], i => by simp
| L, 0 => by simp
| h :: t, n + 1 => by
dsimp
rw [map_drop f t]
/-! ### takeWhile and dropWhile -/
theorem takeWhile_cons (p : α Bool) (a : α) (l : List α) :
(a :: l).takeWhile p = if p a then a :: l.takeWhile p else [] := by
simp only [takeWhile]
by_cases h: p a <;> simp [h]
@[simp] theorem takeWhile_cons_of_pos {p : α Bool} {a : α} {l : List α} (h : p a) :
(a :: l).takeWhile p = a :: l.takeWhile p := by
simp [takeWhile_cons, h]
@[simp] theorem takeWhile_cons_of_neg {p : α Bool} {a : α} {l : List α} (h : ¬ p a) :
(a :: l).takeWhile p = [] := by
simp [takeWhile_cons, h]
theorem dropWhile_cons :
(x :: xs : List α).dropWhile p = if p x then xs.dropWhile p else x :: xs := by
split <;> simp_all [dropWhile]
@[simp] theorem dropWhile_cons_of_pos {a : α} {l : List α} (h : p a) :
(a :: l).dropWhile p = l.dropWhile p := by
simp [dropWhile_cons, h]
@[simp] theorem dropWhile_cons_of_neg {a : α} {l : List α} (h : ¬ p a) :
(a :: l).dropWhile p = a :: l := by
simp [dropWhile_cons, h]
theorem head?_takeWhile (p : α Bool) (l : List α) : (l.takeWhile p).head? = l.head?.filter p := by
cases l with
| nil => rfl
| cons x xs =>
simp only [takeWhile_cons, head?_cons, Option.filter_some]
split <;> simp
theorem head_takeWhile (p : α Bool) (l : List α) (w) :
(l.takeWhile p).head w = l.head (by rintro rfl; simp_all) := by
cases l with
| nil => rfl
| cons x xs =>
simp only [takeWhile_cons, head_cons]
simp only [takeWhile_cons] at w
split <;> simp_all
theorem head?_dropWhile_not (p : α Bool) (l : List α) :
match (l.dropWhile p).head? with | some x => p x = false | none => True := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [dropWhile_cons]
split <;> rename_i h <;> split at h <;> simp_all
theorem head_dropWhile_not (p : α Bool) (l : List α) (w) :
p ((l.dropWhile p).head w) = false := by
simpa [head?_eq_head, w] using head?_dropWhile_not p l
theorem takeWhile_map (f : α β) (p : β Bool) (l : List α) :
(l.map f).takeWhile p = (l.takeWhile (p f)).map f := by
induction l with
| nil => rfl
| cons x xs ih =>
simp only [map_cons, takeWhile_cons]
split <;> simp_all
theorem dropWhile_map (f : α β) (p : β Bool) (l : List α) :
(l.map f).dropWhile p = (l.dropWhile (p f)).map f := by
induction l with
| nil => rfl
| cons x xs ih =>
simp only [map_cons, dropWhile_cons]
split <;> simp_all
theorem takeWhile_filterMap (f : α Option β) (p : β Bool) (l : List α) :
(l.filterMap f).takeWhile p = (l.takeWhile fun a => (f a).all p).filterMap f := by
induction l with
| nil => rfl
| cons x xs ih =>
simp only [filterMap_cons]
split <;> rename_i h
· simp only [takeWhile_cons, h]
split <;> simp_all
· simp [takeWhile_cons, h, ih]
split <;> simp_all [filterMap_cons]
theorem dropWhile_filterMap (f : α Option β) (p : β Bool) (l : List α) :
(l.filterMap f).dropWhile p = (l.dropWhile fun a => (f a).all p).filterMap f := by
induction l with
| nil => rfl
| cons x xs ih =>
simp only [filterMap_cons]
split <;> rename_i h
· simp only [dropWhile_cons, h]
split <;> simp_all
· simp [dropWhile_cons, h, ih]
split <;> simp_all [filterMap_cons]
theorem takeWhile_filter (p q : α Bool) (l : List α) :
(l.filter p).takeWhile q = (l.takeWhile fun a => !p a || q a).filter p := by
simp [ filterMap_eq_filter, takeWhile_filterMap]
theorem dropWhile_filter (p q : α Bool) (l : List α) :
(l.filter p).dropWhile q = (l.dropWhile fun a => !p a || q a).filter p := by
simp [ filterMap_eq_filter, dropWhile_filterMap]
@[simp] theorem takeWhile_append_dropWhile (p : α Bool) :
(l : List α), takeWhile p l ++ dropWhile p l = l
| [] => rfl
| x :: xs => by simp [takeWhile, dropWhile]; cases p x <;> simp [takeWhile_append_dropWhile p xs]
theorem takeWhile_append {xs ys : List α} :
(xs ++ ys).takeWhile p =
if (xs.takeWhile p).length = xs.length then xs ++ ys.takeWhile p else xs.takeWhile p := by
induction xs with
| nil => simp
| cons x xs ih =>
simp only [cons_append, takeWhile_cons]
split
· simp_all only [length_cons, add_one_inj]
split <;> rfl
· simp_all
@[simp] theorem takeWhile_append_of_pos {p : α Bool} {l₁ l₂ : List α} (h : a l₁, p a) :
(l₁ ++ l₂).takeWhile p = l₁ ++ l₂.takeWhile p := by
induction l₁ with
| nil => simp
| cons x xs ih => simp_all [takeWhile_cons]
theorem dropWhile_append {xs ys : List α} :
(xs ++ ys).dropWhile p =
if (xs.dropWhile p).isEmpty then ys.dropWhile p else xs.dropWhile p ++ ys := by
induction xs with
| nil => simp
| cons h t ih =>
simp only [cons_append, dropWhile_cons]
split <;> simp_all
@[simp] theorem dropWhile_append_of_pos {p : α Bool} {l₁ l₂ : List α} (h : a l₁, p a) :
(l₁ ++ l₂).dropWhile p = l₂.dropWhile p := by
induction l₁ with
| nil => simp
| cons x xs ih => simp_all [dropWhile_cons]
@[simp] theorem takeWhile_replicate_eq_filter (p : α Bool) :
(replicate n a).takeWhile p = (replicate n a).filter p := by
induction n with
| zero => simp
| succ n ih =>
simp only [replicate_succ, takeWhile_cons]
split <;> simp_all
theorem takeWhile_replicate (p : α Bool) :
(replicate n a).takeWhile p = if p a then replicate n a else [] := by
rw [takeWhile_replicate_eq_filter, filter_replicate]
@[simp] theorem dropWhile_replicate_eq_filter_not (p : α Bool) :
(replicate n a).dropWhile p = (replicate n a).filter (fun a => !p a) := by
induction n with
| zero => simp
| succ n ih =>
simp only [replicate_succ, dropWhile_cons]
split <;> simp_all
theorem dropWhile_replicate (p : α Bool) :
(replicate n a).dropWhile p = if p a then [] else replicate n a := by
simp only [dropWhile_replicate_eq_filter_not, filter_replicate]
split <;> simp_all
theorem take_takeWhile {l : List α} (p : α Bool) n :
(l.takeWhile p).take n = (l.take n).takeWhile p := by
induction l generalizing n with
| nil => simp
| cons x xs ih =>
by_cases h : p x <;> cases n <;> simp [takeWhile_cons, h, ih, take_succ_cons]
@[simp] theorem all_takeWhile {l : List α} : (l.takeWhile p).all p = true := by
induction l with
| nil => rfl
| cons h t ih => by_cases p h <;> simp_all
@[simp] theorem any_dropWhile {l : List α} :
(l.dropWhile p).any (fun x => !p x) = !l.all p := by
induction l with
| nil => rfl
| cons h t ih => by_cases p h <;> simp_all
/-! ### splitAt -/
@[simp] theorem splitAt_eq (n : Nat) (l : List α) : splitAt n l = (l.take n, l.drop n) := by
rw [splitAt, splitAt_go, reverse_nil, nil_append]
split <;> simp_all [take_of_length_le, drop_of_length_le]
/-! ### rotateLeft -/
@[simp] theorem rotateLeft_replicate (n) (a : α) : rotateLeft (replicate m a) n = replicate m a := by
cases n with
| zero => simp
| succ n =>
suffices 1 < m m - (n + 1) % m + min ((n + 1) % m) m = m by
simpa [rotateLeft]
intro h
rw [Nat.min_eq_left (Nat.le_of_lt (Nat.mod_lt _ (by omega)))]
have : (n + 1) % m < m := Nat.mod_lt _ (by omega)
omega
@[simp] theorem rotateLeft_zero (l : List α) : rotateLeft l 0 = l := by
simp [rotateLeft]
-- TODO Batteries defines its own `getElem?_rotate`, which we need to adapt.
-- TODO Prove `map_rotateLeft`, using `ext` and `getElem?_rotateLeft`.
/-! ### rotateRight -/
@[simp] theorem rotateRight_replicate (n) (a : α) : rotateRight (replicate m a) n = replicate m a := by
cases n with
| zero => simp
| succ n =>
suffices 1 < m m - (m - (n + 1) % m) + min (m - (n + 1) % m) m = m by
simpa [rotateRight]
intro h
have : (n + 1) % m < m := Nat.mod_lt _ (by omega)
rw [Nat.min_eq_left (by omega)]
omega
@[simp] theorem rotateRight_zero (l : List α) : rotateRight l 0 = l := by
simp [rotateRight]
/-! ### zipWith -/
@[simp] theorem length_zipWith (f : α β γ) (l₁ l₂) :
length (zipWith f l₁ l₂) = min (length l₁) (length l₂) := by
induction l₁ generalizing l₂ <;> cases l₂ <;>
simp_all [succ_min_succ, Nat.zero_min, Nat.min_zero]
theorem zipWith_eq_zipWith_take_min : (l₁ : List α) (l₂ : List β),
zipWith f l₁ l₂ = zipWith f (l₁.take (min l₁.length l₂.length)) (l₂.take (min l₁.length l₂.length))
| [], _ => by simp
| _, [] => by simp
| a :: l₁, b :: l₂ => by simp [succ_min_succ, zipWith_eq_zipWith_take_min l₁ l₂]
@[simp] theorem zipWith_replicate {a : α} {b : β} {m n : Nat} :
zipWith f (replicate m a) (replicate n b) = replicate (min m n) (f a b) := by
rw [zipWith_eq_zipWith_take_min]
simp
/-! ### zip -/
@[simp] theorem length_zip (l₁ : List α) (l₂ : List β) :
length (zip l₁ l₂) = min (length l₁) (length l₂) := by
simp [zip]
theorem zip_eq_zip_take_min : (l₁ : List α) (l₂ : List β),
zip l₁ l₂ = zip (l₁.take (min l₁.length l₂.length)) (l₂.take (min l₁.length l₂.length))
| [], _ => by simp
| _, [] => by simp
| a :: l₁, b :: l₂ => by simp [succ_min_succ, zip_eq_zip_take_min l₁ l₂]
@[simp] theorem zip_replicate {a : α} {b : β} {m n : Nat} :
zip (replicate m a) (replicate n b) = replicate (min m n) (a, b) := by
rw [zip_eq_zip_take_min]
simp
/-! ### minimum? -/
-- A specialization of `minimum?_eq_some_iff` to Nat.
theorem minimum?_eq_some_iff' {xs : List Nat} :
xs.minimum? = some a (a xs b xs, a b) :=
minimum?_eq_some_iff
(le_refl := Nat.le_refl)
(min_eq_or := fun _ _ => by omega)
(le_min_iff := fun _ _ _ => by omega)
-- This could be generalized,
-- but will first require further work on order typeclasses in the core repository.
theorem minimum?_cons' {a : Nat} {l : List Nat} :
(a :: l).minimum? = some (match l.minimum? with
| none => a
| some m => min a m) := by
rw [minimum?_eq_some_iff']
split <;> rename_i h m
· simp_all
· rw [minimum?_eq_some_iff'] at m
obtain m, le := m
rw [Nat.min_def]
constructor
· split
· exact mem_cons_self a l
· exact mem_cons_of_mem a m
· intro b m
cases List.mem_cons.1 m with
| inl => split <;> omega
| inr h =>
specialize le b h
split <;> omega
/-! ### maximum? -/
-- A specialization of `maximum?_eq_some_iff` to Nat.
theorem maximum?_eq_some_iff' {xs : List Nat} :
xs.maximum? = some a (a xs b xs, b a) :=
maximum?_eq_some_iff
(le_refl := Nat.le_refl)
(max_eq_or := fun _ _ => by omega)
(max_le_iff := fun _ _ _ => by omega)
-- This could be generalized,
-- but will first require further work on order typeclasses in the core repository.
theorem maximum?_cons' {a : Nat} {l : List Nat} :
(a :: l).maximum? = some (match l.maximum? with
| none => a
| some m => max a m) := by
rw [maximum?_eq_some_iff']
split <;> rename_i h m
· simp_all
· rw [maximum?_eq_some_iff'] at m
obtain m, le := m
rw [Nat.max_def]
constructor
· split
· exact mem_cons_of_mem a m
· exact mem_cons_self a l
· intro b m
cases List.mem_cons.1 m with
| inl => split <;> omega
| inr h =>
specialize le b h
split <;> omega
-- TODO Batteries defines its own `getElem?_rotate`, which we need to adapt.
-- TODO Prove `map_rotateRight`, using `ext` and `getElem?_rotateRight`.
end List

363
src/Init/Data/List/Zip.lean Normal file
View File

@@ -0,0 +1,363 @@
/-
Copyright (c) 2014 Parikshit Khanna. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Parikshit Khanna, Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Mario Carneiro
-/
prelude
import Init.Data.List.TakeDrop
/-!
# Lemmas about `List.zip`, `List.zipWith`, `List.zipWithAll`, and `List.unzip`.
-/
namespace List
open Nat
/-! ## Zippers -/
/-! ### zip -/
theorem zip_map (f : α γ) (g : β δ) :
(l₁ : List α) (l₂ : List β), zip (l₁.map f) (l₂.map g) = (zip l₁ l₂).map (Prod.map f g)
| [], l₂ => rfl
| l₁, [] => by simp only [map, zip_nil_right]
| a :: l₁, b :: l₂ => by
simp only [map, zip_cons_cons, zip_map, Prod.map]; constructor
theorem zip_map_left (f : α γ) (l₁ : List α) (l₂ : List β) :
zip (l₁.map f) l₂ = (zip l₁ l₂).map (Prod.map f id) := by rw [ zip_map, map_id]
theorem zip_map_right (f : β γ) (l₁ : List α) (l₂ : List β) :
zip l₁ (l₂.map f) = (zip l₁ l₂).map (Prod.map id f) := by rw [ zip_map, map_id]
theorem zip_append :
{l₁ r₁ : List α} {l₂ r₂ : List β} (_h : length l₁ = length l₂),
zip (l₁ ++ r₁) (l₂ ++ r₂) = zip l₁ l₂ ++ zip r₁ r₂
| [], r₁, l₂, r₂, h => by simp only [eq_nil_of_length_eq_zero h.symm]; rfl
| l₁, r₁, [], r₂, h => by simp only [eq_nil_of_length_eq_zero h]; rfl
| a :: l₁, r₁, b :: l₂, r₂, h => by
simp only [cons_append, zip_cons_cons, zip_append (Nat.succ.inj h)]
theorem zip_map' (f : α β) (g : α γ) :
l : List α, zip (l.map f) (l.map g) = l.map fun a => (f a, g a)
| [] => rfl
| a :: l => by simp only [map, zip_cons_cons, zip_map']
theorem of_mem_zip {a b} : {l₁ : List α} {l₂ : List β}, (a, b) zip l₁ l₂ a l₁ b l₂
| _ :: l₁, _ :: l₂, h => by
cases h
case head => simp
case tail h =>
· have := of_mem_zip h
exact Mem.tail _ this.1, Mem.tail _ this.2
@[deprecated of_mem_zip (since := "2024-07-28")] abbrev mem_zip := @of_mem_zip
theorem map_fst_zip :
(l₁ : List α) (l₂ : List β), l₁.length l₂.length map Prod.fst (zip l₁ l₂) = l₁
| [], bs, _ => rfl
| _ :: as, _ :: bs, h => by
simp [Nat.succ_le_succ_iff] at h
show _ :: map Prod.fst (zip as bs) = _ :: as
rw [map_fst_zip as bs h]
| a :: as, [], h => by simp at h
theorem map_snd_zip :
(l₁ : List α) (l₂ : List β), l₂.length l₁.length map Prod.snd (zip l₁ l₂) = l₂
| _, [], _ => by
rw [zip_nil_right]
rfl
| [], b :: bs, h => by simp at h
| a :: as, b :: bs, h => by
simp [Nat.succ_le_succ_iff] at h
show _ :: map Prod.snd (zip as bs) = _ :: bs
rw [map_snd_zip as bs h]
theorem map_prod_left_eq_zip {l : List α} (f : α β) :
(l.map fun x => (x, f x)) = l.zip (l.map f) := by
rw [ zip_map']
congr
exact map_id _
theorem map_prod_right_eq_zip {l : List α} (f : α β) :
(l.map fun x => (f x, x)) = (l.map f).zip l := by
rw [ zip_map']
congr
exact map_id _
/-- See also `List.zip_replicate` in `Init.Data.List.TakeDrop` for a generalization with different lengths. -/
@[simp] theorem zip_replicate' {a : α} {b : β} {n : Nat} :
zip (replicate n a) (replicate n b) = replicate n (a, b) := by
induction n with
| zero => rfl
| succ n ih => simp [replicate_succ, ih]
/-! ### zipWith -/
theorem zipWith_comm (f : α β γ) :
(la : List α) (lb : List β), zipWith f la lb = zipWith (fun b a => f a b) lb la
| [], _ => List.zipWith_nil_right.symm
| _ :: _, [] => rfl
| _ :: as, _ :: bs => congrArg _ (zipWith_comm f as bs)
theorem zipWith_comm_of_comm (f : α α β) (comm : x y : α, f x y = f y x) (l l' : List α) :
zipWith f l l' = zipWith f l' l := by
rw [zipWith_comm]
simp only [comm]
@[simp]
theorem zipWith_same (f : α α δ) : l : List α, zipWith f l l = l.map fun a => f a a
| [] => rfl
| _ :: xs => congrArg _ (zipWith_same f xs)
/--
See also `getElem?_zipWith'` for a variant
using `Option.map` and `Option.bind` rather than a `match`.
-/
theorem getElem?_zipWith {f : α β γ} {i : Nat} :
(List.zipWith f as bs)[i]? = match as[i]?, bs[i]? with
| some a, some b => some (f a b) | _, _ => none := by
induction as generalizing bs i with
| nil => cases bs with
| nil => simp
| cons b bs => simp
| cons a as aih => cases bs with
| nil => simp
| cons b bs => cases i <;> simp_all
/-- Variant of `getElem?_zipWith` using `Option.map` and `Option.bind` rather than a `match`. -/
theorem getElem?_zipWith' {f : α β γ} {i : Nat} :
(zipWith f l₁ l₂)[i]? = (l₁[i]?.map f).bind fun g => l₂[i]?.map g := by
induction l₁ generalizing l₂ i with
| nil => rw [zipWith] <;> simp
| cons head tail =>
cases l₂
· simp
· cases i <;> simp_all
theorem getElem?_zipWith_eq_some (f : α β γ) (l₁ : List α) (l₂ : List β) (z : γ) (i : Nat) :
(zipWith f l₁ l₂)[i]? = some z
x y, l₁[i]? = some x l₂[i]? = some y f x y = z := by
induction l₁ generalizing l₂ i
· simp
· cases l₂ <;> cases i <;> simp_all
theorem getElem?_zip_eq_some (l₁ : List α) (l₂ : List β) (z : α × β) (i : Nat) :
(zip l₁ l₂)[i]? = some z l₁[i]? = some z.1 l₂[i]? = some z.2 := by
cases z
rw [zip, getElem?_zipWith_eq_some]; constructor
· rintro x, y, h₀, h₁, h₂
simpa [h₀, h₁] using h₂
· rintro h₀, h₁
exact _, _, h₀, h₁, rfl
@[deprecated getElem?_zipWith (since := "2024-06-12")]
theorem get?_zipWith {f : α β γ} :
(List.zipWith f as bs).get? i = match as.get? i, bs.get? i with
| some a, some b => some (f a b) | _, _ => none := by
simp [getElem?_zipWith]
set_option linter.deprecated false in
@[deprecated getElem?_zipWith (since := "2024-06-07")] abbrev zipWith_get? := @get?_zipWith
theorem head?_zipWith {f : α β γ} :
(List.zipWith f as bs).head? = match as.head?, bs.head? with
| some a, some b => some (f a b) | _, _ => none := by
simp [head?_eq_getElem?, getElem?_zipWith]
theorem head_zipWith {f : α β γ} (h):
(List.zipWith f as bs).head h = f (as.head (by rintro rfl; simp_all)) (bs.head (by rintro rfl; simp_all)) := by
apply Option.some.inj
rw [ head?_eq_head, head?_zipWith, head?_eq_head, head?_eq_head]
@[simp]
theorem zipWith_map {μ} (f : γ δ μ) (g : α γ) (h : β δ) (l₁ : List α) (l₂ : List β) :
zipWith f (l₁.map g) (l₂.map h) = zipWith (fun a b => f (g a) (h b)) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWith_map_left (l₁ : List α) (l₂ : List β) (f : α α') (g : α' β γ) :
zipWith g (l₁.map f) l₂ = zipWith (fun a b => g (f a) b) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWith_map_right (l₁ : List α) (l₂ : List β) (f : β β') (g : α β' γ) :
zipWith g l₁ (l₂.map f) = zipWith (fun a b => g a (f b)) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWith_foldr_eq_zip_foldr {f : α β γ} (i : δ):
(zipWith f l₁ l₂).foldr g i = (zip l₁ l₂).foldr (fun p r => g (f p.1 p.2) r) i := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWith_foldl_eq_zip_foldl {f : α β γ} (i : δ):
(zipWith f l₁ l₂).foldl g i = (zip l₁ l₂).foldl (fun r p => g r (f p.1 p.2)) i := by
induction l₁ generalizing i l₂ <;> cases l₂ <;> simp_all
@[simp]
theorem zipWith_eq_nil_iff {f : α β γ} {l l'} : zipWith f l l' = [] l = [] l' = [] := by
cases l <;> cases l' <;> simp
theorem map_zipWith {δ : Type _} (f : α β) (g : γ δ α) (l : List γ) (l' : List δ) :
map f (zipWith g l l') = zipWith (fun x y => f (g x y)) l l' := by
induction l generalizing l' with
| nil => simp
| cons hd tl hl =>
· cases l'
· simp
· simp [hl]
theorem take_zipWith : (zipWith f l l').take n = zipWith f (l.take n) (l'.take n) := by
induction l generalizing l' n with
| nil => simp
| cons hd tl hl =>
cases l'
· simp
· cases n
· simp
· simp [hl]
@[deprecated take_zipWith (since := "2024-07-26")] abbrev zipWith_distrib_take := @take_zipWith
theorem drop_zipWith : (zipWith f l l').drop n = zipWith f (l.drop n) (l'.drop n) := by
induction l generalizing l' n with
| nil => simp
| cons hd tl hl =>
· cases l'
· simp
· cases n
· simp
· simp [hl]
@[deprecated drop_zipWith (since := "2024-07-26")] abbrev zipWith_distrib_drop := @drop_zipWith
theorem tail_zipWith : (zipWith f l l').tail = zipWith f l.tail l'.tail := by
rw [ drop_one]; simp [drop_zipWith]
@[deprecated tail_zipWith (since := "2024-07-28")] abbrev zipWith_distrib_tail := @tail_zipWith
theorem zipWith_append (f : α β γ) (l la : List α) (l' lb : List β)
(h : l.length = l'.length) :
zipWith f (l ++ la) (l' ++ lb) = zipWith f l l' ++ zipWith f la lb := by
induction l generalizing l' with
| nil =>
have : l' = [] := eq_nil_of_length_eq_zero (by simpa using h.symm)
simp [this]
| cons hl tl ih =>
cases l' with
| nil => simp at h
| cons head tail =>
simp only [length_cons, Nat.succ.injEq] at h
simp [ih _ h]
/-- See also `List.zipWith_replicate` in `Init.Data.List.TakeDrop` for a generalization with different lengths. -/
@[simp] theorem zipWith_replicate' {a : α} {b : β} {n : Nat} :
zipWith f (replicate n a) (replicate n b) = replicate n (f a b) := by
induction n with
| zero => rfl
| succ n ih => simp [replicate_succ, ih]
/-! ### zipWithAll -/
theorem getElem?_zipWithAll {f : Option α Option β γ} {i : Nat} :
(zipWithAll f as bs)[i]? = match as[i]?, bs[i]? with
| none, none => .none | a?, b? => some (f a? b?) := by
induction as generalizing bs i with
| nil => induction bs generalizing i with
| nil => simp
| cons b bs bih => cases i <;> simp_all
| cons a as aih => cases bs with
| nil =>
specialize @aih []
cases i <;> simp_all
| cons b bs => cases i <;> simp_all
@[deprecated getElem?_zipWithAll (since := "2024-06-12")]
theorem get?_zipWithAll {f : Option α Option β γ} :
(zipWithAll f as bs).get? i = match as.get? i, bs.get? i with
| none, none => .none | a?, b? => some (f a? b?) := by
simp [getElem?_zipWithAll]
set_option linter.deprecated false in
@[deprecated getElem?_zipWithAll (since := "2024-06-07")] abbrev zipWithAll_get? := @get?_zipWithAll
theorem head?_zipWithAll {f : Option α Option β γ} :
(zipWithAll f as bs).head? = match as.head?, bs.head? with
| none, none => .none | a?, b? => some (f a? b?) := by
simp [head?_eq_getElem?, getElem?_zipWithAll]
theorem head_zipWithAll {f : Option α Option β γ} (h) :
(zipWithAll f as bs).head h = f as.head? bs.head? := by
apply Option.some.inj
rw [ head?_eq_head, head?_zipWithAll]
split <;> simp_all
theorem zipWithAll_map {μ} (f : Option γ Option δ μ) (g : α γ) (h : β δ) (l₁ : List α) (l₂ : List β) :
zipWithAll f (l₁.map g) (l₂.map h) = zipWithAll (fun a b => f (g <$> a) (h <$> b)) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWithAll_map_left (l₁ : List α) (l₂ : List β) (f : α α') (g : Option α' Option β γ) :
zipWithAll g (l₁.map f) l₂ = zipWithAll (fun a b => g (f <$> a) b) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem zipWithAll_map_right (l₁ : List α) (l₂ : List β) (f : β β') (g : Option α Option β' γ) :
zipWithAll g l₁ (l₂.map f) = zipWithAll (fun a b => g a (f <$> b)) l₁ l₂ := by
induction l₁ generalizing l₂ <;> cases l₂ <;> simp_all
theorem map_zipWithAll {δ : Type _} (f : α β) (g : Option γ Option δ α) (l : List γ) (l' : List δ) :
map f (zipWithAll g l l') = zipWithAll (fun x y => f (g x y)) l l' := by
induction l generalizing l' with
| nil => simp
| cons hd tl hl =>
cases l' <;> simp_all
@[simp] theorem zipWithAll_replicate {a : α} {b : β} {n : Nat} :
zipWithAll f (replicate n a) (replicate n b) = replicate n (f a b) := by
induction n with
| zero => rfl
| succ n ih => simp [replicate_succ, ih]
/-! ### unzip -/
@[simp] theorem unzip_fst : (unzip l).fst = l.map Prod.fst := by
induction l <;> simp_all
@[simp] theorem unzip_snd : (unzip l).snd = l.map Prod.snd := by
induction l <;> simp_all
@[deprecated unzip_fst (since := "2024-07-28")] abbrev unzip_left := @unzip_fst
@[deprecated unzip_snd (since := "2024-07-28")] abbrev unzip_right := @unzip_snd
theorem unzip_eq_map : l : List (α × β), unzip l = (l.map Prod.fst, l.map Prod.snd)
| [] => rfl
| (a, b) :: l => by simp only [unzip_cons, map_cons, unzip_eq_map l]
theorem zip_unzip : l : List (α × β), zip (unzip l).1 (unzip l).2 = l
| [] => rfl
| (a, b) :: l => by simp only [unzip_cons, zip_cons_cons, zip_unzip l]
theorem unzip_zip_left :
{l₁ : List α} {l₂ : List β}, length l₁ length l₂ (unzip (zip l₁ l₂)).1 = l₁
| [], l₂, _ => rfl
| l₁, [], h => by rw [eq_nil_of_length_eq_zero (Nat.eq_zero_of_le_zero h)]; rfl
| a :: l₁, b :: l₂, h => by
simp only [zip_cons_cons, unzip_cons, unzip_zip_left (le_of_succ_le_succ h)]
theorem unzip_zip_right :
{l₁ : List α} {l₂ : List β}, length l₂ length l₁ (unzip (zip l₁ l₂)).2 = l₂
| [], l₂, _ => by simp_all
| l₁, [], _ => by simp
| a :: l₁, b :: l₂, h => by
simp only [zip_cons_cons, unzip_cons, unzip_zip_right (le_of_succ_le_succ h)]
theorem unzip_zip {l₁ : List α} {l₂ : List β} (h : length l₁ = length l₂) :
unzip (zip l₁ l₂) = (l₁, l₂) := by
ext
· rw [unzip_zip_left (Nat.le_of_eq h)]
· rw [unzip_zip_right (Nat.le_of_eq h.symm)]
theorem zip_of_prod {l : List α} {l' : List β} {lp : List (α × β)} (hl : lp.map Prod.fst = l)
(hr : lp.map Prod.snd = l') : lp = l.zip l' := by
rw [ hl, hr, zip_unzip lp, unzip_fst, unzip_snd, zip_unzip, zip_unzip]
@[simp] theorem unzip_replicate {n : Nat} {a : α} {b : β} :
unzip (replicate n (a, b)) = (replicate n a, replicate n b) := by
ext1 <;> simp

View File

@@ -102,6 +102,13 @@ def blt (a b : Nat) : Bool :=
attribute [simp] Nat.zero_le
attribute [simp] Nat.not_lt_zero
theorem and_forall_add_one {p : Nat Prop} : p 0 ( n, p (n + 1)) n, p n :=
fun h n => Nat.casesOn n h.1 h.2, fun h => h _, fun _ => h _
theorem or_exists_add_one : p 0 (Exists fun n => p (n + 1)) Exists p :=
fun h => h.elim (fun h0 => 0, h0) fun n, hn => n + 1, hn,
fun n, h => match n with | 0 => Or.inl h | n+1 => Or.inr n, h
/-! # Helper "packing" theorems -/
@[simp] theorem zero_eq : Nat.zero = 0 := rfl
@@ -388,11 +395,11 @@ theorem le_or_eq_of_le_succ {m n : Nat} (h : m ≤ succ n) : m ≤ n m = suc
theorem le_or_eq_of_le_add_one {m n : Nat} (h : m n + 1) : m n m = n + 1 :=
le_or_eq_of_le_succ h
theorem le_add_right : (n k : Nat), n n + k
@[simp] theorem le_add_right : (n k : Nat), n n + k
| n, 0 => Nat.le_refl n
| n, k+1 => le_succ_of_le (le_add_right n k)
theorem le_add_left (n m : Nat): n m + n :=
@[simp] theorem le_add_left (n m : Nat): n m + n :=
Nat.add_comm n m le_add_right n m
theorem le_of_add_right_le {n m k : Nat} (h : n + k m) : n m :=
@@ -528,7 +535,7 @@ protected theorem le_of_add_le_add_right {a b c : Nat} : a + b ≤ c + b → a
rw [Nat.add_comm _ b, Nat.add_comm _ b]
apply Nat.le_of_add_le_add_left
protected theorem add_le_add_iff_right {n : Nat} : m + n k + n m k :=
@[simp] protected theorem add_le_add_iff_right {n : Nat} : m + n k + n m k :=
Nat.le_of_add_le_add_right, fun h => Nat.add_le_add_right h _
/-! ### le/lt -/
@@ -1080,6 +1087,10 @@ protected theorem sub_eq_iff_eq_add {c : Nat} (h : b ≤ a) : a - b = c ↔ a =
protected theorem sub_eq_iff_eq_add' {c : Nat} (h : b a) : a - b = c a = b + c := by
rw [Nat.add_comm, Nat.sub_eq_iff_eq_add h]
protected theorem sub_one_sub_lt_of_lt (h : a < b) : b - 1 - a < b := by
rw [ Nat.sub_add_eq]
exact sub_lt (zero_lt_of_lt h) (Nat.lt_add_right a Nat.one_pos)
/-! ## Mul sub distrib -/
theorem pred_mul (n m : Nat) : pred n * m = n * m - m := by

View File

@@ -265,8 +265,8 @@ theorem testBit_two_pow_add_gt {i j : Nat} (j_lt_i : j < i) (x : Nat) :
have x_eq : x = y + 2^j := Nat.eq_add_of_sub_eq x_ge_j y_eq
simp only [Nat.two_pow_pos, x_eq, Nat.le_add_left, true_and, ite_true]
have y_lt_x : y < x := by
simp [x_eq]
exact Nat.lt_add_of_pos_right (Nat.two_pow_pos j)
simp only [x_eq, Nat.lt_add_right_iff_pos]
exact Nat.two_pow_pos j
simp only [hyp y y_lt_x]
if i_lt_j : i < j then
rw [Nat.add_comm _ (2^_), testBit_two_pow_add_gt i_lt_j]

View File

@@ -203,6 +203,10 @@ theorem mod_add_div (m k : Nat) : m % k + k * (m / k) = m := by
| base x y h => simp [h]
| ind x y h IH => simp [h]; rw [Nat.mul_succ, Nat.add_assoc, IH, Nat.sub_add_cancel h.2]
theorem mod_def (m k : Nat) : m % k = m - k * (m / k) := by
rw [Nat.sub_eq_of_eq_add]
apply (Nat.mod_add_div _ _).symm
@[simp] protected theorem div_one (n : Nat) : n / 1 = n := by
have := mod_add_div n 1
rwa [mod_one, Nat.zero_add, Nat.one_mul] at this

View File

@@ -46,6 +46,9 @@ theorem gcd_succ (x y : Nat) : gcd (succ x) y = gcd (y % succ x) (succ x) := by
theorem gcd_add_one (x y : Nat) : gcd (x + 1) y = gcd (y % (x + 1)) (x + 1) := by
rw [gcd]; rfl
theorem gcd_def (x y : Nat) : gcd x y = if x = 0 then y else gcd (y % x) x := by
cases x <;> simp [Nat.gcd_add_one]
@[simp] theorem gcd_one_left (n : Nat) : gcd 1 n = 1 := by
rw [gcd_succ, mod_one]
rfl

View File

@@ -19,6 +19,14 @@ and later these lemmas should be organised into other files more systematically.
-/
namespace Nat
@[deprecated and_forall_add_one (since := "2024-07-30")] abbrev and_forall_succ := @and_forall_add_one
@[deprecated or_exists_add_one (since := "2024-07-30")] abbrev or_exists_succ := @or_exists_add_one
@[simp] theorem exists_ne_zero {P : Nat Prop} : ( n, ¬ n = 0 P n) n, P (n + 1) :=
fun n, h, w => by cases n with | zero => simp at h | succ n => exact n, w,
fun n, w => n + 1, by simp, w
/-! ## add -/
protected theorem add_add_add_comm (a b c d : Nat) : (a + b) + (c + d) = (a + c) + (b + d) := by
@@ -36,13 +44,13 @@ protected theorem eq_zero_of_add_eq_zero_right (h : n + m = 0) : n = 0 :=
protected theorem add_eq_zero_iff : n + m = 0 n = 0 m = 0 :=
Nat.eq_zero_of_add_eq_zero, fun h₁, h₂ => h₂.symm h₁
protected theorem add_left_cancel_iff {n : Nat} : n + m = n + k m = k :=
@[simp] protected theorem add_left_cancel_iff {n : Nat} : n + m = n + k m = k :=
Nat.add_left_cancel, fun | rfl => rfl
protected theorem add_right_cancel_iff {n : Nat} : m + n = k + n m = k :=
@[simp] protected theorem add_right_cancel_iff {n : Nat} : m + n = k + n m = k :=
Nat.add_right_cancel, fun | rfl => rfl
protected theorem add_le_add_iff_left {n : Nat} : n + m n + k m k :=
@[simp] protected theorem add_le_add_iff_left {n : Nat} : n + m n + k m k :=
Nat.le_of_add_le_add_left, fun h => Nat.add_le_add_left h _
protected theorem lt_of_add_lt_add_right : {n : Nat}, k + n < m + n k < m
@@ -52,10 +60,10 @@ protected theorem lt_of_add_lt_add_right : ∀ {n : Nat}, k + n < m + n → k <
protected theorem lt_of_add_lt_add_left {n : Nat} : n + k < n + m k < m := by
rw [Nat.add_comm n, Nat.add_comm n]; exact Nat.lt_of_add_lt_add_right
protected theorem add_lt_add_iff_left {k n m : Nat} : k + n < k + m n < m :=
@[simp] protected theorem add_lt_add_iff_left {k n m : Nat} : k + n < k + m n < m :=
Nat.lt_of_add_lt_add_left, fun h => Nat.add_lt_add_left h _
protected theorem add_lt_add_iff_right {k n m : Nat} : n + k < m + k n < m :=
@[simp] protected theorem add_lt_add_iff_right {k n m : Nat} : n + k < m + k n < m :=
Nat.lt_of_add_lt_add_right, fun h => Nat.add_lt_add_right h _
protected theorem add_lt_add_of_le_of_lt {a b c d : Nat} (hle : a b) (hlt : c < d) :
@@ -75,10 +83,10 @@ protected theorem pos_of_lt_add_right (h : n < n + k) : 0 < k :=
protected theorem pos_of_lt_add_left : n < k + n 0 < k := by
rw [Nat.add_comm]; exact Nat.pos_of_lt_add_right
protected theorem lt_add_right_iff_pos : n < n + k 0 < k :=
@[simp] protected theorem lt_add_right_iff_pos : n < n + k 0 < k :=
Nat.pos_of_lt_add_right, Nat.lt_add_of_pos_right
protected theorem lt_add_left_iff_pos : n < k + n 0 < k :=
@[simp] protected theorem lt_add_left_iff_pos : n < k + n 0 < k :=
Nat.pos_of_lt_add_left, Nat.lt_add_of_pos_left
protected theorem add_pos_left (h : 0 < m) (n) : 0 < m + n :=

View File

@@ -173,13 +173,13 @@ instance : LawfulBEq PolyCnstr where
eq_of_beq {a b} h := by
cases a; rename_i eq₁ lhs₁ rhs₁
cases b; rename_i eq₂ lhs₂ rhs₂
have h : eq₁ == eq₂ && lhs₁ == lhs₂ && rhs₁ == rhs₂ := h
have h : eq₁ == eq₂ && (lhs₁ == lhs₂ && rhs₁ == rhs₂) := h
simp at h
have h₁, h₂, h₃ := h
have h₁, h₂, h₃ := h
rw [h₁, h₂, h₃]
rfl {a} := by
cases a; rename_i eq lhs rhs
show (eq == eq && lhs == lhs && rhs == rhs) = true
show (eq == eq && (lhs == lhs && rhs == rhs)) = true
simp [LawfulBEq.rfl]
def PolyCnstr.mul (k : Nat) (c : PolyCnstr) : PolyCnstr :=

View File

@@ -212,6 +212,9 @@ instance (α) [BEq α] [LawfulBEq α] : LawfulBEq (Option α) where
@[simp] theorem all_none : Option.all p none = true := rfl
@[simp] theorem all_some : Option.all p (some x) = p x := rfl
@[simp] theorem any_none : Option.any p none = false := rfl
@[simp] theorem any_some : Option.any p (some x) = p x := rfl
/-- The minimum of two optional values. -/
protected def min [Min α] : Option α Option α Option α
| some x, some y => some (Min.min x y)

View File

@@ -167,6 +167,9 @@ theorem isSome_map {x : Option α} : (f <$> x).isSome = x.isSome := by
@[simp] theorem isSome_map' {x : Option α} : (x.map f).isSome = x.isSome := by
cases x <;> simp
@[simp] theorem isNone_map' {x : Option α} : (x.map f).isNone = x.isNone := by
cases x <;> simp
theorem map_eq_none : f <$> x = none x = none := map_eq_none'
theorem map_eq_bind {x : Option α} : x.map f = x.bind (some f) := by
@@ -190,9 +193,27 @@ theorem comp_map (h : β → γ) (g : α → β) (x : Option α) : x.map (h ∘
theorem mem_map_of_mem (g : α β) (h : a x) : g a Option.map g x := h.symm map_some' ..
@[simp] theorem map_if {f : α β} [Decidable c] :
(if c then some a else none).map f = if c then some (f a) else none := by
split <;> rfl
@[simp] theorem map_dif {f : α β} [Decidable c] {a : c α} :
(if h : c then some (a h) else none).map f = if h : c then some (f (a h)) else none := by
split <;> rfl
@[simp] theorem filter_none (p : α Bool) : none.filter p = none := rfl
theorem filter_some : Option.filter p (some a) = if p a then some a else none := rfl
@[simp] theorem all_guard (p : α Prop) [DecidablePred p] (a : α) :
Option.all q (guard p a) = (!p a || q a) := by
simp only [guard]
split <;> simp_all
@[simp] theorem any_guard (p : α Prop) [DecidablePred p] (a : α) :
Option.any q (guard p a) = (p a && q a) := by
simp only [guard]
split <;> simp_all
theorem bind_map_comm {α β} {x : Option (Option α)} {f : α β} :
x.bind (Option.map f) = (x.map (Option.map f)).bind id := by cases x <;> simp
@@ -240,7 +261,7 @@ theorem liftOrGet_eq_or_eq {f : ααα} (h : ∀ a b, f a b = a f
@[simp] theorem getD_map (f : α β) (x : α) (o : Option α) :
(o.map f).getD (f x) = f (getD o x) := by cases o <;> rfl
section
section choice
attribute [local instance] Classical.propDecidable
@@ -256,7 +277,7 @@ theorem choice_eq {α : Type _} [Subsingleton α] (a : α) : choice α = some a
theorem choice_isSome_iff_nonempty {α : Type _} : (choice α).isSome Nonempty α :=
fun h => (choice α).get h, fun h => by simp only [choice, dif_pos h, isSome_some]
end
end choice
@[simp] theorem toList_some (a : α) : (a : Option α).toList = [a] := rfl
@@ -304,3 +325,51 @@ theorem map_or : f <$> or o o' = (f <$> o).or (f <$> o') := by
theorem map_or' : (or o o').map f = (o.map f).or (o'.map f) := by
cases o <;> rfl
theorem or_of_isSome {o o' : Option α} (h : o.isSome) : o.or o' = o := by
match o, h with
| some _, _ => simp
theorem or_of_isNone {o o' : Option α} (h : o.isNone) : o.or o' = o' := by
match o, h with
| none, _ => simp
section ite
@[simp] theorem isSome_dite {p : Prop} [Decidable p] {b : p β} :
(if h : p then some (b h) else none).isSome = true p := by
split <;> simpa
@[simp] theorem isSome_ite {p : Prop} [Decidable p] :
(if p then some b else none).isSome = true p := by
split <;> simpa
@[simp] theorem isSome_dite' {p : Prop} [Decidable p] {b : ¬ p β} :
(if h : p then none else some (b h)).isSome = true ¬ p := by
split <;> simpa
@[simp] theorem isSome_ite' {p : Prop} [Decidable p] :
(if p then none else some b).isSome = true ¬ p := by
split <;> simpa
@[simp] theorem get_dite {p : Prop} [Decidable p] (b : p β) (w) :
(if h : p then some (b h) else none).get w = b (by simpa using w) := by
split
· simp
· exfalso
simp at w
contradiction
@[simp] theorem get_ite {p : Prop} [Decidable p] (h) :
(if p then some b else none).get h = b := by
simpa using get_dite (p := p) (fun _ => b) (by simpa using h)
@[simp] theorem get_dite' {p : Prop} [Decidable p] (b : ¬ p β) (w) :
(if h : p then none else some (b h)).get w = b (by simpa using w) := by
split
· exfalso
simp at w
contradiction
· simp
@[simp] theorem get_ite' {p : Prop} [Decidable p] (h) :
(if p then none else some b).get h = b := by
simpa using get_dite' (p := p) (fun _ => b) (by simpa using h)
end ite
end Option

View File

@@ -5,9 +5,18 @@ Author: Leonardo de Moura
-/
prelude
import Init.SimpLemmas
import Init.NotationExtra
instance [BEq α] [BEq β] [LawfulBEq α] [LawfulBEq β] : LawfulBEq (α × β) where
eq_of_beq {a b} (h : a.1 == b.1 && a.2 == b.2) := by
cases a; cases b
refine congr (congrArg _ (eq_of_beq ?_)) (eq_of_beq ?_) <;> simp_all
rfl {a} := by cases a; simp [BEq.beq, LawfulBEq.rfl]
@[simp]
protected theorem Prod.forall {p : α × β Prop} : ( x, p x) a b, p (a, b) :=
fun h a b h (a, b), fun h a, b h a b
@[simp]
protected theorem Prod.exists {p : α × β Prop} : ( x, p x) a b, p (a, b) :=
fun a, b, h a, b, h, fun a, b, h a, b, h

View File

@@ -7,7 +7,7 @@ Simple queue implemented using two lists.
Note: this is only a temporary placeholder.
-/
prelude
import Init.Data.List
import Init.Data.Array.Basic
namespace Std

View File

@@ -0,0 +1,27 @@
/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
prelude
import Init.Ext
namespace Subtype
universe u
variable {α : Sort u} {p q : α Prop}
@[ext]
protected theorem ext : {a1 a2 : { x // p x }}, (a1 : α) = (a2 : α) a1 = a2
| _, _, _, _, rfl => rfl
@[simp]
protected theorem «forall» {q : { a // p a } Prop} : ( x, q x) a b, q a, b :=
fun h a b h a, b, fun h a, b h a b
@[simp]
protected theorem «exists» {q : { a // p a } Prop} :
(Exists fun x => q x) Exists fun a => Exists fun b => q a, b :=
fun a, b, h a, b, h, fun a, b, h a, b, h
end Subtype

View File

@@ -47,11 +47,11 @@ protected theorem ne_of_val_ne {a b : $typeName} (h : a.val ≠ b.val) : a ≠ b
open $typeName (ne_of_val_ne) in
protected theorem ne_of_lt {a b : $typeName} (h : a < b) : a b := ne_of_val_ne (Fin.ne_of_lt h)
@[simp] protected theorem zero_toNat : (0 : $typeName).toNat = 0 := Nat.zero_mod _
@[simp] protected theorem mod_toNat (a b : $typeName) : (a % b).toNat = a.toNat % b.toNat := Fin.mod_val ..
@[simp] protected theorem div_toNat (a b : $typeName) : (a / b).toNat = a.toNat / b.toNat := Fin.div_val ..
@[simp] protected theorem sub_toNat_of_le (a b : $typeName) : b a (a - b).toNat = a.toNat - b.toNat := Fin.sub_val_of_le
@[simp] protected theorem modn_toNat (a : $typeName) (b : Nat) : (a.modn b).toNat = a.toNat % b := Fin.modn_val ..
@[simp] protected theorem toNat_zero : (0 : $typeName).toNat = 0 := Nat.zero_mod _
@[simp] protected theorem toNat_mod (a b : $typeName) : (a % b).toNat = a.toNat % b.toNat := Fin.mod_val ..
@[simp] protected theorem toNat_div (a b : $typeName) : (a / b).toNat = a.toNat / b.toNat := Fin.div_val ..
@[simp] protected theorem toNat_sub_of_le (a b : $typeName) : b a (a - b).toNat = a.toNat - b.toNat := Fin.sub_val_of_le
@[simp] protected theorem toNat_modn (a : $typeName) (b : Nat) : (a.modn b).toNat = a.toNat % b := Fin.modn_val ..
protected theorem modn_lt {m : Nat} : (u : $typeName), m > 0 toNat (u % m) < m
| u, h => Fin.modn_lt u h
open $typeName (modn_lt) in
@@ -69,3 +69,28 @@ declare_uint_theorems UInt16
declare_uint_theorems UInt32
declare_uint_theorems UInt64
declare_uint_theorems USize
@[deprecated (since := "2024-06-23")] protected abbrev UInt8.zero_toNat := @UInt8.toNat_zero
@[deprecated (since := "2024-06-23")] protected abbrev UInt8.div_toNat := @UInt8.toNat_div
@[deprecated (since := "2024-06-23")] protected abbrev UInt8.mod_toNat := @UInt8.toNat_mod
@[deprecated (since := "2024-06-23")] protected abbrev UInt8.modn_toNat := @UInt8.toNat_modn
@[deprecated (since := "2024-06-23")] protected abbrev UInt16.zero_toNat := @UInt16.toNat_zero
@[deprecated (since := "2024-06-23")] protected abbrev UInt16.div_toNat := @UInt16.toNat_div
@[deprecated (since := "2024-06-23")] protected abbrev UInt16.mod_toNat := @UInt16.toNat_mod
@[deprecated (since := "2024-06-23")] protected abbrev UInt16.modn_toNat := @UInt16.toNat_modn
@[deprecated (since := "2024-06-23")] protected abbrev UInt32.zero_toNat := @UInt32.toNat_zero
@[deprecated (since := "2024-06-23")] protected abbrev UInt32.div_toNat := @UInt32.toNat_div
@[deprecated (since := "2024-06-23")] protected abbrev UInt32.mod_toNat := @UInt32.toNat_mod
@[deprecated (since := "2024-06-23")] protected abbrev UInt32.modn_toNat := @UInt32.toNat_modn
@[deprecated (since := "2024-06-23")] protected abbrev UInt64.zero_toNat := @UInt64.toNat_zero
@[deprecated (since := "2024-06-23")] protected abbrev UInt64.div_toNat := @UInt64.toNat_div
@[deprecated (since := "2024-06-23")] protected abbrev UInt64.mod_toNat := @UInt64.toNat_mod
@[deprecated (since := "2024-06-23")] protected abbrev UInt64.modn_toNat := @UInt64.toNat_modn
@[deprecated (since := "2024-06-23")] protected abbrev USize.zero_toNat := @USize.toNat_zero
@[deprecated (since := "2024-06-23")] protected abbrev USize.div_toNat := @USize.toNat_div
@[deprecated (since := "2024-06-23")] protected abbrev USize.mod_toNat := @USize.toNat_mod
@[deprecated (since := "2024-06-23")] protected abbrev USize.modn_toNat := @USize.toNat_modn

View File

@@ -78,7 +78,10 @@ end Elab.Tactic.Ext
end Lean
attribute [ext] Prod PProd Sigma PSigma
attribute [ext] funext propext Subtype.eq
attribute [ext] funext propext Subtype.eq Array.ext
@[ext] protected theorem PUnit.ext (x y : PUnit) : x = y := rfl
protected theorem Unit.ext (x y : Unit) : x = y := rfl
@[ext] protected theorem Thunk.ext : {a b : Thunk α} a.get = b.get a = b
| {..}, {..}, heq => congrArg _ <| funext fun _ => heq

View File

@@ -399,9 +399,16 @@ def setTailInfo (stx : Syntax) (info : SourceInfo) : Syntax :=
| some stx => stx
| none => stx
/--
Replaces the trailing whitespace in `stx`, if any, with an empty substring.
The trailing substring's `startPos` and `str` are preserved in order to ensure that the result could
have been produced by the parser, in case any syntax consumers rely on such an assumption.
-/
def unsetTrailing (stx : Syntax) : Syntax :=
match stx.getTailInfo with
| SourceInfo.original lead pos _ endPos => stx.setTailInfo (SourceInfo.original lead pos "".toSubstring endPos)
| SourceInfo.original lead pos trail endPos =>
stx.setTailInfo (SourceInfo.original lead pos { trail with stopPos := trail.startPos } endPos)
| _ => stx
@[specialize] private partial def updateFirst {α} [Inhabited α] (a : Array α) (f : α Option α) (i : Nat) : Option (Array α) :=

View File

@@ -104,6 +104,11 @@ structure Config where
That is, given a local context containing entry `x : t := e`, the free variable `x` reduces to `e`.
-/
zetaDelta : Bool := false
/--
When `index` (default : `true`) is `false`, `simp` will only use the root symbol
to find candidate `simp` theorems. It approximates Lean 3 `simp` behavior.
-/
index : Bool := true
deriving Inhabited, BEq
end DSimp
@@ -219,13 +224,13 @@ structure Config where
-/
index : Bool := true
/--
When `true` (default: `false`), `simp` will **not** create a proof for a rewriting rule associated
When `true` (default: `true`), `simp` will **not** create a proof for a rewriting rule associated
with an `rfl`-theorem.
Rewriting rules are provided by users by annotating theorems with the attribute `@[simp]`.
If the proof of the theorem is just `rfl` (reflexivity), and `implicitDefEqProofs := true`, `simp`
will **not** create a proof term which is an application of the annotated theorem.
-/
implicitDefEqProofs : Bool := false
implicitDefEqProofs : Bool := true
deriving Inhabited, BEq
-- Configuration object for `simp_all`

View File

@@ -704,6 +704,17 @@ syntax (name := checkSimp) "#check_simp " term "~>" term : command
-/
syntax (name := checkSimpFailure) "#check_simp " term "!~>" : command
/--
Time the elaboration of a command, and print the result (in milliseconds).
Example usage:
```
set_option maxRecDepth 100000 in
#time example : (List.range 500).length = 500 := rfl
```
-/
syntax (name := timeCmd) "#time " command : command
/--
`#discr_tree_key t` prints the discrimination tree keys for a term `t` (or, if it is a single identifier, the type of that constant).
It uses the default configuration for generating keys.

View File

@@ -83,6 +83,10 @@ theorem neg_congr {a b : Int} (h₁ : a = b) : -a = -b := by
theorem lt_of_gt {x y : Int} (h : x > y) : y < x := gt_iff_lt.mp h
theorem le_of_ge {x y : Int} (h : x y) : y x := ge_iff_le.mp h
theorem ofNat_mul_nonneg {a b : Nat} : 0 (a : Int) * b := by
rw [ Int.ofNat_mul]
exact Int.ofNat_zero_le (a * b)
theorem ofNat_sub_eq_zero {b a : Nat} (h : ¬ b a) : ((a - b : Nat) : Int) = 0 :=
Int.ofNat_eq_zero.mpr (Nat.sub_eq_zero_of_le (Nat.le_of_lt (Nat.not_le.mp h)))

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
prelude
import Init.Data.List.Lemmas
import Init.Data.List.Zip
import Init.Data.Int.DivModLemmas
import Init.Data.Nat.Gcd

View File

@@ -320,7 +320,7 @@ Because this is in the `Eq` namespace, if you have a variable `h : a = b`,
For more information: [Equality](https://lean-lang.org/theorem_proving_in_lean4/quantifiers_and_equality.html#equality)
-/
theorem Eq.symm {α : Sort u} {a b : α} (h : Eq a b) : Eq b a :=
@[symm] theorem Eq.symm {α : Sort u} {a b : α} (h : Eq a b) : Eq b a :=
h rfl
/--
@@ -1845,14 +1845,11 @@ theorem Fin.eq_of_val_eq {n} : ∀ {i j : Fin n}, Eq i.val j.val → Eq i j
theorem Fin.val_eq_of_eq {n} {i j : Fin n} (h : Eq i j) : Eq i.val j.val :=
h rfl
theorem Fin.ne_of_val_ne {n} {i j : Fin n} (h : Not (Eq i.val j.val)) : Not (Eq i j) :=
fun h' => absurd (val_eq_of_eq h') h
instance (n : Nat) : DecidableEq (Fin n) :=
fun i j =>
match decEq i.val j.val with
| isTrue h => isTrue (Fin.eq_of_val_eq h)
| isFalse h => isFalse (Fin.ne_of_val_ne h)
| isFalse h => isFalse (fun h' => absurd (Fin.val_eq_of_eq h') h)
instance {n} : LT (Fin n) where
lt a b := LT.lt a.val b.val
@@ -2214,12 +2211,17 @@ def Char.utf8Size (c : Char) : Nat :=
or `none`. In functional programming languages, this type is used to represent
the possibility of failure, or sometimes nullability.
For example, the function `HashMap.find? : HashMap α β → α → Option β` looks up
For example, the function `HashMap.get? : HashMap α β → α → Option β` looks up
a specified key `a : α` inside the map. Because we do not know in advance
whether the key is actually in the map, the return type is `Option β`, where
`none` means the value was not in the map, and `some b` means that the value
was found and `b` is the value retrieved.
The `xs[i]` syntax, which is used to index into collections, has a variant
`xs[i]?` that returns an optional value depending on whether the given index
is valid. For example, if `m : HashMap α β` and `a : α`, then `m[a]?` is
equivalent to `HashMap.get? m a`.
To extract a value from an `Option α`, we use pattern matching:
```
def map (f : α → β) (x : Option α) : Option β :=

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