To complement the other answer, by
is used to start tactic mode proofs (from inside term mode). The most common setting for this is in a top-level theorem such as:
theorem n_plus_m_eq_m_plus_n (n m : Nat) : n + m = m + n := by
-- now in tactic mode
simp -- apply tactic simp
However, there are a number of more subtle situations where :=
is also used to start a proof and by
is needed to write tactics. Without the by
, then one would be giving a term proof instead of a tactic proof.
example : True := by
-- tactic mode
have h0 : True ∨ False -> True := by
-- this is a tactic proof inside a tactic proof
intro h
trivial
-- back to outer tactic block
have h1 : True ∨ False :=
-- this is a term proof (inside a tactic proof)
Or.inl True.intro
-- back to outer tactic block
apply h0
exact h1
example : 1 + 2 = (1 * 1) + (1 + 1) := by
-- in tactic mode, but we didn't have to be since calc also works in term mode
calc
1 + 2 = 1 + (1 + 1) := by
-- in tactic mode inside calc block inside tactic mode
rfl -- rfl the tactic
_ = (1 * 1) + (1 + 1) :=
-- in term mode inside calc block inside tactic mode
rfl -- rfl the term
Advanced
An advanced application is where one goes into tactic mode in the middle of a term proof. It is rare, but one case is where one starts with a match or recursion like the following:
theorem foo : (n : Nat) -> (n = 0 ∨ n > 0)
| 0 => by
-- tactic mode inside recursion
simp
| n+1 =>
-- term mode inside recursion
Or.inr (Nat.zero_lt_succ n)
But here are some more cases where I've seen this sort of thing:
example : ∀ (n : Nat), n + 1 = 1 + n :=
-- term proof
fun n => by
-- entering tactic mode in the middle of a term proof
simp [Nat.add_comm]
This is sort of a dumb example. It would be better to do the whole thing in tactic mode with intro n
at the start.
Sometimes by
is nice for making an inline lemma on the spot.
example : ∀ (n : Nat), n + 0 = 0 + n := by
-- tactic mode
intro n
-- normally put
-- here I'm applying rw with the term (Nat.add_zero n)
rw [Nat.add_zero n]
-- here I'm applying rw with the term `(by simp : (∀ n : Nat, 0 + n = n))`
-- this is going into tactic mode and using `simp` to prove the goal `∀ n : Nat, 0 + n = n`
rw [(by simp : (∀ n : Nat, 0 + n = n))]
Finally, as for the foo
example above, here is one way to write it in tactic mode. Note we don't use by
to give the subproofs. We are still in tactic mode.
theorem foo' (n : Nat) : (n = 0 ∨ n > 0) := by
-- tactic mode
induction n with
| zero =>
-- no `by` required here. Still in tactic mode.
simp
| succ n ih =>
-- no `by` required here. Still in tactic mode.
simp
How to know if you are in tactic mode or term mode?
If you see a goal in the info view, you are likely in tactic mode. If you don't but do see a goal if you write _
, then you are in term mode.
You can also write tactics like done
. If it says "unknown identifier done" then you are in term mode. Otherwise, if it tells you you have unsolved goals, you are in tactic mode.
sorry
,calc
andrfl
are the three I recall in both modes. I think each has a different reason they exist in both. For examplerfl
is just a theorem, and it is instead Lean’s unifier which makes it so powerful to the point that it looks like a tactic. (Also I recall there are subtle differences betweenrfl
andby rfl
.). I thinkcalc
is a macro, and I don’t know aboutsorry
. But in general don’t expect you can get by withoutby
unless you are writing a pure term proof and only using a select few built-in macros like the above ones and the sideways triangle symbol ▸ (\t
). $\endgroup$