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