This is the first community release of Lean 3.
Features
-
Add
native.floatfor using floating point values within meta functions. -
Add
reflect (p : parser α) [r : reflectable p] : parser exprdef for parsing rawexprs. (#33) -
Add
tactic.unsafe.type_contextandlocal_contextfor directly interacting with Lean's type context.This means that metavariables and local constants can be directly declared and assigned. (#69)
-
Docstrings are now supported on inductive constructors. (#61)
-
Add
environment.add_defn_eqnsto programmatically create a definition that uses the equation compiler. (#26) -
Add
environment.add_ginductiveto give tactics access to the utility lemmas associated with inductive type declaration. (#3) -
Improve file API. Add support for UNIX sockets. (#20, #31)
-
Add
lean.parser.itactic, a tactic block parser. (#19) -
Add
interactive.executor, a class to implement custom tactic monads inbegin ... endblocks. (#10) -
Add
exprserialization/deserialization functions. (#6) -
Improve and add numerous docstrings.
-
Module documentation is now stored in .olean files to allow documentation to be automatically generated. (#54)
-
Documentation of all imported modules is now exposed via the
olean_doc_stringstactic. (#81)
Bug Fixes
- build: fix emscripten build in travis (#68)
- CMakeLists.txt: GCC 9.1.0 miscompiles certain if statements #63
- tactic/revert_tactic: disallow duplicates (#56)
- level: give level.instantiate correct type (#46)
- documentation: The closing code fence must match opening fence. (#40)
- emscripten: fix FS issue: don't mkdir in docker (#39)
- emscripten: fix emscripten build (#17)
- tactic/case:
casefails when used withlet(#32) - tactic/revert_lst: check that the provided expressions are variables (#12)
- init/algebra/field: repeated instance (#8)
- Add an additional check to
type_context_old::mk_class_instance_at(lctx,type)to fix issue #55. (#79)
Changes
- Notation removed:
=<<,>=>,<=< transferandrelatornamespaces removed.
Patch release
Features
leanpkg: Allow specifying a branch to use forleanpkg upgrade(#1981)
Changes
-
Fix the definition of
list.lt -
Make
leanpkgwork when installed in a path containing spaces -
io: Encode/decode UTF-8 for text-mode streams -
Remove
coinductivepredicates andtransfer. To be moved to mathlib instead. -
Windows: Ignore file changes that only changed line endings
Bugfix release: Fix a regression concerning type ascriptions in function position
Features
-
Implement RFC #1820
-
Add
string.iteratorabstraction for traversing strings. The VM contains an efficient implementation of this type. -
Add support for non-ASCII char literals. Example:
'α'. -
Unicode escape characters in string and char literals. For example,
'\u03B1'is equivalent to'α'. -
Predictable runtime cost model for recursive functions. The equation compiler uses different techniques for converting recursive equations into recursors and/or well-founded fixed points. The new approach used in the code generator ignores these encoding tricks when producing byte code. So, the runtime cost model is identical to the one in regular strict functional languages.
-
Add
d_array n α(array type where value type may depend on index), where (α : fin n → Type u). -
Add instance for
decidable_eq (d_array n α)anddecidable_eq (array n α). The new instance is more efficient than the one in mathlib because it doesn't convert the array into a list. -
Add aliasing pattern syntax
id@pat, which introduces the nameidfor the value matched by the patternpat. -
Add alternative syntax
{..., ..s}for the structure update{s with ...}. Multiple fallback sources can be given:{..., ..s, ..t}will fall back to searching a field ins, then int. The last component can also be.., which will replace any missing fields with a placeholder. The old notation will be removed in the future. -
Add support for structure instance notation
{...}in patterns. Use..to ignore unmatched fields. -
Type class
has_equivfor≈notation. -
Add
funext ids*tactic for applying the funext lemma. -
Add
iterate n { t }for applying tactictntimes. Remark:iterate { t }appliestuntil it fails. -
Add
with_cases { t }. This tactic appliestto the main goal, and reverts any new hypothesis in the resulting subgoals.with_casesalso enable "goal tagging". Remark:inductionandcasestag goals using constructor names.applyandconstructortag goals using parameter names. Thecasetactic can select goals using tags. -
Add
cases_matching ptactic for applying thecasestactic to a hypothesish : ts.t.tmatches the patternp. Alternative versions:cases_matching* pandcases_matching [p_1, ..., p_n]. Example:cases_matching* [_ ∨ _, _ ∧ _]destructs all conjunctions and disjunctions in the main goal. -
Add
cases_type Itactic for applying thecasestactic to a hypothesish : I ....cases_type! Ionly succeeds when the number of resulting goals is <= 1. Alternative versions:cases_type I_1 ... I_n,cases_type* I,cases_type!* I. Example:cases_type* and ordestructs all conjunctions and disjunctions in the main goal. -
Add
constructor_matching ptactic. It is syntax sugar formatch_target p; constructor. The variantconstructor_matching* pis more efficient thanfocus1 { repeat { match_target p; constructor } }because the patterns are compiled only once. -
injection hnow supports nested and mutually recursive datatypes. -
Display number of goals in the
*Lean Goal*buffer (if number of goals > 1). -
hide id*command for hiding aliases. The following command hides the aliasis_truefordecidable.is_true.hide is_true -
Add
abbreviationdeclaration command.abbreviation d : t := vis equivalent to@[reducible, inline] def d : t := vand a kernel reducibility hint. Before this command, we had to use meta programming for setting the kernel reducibility hint. This was problematic because we could only define abbreviations after the meta programming framework was defined. -
Add "smart unfolding". The idea is to prevent internal compilation details used by the equation compiler to "leak" during unification, tactic execution and reduction. With "smart unfolding", the term
nat.add a (nat.succ b)reduces tonat.succ (nat.add a b)instead ofnat.succ (... incomprehensible mess ...). This feature addresses a problem reported by many users. See issue #1794. The commandset_option type_context.smart_unfolding falsedisables this feature. -
Add support for "auto params" at
simptactic. Example: given@[simp] lemma fprop1 (x : nat) (h : x > 0 . tactic.assumption) : f x = x := ...The simplifier will try to use tactic
assumptionto synthesize parameterh. -
Add interactive
sorrytactic (alias foradmit). -
simpnow reduces equalitiesc_1 ... = c_2 ...tofalseifc_1andc_2are distinct constructors. This feature can be disabled usingsimp {constructor_eq := ff}. -
simpnow reduces equalitiesc a_1 ... a_n = c b_1 ... b_ntoa_1 = b_1 /\ ... /\ a_n = b_nifcis a constructor. This feature can be disabled usingsimp {constructor_eq := ff}. -
substandsubst_varsnow support heterogeneous equalities that are actually homogeneous (i.e.,@heq α a β bwhereαandβare definitionally equal). -
Add interactive
subst_varstactic. -
Add
leanpkg add foo/baras a shorthand forleanpkg add https://github.com/foo/bar. -
Add
leanpkg help <command>. -
Add
[norm]simp set. It contains all lemmas tagged with[simp]plus any lemma tagged with[norm]. These rules are used to produce normal forms and/or reduce the number of constants used in a goal. For example, we plan to add the lemmaf <$> x = x >>= pure ∘ fto[norm]. -
Add
iota_eqn : boolfield tosimp_config.simp {iota_eqn := tt}uses all non trivial equation lemmas generated by equation/pattern-matching compiler. A lemma is considered non trivial if it is not of the formforall x_1 ... x_n, f x_1 ... x_n = tand a proof by reflexivity.simp!is a shorthand forsimp {iota_eqn := tt}. For example, given the goal... |- [1,2].map nat.succ = t,simp!reduces the left-hand-side of the equation to[nat.succ 1, nat.succ 2]. In this example,simp!is equivalent tosimp [list.map]. -
Allow the Script, Double-struck, and Fractur symbols from Mathematical Alphanumeric Symbols: https://unicode.org/charts/PDF/U1D400.pdf to be used as variables Example:
variables 𝓞 : Prop. -
Structure instance notation now allows explicitly setting implicit structure fields
-
Structure instance notation now falls back to type inference for inferring the value of a superclass. This change eliminates the need for most
..source specifiers in instance declarations. -
The
--profileflag will now print cumulative profiling times at the end of execution -
do notation now uses the top-level, overloadable
bindfunction instead ofhas_bind.bind, allowing binds with different type signatures -
Structures fields can now be defined with an implicitness infer annotation and parameters.
class has_pure (f : Type u → Type v) := -- make f implicit (pure {} {α : Type u} : α → f α) -
Add
except_tandreader_tmonad transformers -
Add
monad_{except,reader,state}classes for accessing effects anywhere in a monad stack without the need for explicit lifting. Add analogousmonad_{except,reader,state}_adapterclasses for translating a monad stack into another one with the same shape but different parameter types.
Changes
-
Command
variable [io.interface]is not needed anymore to use theiomonad. It is also easier to add new io primitives. -
Replace
inoutmodifier in type class declarations without_parammodifier. Reason: counterintuitive behavior in the type class resolution procedure. The result could depend on partial information available in theinoutparameter. Now a parameter(R : inout α → β → Prop)should be written as(R : out_param (α → β → Prop))or(R : out_param $ α → β → Prop). Remark: users may define their own notation for declaringout_params. Example:notation `out`:1024 a:0 := out_param aWe did not include this notation in core lib because
outis frequently used to name parameters, local variables, etc. -
casetactic now supports thewith_cases { t }tactic. See entry above aboutwith_cases. The tag and new hypotheses are now separated with:. Example:case pos { t }: execute tactictto goal taggedposcase pos neg { t }: execute tactictto goal taggedpos negcase : x y { t }: execute tactictto main goal after renaming the first two hypotheses produced by precedingwith_case { t' }.case pos neg : x y { t }: execute tactictto goal taggedpos negafter renaming the first two hypotheses produced by precedingwith_case { t' }.
-
cases hnow also tries to clearhwhen performing dependent elimination. -
repeat { t }behavior changed. Now, it appliestto each goal. If the application succeeds, the tactic is applied recursively to all the generated subgoals until it eventually fails. The recursion stops in a subgoal when the tactic has failed to make progress. The previousrepeattactic was renamed toiterate. -
The automatically generated recursor
C.recfor an inductive datatype now usesihto name induction hypotheses instead ofih_1if there is only one. If there is more than one induction hypotheses, the name is generated by concatenatingih_before the constructor field name. For example, for the constructor| node (left right : tree) (val : A) : treeThe induction hypotheses are now named:
ih_leftandih_right. This change only affects tactical proofs where explicit names are not provided toinductionandcasestactics. -
induction handcases htactic use a new approach for naming new hypotheses. If names are not provided by the user, these tactics will create a "base" name by concatenating the input hypothesis name with the constructor field name. If there is only one field, the tactic simply reuses the hypothesis name. The final name is generated by making sure the "base" name is unique. Remarks:- If
his not a hypothesis, then no concatenation is performed. - The old behavior can be obtained by using the following command
set_option tactic.induction.concat_names falseThis change was suggested by Tahina Ramananandro. The idea is to have more robust tactic scripts when helper tactics that destruct many hypotheses automatically are used. Remark: The new
guard_names { t }tactical can be used to generate robust tactic scripts that are not sensitive to naming generation strategies used byt. - If
-
Remove
[simp]attribute from lemmasor.assoc,or.comm,or.left_comm,and.assoc,and.comm,and.left_comm,add_assoc,add_comm,add_left_com,mul_assoc,mul_commandmul_left_comm. These lemmas were being used to "sort" arguments of AC operators: and, or, (+) and (*). This was producing unstable proofs. The old behavior can be retrieved by using the commandslocal attribute [simp] ...orattribute [simp] ...in the affected files. -
stringis now a list of unicode scalar values. Moreover, in the VM, strings are implemented as an UTF-8 encoded array of bytes. -
array α nis now writtenarray n α. Motivation: consistencyd_array n α. -
Move
rb_mapandrb_treeto thenativenamespace. We will later add pure Lean implementations. Useopen nativeto port files. -
apply tbehavior changed when type oftis of the formforall (a_1 : A_1) ... (a_n : A_n), ?m ..., where?mis an unassigned metavariable. In this case,apply tbehaves asapply t _ ... _wheren_have been added, independently of the goal target type. The new behavior is useful when usingapplywith eliminator-like definitions. -
ginduction t with h h1 h2is nowinduction h : t with h1 h2. -
apply_corenow also returns the parameter name associated with new metavariables. -
applynow also returns the new metavariables (and the parameter name associated with them). Even the assigned metavariables are returned. -
by_cases p with h==>by_cases h : p -
leanpkg now always stores .lean package files in a separate
srcdirectory. -
Structure constructor parameters representing superclasses are now marked as instance implicit. Note: Instances using the {...} structure notation should not be affected by this change.
-
The monad laws have been separated into new type classes
is_lawful_{functor,applicative,monad}. -
unitis now an abbreviation ofpunit.{0}
API name changes
monad.has_monad_lift(_t)~>has_monad_lift(_t)monad.map_comp~>comp_mapstate(_t).{read,write}~>{get,put}(general operations defined on anymonad_state)- deleted
monad.monad_transformer - deleted
monad.lift{n}. Usef <$> a1 <*> ... <*> aninstead ofmonad.lift{n} f a1 ... an. - merged
has_mapintofunctor unit_eq(_unit)~>punit_eq(_punit)
Features
-
In addition to user-defined notation parsers introduced in Lean 3.2.0, users may now also define top-level commands in Lean. For an example, see the
coinductivecommand that has been ported to the new model. -
Add new simplifier configuration option
simp_config.single_pass. It is useful for simplification rules that may produce non-termination. Example:simp {single_pass := tt} -
The rewrite tactic has support for equational lemmas. Example: Given a definition
f,rw [f]will try to rewrite the goal using the equational lemmas forf. The tactic fails if none of the equational lemmas can be used. -
Add
simp * at *variant. It acts on all (non dependent propositional) hypotheses. Simplified hypotheses are automatically inserted into the simplification set as additional simplification rules. -
Add
«id»notation that can be used to declare and refer to identifiers containing prohibited characters. For example,a.«b.c»is a two-part identifier with partsaandb.c. -
simptactic now handles lemmas with metavariables. Examplesimp [add_comm _ b]. -
conv { ... }tactic for applying simplification and rewriting steps. In the block{...}, we can use tactics fromconv.interactive. Examples:conv at h in (f _ _) { simp }appliessimpto first subterm matchingf _ _at hypothesish.conv in (_ = _) { to_lhs, whnf }replace the left-hand-side of the equality in target with its weak-head-normal-form.conv at h in (0 + _) { rw [zero_add] }conv { for (f _ _) [1, 3] {rw [h]}, simp }, first executerw[h]to the first and third occurrences of anf-application, and then executesimp.
-
simptactics in interactive mode have a new configuration parameter (discharger : tactic unit) a tactic for discharging subgoals created by the simplifier. If the tactic fails, the simplifier tries to discharge the subgoal by reducing it totrue. Example:simp {discharger := assumption}. -
simpanddsimpcan be used to unfold projection applications when the argument is a type class instance. Example:simp [has_add.add]will replace@has_add.add nat nat.has_add a bwithnat.add a b -
dsimphas several new configuration options to control reduction (e.g.,iota,beta,zeta, ...). -
Non-exhaustive pattern matches now show missing cases.
-
induction enow also works on non-variablee. Unlikeginduction, it will not introduce equalities relatingeto the inductive cases. -
Add notation
#[a, b, c, d]forbin_tree.node (bin_tree.node (bin_tree.leaf a) (bin_tree.leaf b)) (bin_tree.node (bin_tree.leaf c) (bin_tree.leaf d)). There is a coercion frombin_treetolist. The new notation allows to input long sequences efficiently. It also prevents system stack overflows. -
Tactics that accept a location parameter, like
rw thm at h, may now use⊢or the ASCII version|-to select the goal as well as any hypotheses, for examplerw thm at h1 h2 ⊢. -
Add
user_attribute.after_set/before_unsethandlers that can be used for validation as well as side-effecting attributes. -
Field notation can now be used to make recursive calls.
def list.append : list α → list α → list α
| [] l := l
| (h :: s) t := h :: s.append t
-
leanpkg now stores the intended Lean version in the
leanpkg.tomlfile and reports a warning if the version does not match the installed Lean version. -
simpanddsimpcan now unfold let-bindings in the local context. Usedsimp [x]orsimp [x]to unfold the let-bindingx : nat := 3. -
User-defined attributes can now take parameters parsed by a
lean.parser. A[derive]attribute that can derive typeclasses such asdecidable_eqandinhabitedhas been implemented on top of this.
Changes
-
We now have two type classes for converting to string:
has_to_stringandhas_repr. Thehas_to_stringtype class in v3.2.0 is roughly equivalent tohas_repr. For more details, see discussion here. -
Merged
assertandnotetactics and renamed ->have. -
Renamed
posetactic ->let -
assumeis now a real tactic that does not exit tactic mode. -
Modified
applytactic configuration object, and implemented RFC #1342. It now has support forauto_paramandopt_param. The neweapplytactic only creates subgoals for non dependent premises, and it simulates the behavior of theapplytactic in version 3.2.0. -
Add configuration object
rewrite_cfgtorw/rewritetactic. It now has support forauto_paramandopt_param. The new goals are ordered using the same strategies available forapply. -
All
dsimptactics fail if they did not change anything. We can simulate the v3.2.0 usingdsimp {fail_if_unchaged := ff}ortry dsimp. -
dsimpdoes not unfold reducible definitions by default anymore. Example:function.compapplications will not be unfolded by default. We should usedsimp [f]if we want to reduce a reducible definitionf. Another option is to use the new configuration parameterunfold_reducible. Exampledsimp {unfold_reducible := tt} -
All
dunfoldandunfoldtactics fail if they did not unfold anything. We can simulate the v3.2.0 usingunfold f {fail_if_unchaged := ff}ortry {unfold f}. -
dunfold_occswas deleted, the newconvtactical should be used instead. -
unfoldtactic is now implemented on top of thesimptactics. So, we can use it to unfold declarations defined using well founded recursion. Theunfold1variant does not unfold recursively, and it is shorthand forunfold f {single_pass := tt}. Remark: in v3.2.0,unfoldwas just an alias for thedunfoldtactic. -
Deleted
simphandsimp_using_hstactics. We should usesimp [*]instead. -
Use
simp [-h]anddsimp [-h]instead ofsimp without handdsimp without h. Moreover,simp [*, -h]ifhis a hypothesis, we are adding all hypotheses buthas additional simplification lemmas. -
Changed notation
rw [-h]torw [← h]to avoid confusion with the newsimp [-h]notation. The ASCII versionrw [<- h]is also supported. -
rw [t] at *now skips any hypothesis used byt. See discussion here. -
Removed the redundant keywords
take(replace withassume) andsuppose(replace with the new anonymousassume :) -
Universes
max u v + 1andimax u v + 1are now parsed as(max u v) + 1and(imax u v) + 1. -
Merged
generalizeandgeneralize2tactics into newgeneralize id? : expr = idtactic -
standard.leanhas been removed. Any files thatimport standardcan simply remove the line as most things that were once imported by this are now imported by default. -
The type classes for orders have been refactored to combine both the
(<)and(≤)operations. The new classes arepreorder,partial_order, andlinear_order. Thepartial_orderclass corresponds toweak_order,strict_order,order_pair, andstrong_order_pair. Thelinear_orderclass corresponds tolinear_order_pair, andlinear_strong_order_pair. -
injectionandinjectionstactics generate fresh names when user does not provide names. The fresh names are of the formh_<idx>. See discussion here. -
Use
structureto declareand. This change allows us to useh.1andh.2as shorthand forh.leftandh.right. -
Add attribute
[pp_using_anonymous_constructor]toand. Now,and.intro h1 h2is pretty printed as⟨h1, h2⟩. This change is motivated by the previous one. Without it,and.intro h1 h2would be pretty printed as{left := h1, right := h2}. -
User attributes can no longer be set with
set_basic_attribute. You need to useuser_attribute.setnow. -
The Emacs mode has been moved into its own repository and MELPA packages: https://github.com/leanprover/lean-mode
API name changes
list.dropn=>list.droplist.taken=>list.taketactic.dsimpandtactic.dsimp_core=>tactic.dsimp_targettactic.dsimp_at_coreandtactic.dsimp_at=>tactic.dsimp_hyptactic.delta_expr=>tactic.deltatactic.delta=>tactic.delta_targettactic.delta_at=>tactic.delta_hyptactic.simplify_goal=>tactic.simp_targettactic.unfold_projection=>tactic.unfold_projtactic.unfold_projections_core=>tactic.unfold_projstactic.unfold_projections=>tactic.unfold_projs_targettactic.unfold_projections_at=>tactic.unfold_projs_hyptactic.simp_intros_using,tactic.simph_intros_using,tactic.simp_intro_lst_using,tactic.simph_intro_lst_using=>tactic.simp_introstactic.simp_at=>tactic.simp_hyp- deleted
tactic.simp_at_using - deleted
tactic.simph_at
Lean is still evolving rapidly, and we apologize for the resulting instabilities. The lists below summarizes some of the new features and incompatibilities with respect to release 3.1.0.
Features
-
Holes
{! ... !}expressions and (user-defined) hole commands. In Emacs, hole commands are executed using the keybinding C-c SPC. The available commands can be found here. -
The
leanpkgpackage manager now manages projects and dependencies. See the documentation here. Parts of the standard library, like the superposition theorem proversuper, have been moved to their own repositories..projectfiles are no longer needed to useemacswith projects. -
Well-founded recursion is now supported. (Details and examples for this and the next two items will soon appear in Theorem Proving in Lean.)
-
Mutually (non meta) recursive definitions are now supported.
-
Nested and mutual inductive data types are now supported.
-
There is support for coinductive predicates.
-
The
simptactic has been improved and supports more options, like wildcards. Hover oversimpin an editor to see the documentation string (docstring). -
Additional interactive tactics have been added, and can be found here.
-
A
casetactic can now be used to structure proofs by cases and by induction. See here. -
Various data structures, such as hash maps, have been added to the standard library here.
-
There is preliminary support for user-defined parser extensions. More information can be found here, and some examples can be found here.
-
Numerous improvements have been made to the parallel compilation infrastructure and editor interfaces, for example, as described here and here.
-
There is a
transfermethod that can be used to transfer results e.g. to isomorphic structures; see here. -
The monadic hierarchy now includes axioms for monadic classes. (See here.)
-
The tactic notation
tac ; [tac_1, ..., tac_n]has been added. -
The type classes
has_well_founded,has_map,has_seq,has_orelsehave been added. -
Type classes can have input/output parameters. Some examples can be found here.
-
tactic.run_iocan now be used to perform IO in tactics.
Changes
-
Type class instances are not
[reducible]by default anymore. -
Commands that produce output are now preceded by a hash symbol, as in
#check,#print,#evaland#reduce. The#evalcommand calls the bytecode evaluator, and#reducedoes definitional reduction in the kernel. Many instances of alternative syntax likepremiseforvariableandhypothesisforparameterhave been eliminated. See the discussion here. -
The hierarchy of universes is now named
Sort 0,Sort 1,Sort 2, and so on.Propis alternative syntax forSort 0,Typeis alternative syntax forSort 1, andType nis alternative syntax forSort (n + 1). Many general constructors have been specialized from arbitrarySorts toTypein order to simplify elaboration. -
Automatically generate dependent eliminators for inductive predicates.
-
Improve unification hint matcher.
-
Improve unifier. In function applications, explicit arguments are unified before implicit ones. Moreover, more aggresive unfolding is used when processing implicit arguments.
-
Use
universe uinstead ofuniverse variable uto declare a universe variable. -
The syntax
l^.map ffor projections is now deprecated in favor ofl.map f. -
The behavior of the
showtactic in interactive tactic mode has changed. It no longer leaves tactic mode. Also, it can now be used to select a goal other than the current one. -
The
assertvanddefinevtactics have been eliminated in favor ofnoteandpose. -
has_andthentype class is now heterogeneous, -
The encoding of structures has been changed, following the strategy described here. Generic operations like
addandmulare replaced byhas_add.addandhas_mul.mul, respectively. One can provisionally obtain the old encodings withset_option old_structure_cmd true. -
Syntax for quotations in metaprograms has changed. The notation
`(t)elaboratestat definition time and produces an expression. The notation``(t)resolves names at definition time produces a pre-expression, and```(t)resolves names at tactic execution time, and produces a pre-expression. Details can be found in the paper Ebner et al, A Metaprogramming Framework for Formal Verification. -
The types
exprfor expressions andpexprfor pre-expressions have been unified, so thatpexpris defined asexpr ff. See here. -
Handling of the
iomonad has changed. Examples can be found here in the code forleanpkg, which is written in Lean itself.
stateandstate_tare universe polymorphic.
-
option_mapandoption_bindhave been renamed tooption.mapandoption.bind, respectively. -
GCC 7 compatibility
-
Use single quotes for character literals (e.g., 'a').