Я заметил, что эти два выражения
#eval have amount : Nat := 5 ; amount*4
и
#eval let amount : Nat := 5 ; amount*4
ведут себя одинаково. То же происходит при доказательстве теорем. Они взаимозаменяемы? В чём различие?
Я заметил, что эти два выражения
#eval have amount : Nat := 5 ; amount*4
и
#eval let amount : Nat := 5 ; amount*4
ведут себя одинаково. То же происходит при доказательстве теорем. Они взаимозаменяемы? В чём различие?
I've observed that these two expressions
#eval have amount : Nat := 5 ; amount*4
and
#eval let amount : Nat := 5 ; amount*4
behave identically. This also happens in theorem proofs. Are they interchangeable? How do they differ?
Вот как работает have
Внутренне have h : p := s; t превращается в терм (fun (h : p) => t) s. Иначе говоря, s доказывает p, а t доказывает нужное заключение при допущении h : p, после чего лямбда-абстракция и применение объединяют их.
Вот как работает let
Конструкция let предоставляет более мощное сокращение, поэтому некоторые выражения let a := t1; t2 нельзя представить в виде (fun a => t2) t1. Для упражнения разберитесь, почему определение keepType ниже проходит проверку типов, а loseType не проходит.
def keepType := let chosen := Nat; fun item : chosen => item + 4
/-
def loseType := (fun chosen => fun item : chosen => item + 4) Nat
-/
Here is how have operates
Internally, have h : p := s; t becomes the term (fun (h : p) => t) s. Put differently, s proves p, while t proves the intended conclusion under the assumption h : p, and lambda abstraction followed by application joins them together.
Here is how let operates
The let construct provides more powerful abbreviation, so some expressions let a := t1; t2 have no equivalent (fun a => t2) t1. For practice, work out why the definition called keepType below passes type checking while loseType does not.
def keepType := let chosen := Nat; fun item : chosen => item + 4
/-
def loseType := (fun chosen => fun item : chosen => item + 4) Nat
-/
Разница в том, что let сохраняет определение, а have его отбрасывает.
Например, в Lean 4 этот пример работает с let, но замена на have приводит к ошибке.
example : {item : Nat // item = 0} := let item := 0 ; ⟨item, rfl⟩
Обычно для доказательств используют have, а для всего остального let. В режиме тактик Lean3 с помощью dsimp [x] можно раскрыть определение let x := ..., но соответствующего способа для Lean4 я не знаю.
The distinction is that let retains a definition, whereas have discards it.
For instance, this example accepts let, but substituting have fails in Lean 4.
example : {item : Nat // item = 0} := let item := 0 ; ⟨item, rfl⟩
Typically, proofs use have, while other things use let. Within Lean3's tactic mode, dsimp [x] lets you unfold let x := ..., but I don't know the corresponding approach in Lean4.
Рассмотрим пример с более практическим применением:
def divisibleByTwo (number : Nat) : Prop :=
∃ factor : Nat, number = factor * 2
theorem doubled_half (number : Nat) : divisibleByTwo (number / 2 * 2) :=
let half := number / 2
have : number / 2 * 2 = half * 2 := rfl
Exists.intro half this
theorem doubled_failure (number : Nat) : divisibleByTwo (number / 2 * 2) :=
have half := number / 2
have : number / 2 * 2 = half * 2 := rfl -- error: type mismatch
Exists.intro half this
Первое доказательство работает, а второе завершается ошибкой на выражении rfl. Причина в том, что let подставляет определение number / 2 вместо всех последующих вхождений half. После have компилятор, напротив, сохраняет только то, что half имеет тип Nat, и отбрасывает определение, поэтому rfl не может отождествить number / 2 * 2 с произвольным half / 2.
Поэтому промежуточные утверждения или леммы обычно вводят через have, ведь системе типов нужно свидетельство конкретного утверждения, а не сама процедура доказательства. Напротив, let вводит переменные, определения которых нужны в последующих частях доказательства.
Consider this example with a practical use:
def divisibleByTwo (number : Nat) : Prop :=
∃ factor : Nat, number = factor * 2
theorem doubled_half (number : Nat) : divisibleByTwo (number / 2 * 2) :=
let half := number / 2
have : number / 2 * 2 = half * 2 := rfl
Exists.intro half this
theorem doubled_failure (number : Nat) : divisibleByTwo (number / 2 * 2) :=
have half := number / 2
have : number / 2 * 2 = half * 2 := rfl -- error: type mismatch
Exists.intro half this
The initial proof works, whereas the other one fails at its rfl expression. The reason is that let substitutes the definition number / 2 for all later uses of half. After have, however, the compiler retains only that half has type Nat and discards its definition, leaving rfl unable to identify number / 2 * 2 with an arbitrary half / 2.
Consequently, we normally introduce intermediate propositions or lemmas with have, since the type system needs evidence for the particular proposition without needing the proof's actual procedure. By contrast, let introduces variables when later parts of the proof need their definitions.
Здравствуйте, В пакете, код которого я не могу менять, есть такая иерархия классов: Я хочу добавить удобную обёртку вокруг In…
Создаю Map, в котором каждая запись содержит массив строк. Как одновременно задать его тип и инициализировать одним выражение…
Почему Typescript принимает определение моей функции, хотя оно отличается от сигнатуры интерфейса, но сообщает о проблеме, ко…