#scala #implicit #scala-3 #match-types
Вопрос:
Учитывая следующую функцию сложения на уровне типов для чисел Пеано
sealed trait Nat
class O extends Nat
class S[N <: Nat] extends Nat
type plus[a <: Nat, b <: Nat] = a match
case O => b
case S[n] => S[n plus b]
скажем, мы хотим доказать теорему, как
для всех натуральных чисел n, n 0 = n
что, возможно, можно указать следующим образом
type plus_n_0 = [n <: Nat] =>> (n plus O) =:= n
затем, когда дело доходит до предоставления доказательств теоремы, мы можем легко попросить компилятор Scala предоставить доказательства в конкретных случаях
summon[plus_n_O[S[S[O]]]] // ok, 2 0 = 2
но как мы можем спросить Scala , может ли она генерировать доказательства для всех экземпляров [n <: Nat]
, тем самым предоставляя доказательства plus_n_0
?
Комментарии:
1. Если область бесконечна, и при тестировании каждого случая нет никакой связи с каким-либо другим натуральным числом, как вы думаете, вы могли бы заставить компилятор сделать это неявно?
2. Я не понимаю. Вы хотите что-то вроде
given [n <: Nat]: (n plus 0) =:= n
этого ? Разве это не было бы предусмотрено в любом случае, такn plus 0
как всегда оцениваетсяn
как ?3. В зависимости от того , как вы определяете
, один из двух вариантов
0 n = n
сравненияn 0 = n
намного проще, чем другой. Я не проверял, какой из них на самом деле был доказан в учебнике, который вы связали. В любом случае, доказательство Coq в любом случае кажется в основном автоматическим, все скрыто внутриnat
определения и тактики доказательства. Не похоже, что Scala-3 обеспечит что-либо сопоставимое, это не помощник для доказательства. В любом случае, поскольку вы упомянули Coq, я процитировал абзац из первой книги Хотта, за исключением того, что я расширил ее дополнительным свидетелем выполнения, чтобы все это было исполняемым.4. @MarioGalic Я очистил
apply
inNatInductionPrinciple
, оказалось, что компилятор может автоматически предоставить лучшую альтернативу сгенерированным вручную терминам-свидетелям среды выполнения, которые я создал ранее. Я думаю, что это хорошая возможность увидеть на крошечном минимальном примере, для чегоerasedValue
inline match
хорош и: просто посмотрите, насколько лучшеNatInductionPrinciple.apply
сinline match
этим . Это на самом деле довольно круто! Фокус сinline match
этим начинается отсюда .5. @AndreyTyukin Одерский здесь кратко описывает данный механизм в доказательствах теорем.
Ответ №1:
Вот один из возможных подходов, который представляет собой попытку буквального толкования этого пункта:
При доказательстве утверждения
E:N→U
обо всех натуральных числах достаточно доказать его для0
и дляsucc(n)
, предполагая , что оно справедливо дляn
, т. Е. Мы строимez:E(0)
иes:∏(n:N)E(n)→E(succ(n))
.
Вот план того, что было реализовано в приведенном ниже коде:
- Сформулируйте, что значит иметь доказательство утверждения, что «Некоторое свойство
P
справедливо для всех натуральных чисел». Ниже мы будем использоватьtrait Forall[N, P[n <: N]]: inline def apply[n <: N]: P[n]
где подпись
apply
метода-по существу говорит: «для всехn <: N
мы можем генерировать доказательстваP[n]
«.Обратите внимание, что метод объявлен
inline
как . Это один из возможных способов убедиться, что доказательство∀n.P(n)
является конструктивным и исполняемым во время выполнения (однако альтернативные предложения с сгенерированными вручную условиями-свидетелями см. в разделе История редактирования). - Постулируйте какой-то принцип индукции для натуральных чисел. Ниже мы будем использовать следующую формулировку:
If P(0) holds, and whenever P(i) holds, then also P(i 1) holds, then For all `n`, P(n) holds
Я считаю, что такие принципы индукции должны быть возможны с
derive
использованием некоторых средств метапрограммирования. - Напишите доказательства для базового случая и индукционного случая принципа индукции.
???
- Прибыль
Затем код выглядит следующим образом:
sealed trait Nat
class O extends Nat
class S[N <: Nat] extends Nat
type plus[a <: Nat, b <: Nat] <: Nat = a match
case O => b
case S[n] => S[n plus b]
trait Forall[N, P[n <: N]]:
inline def apply[n <: N]: P[n]
trait NatInductionPrinciple[P[n <: Nat]] extends Forall[Nat, P]:
def base: P[O]
def step: [i <: Nat] => (P[i] => P[S[i]])
inline def apply[n <: Nat]: P[n] =
(inline compiletime.erasedValue[n] match
case _: O => base
case _: S[pred] => step(apply[pred])
).asInstanceOf[P[n]]
given liftCoUpperbounded[U, A <: U, B <: U, S[_ <: U]](using ev: A =:= B):
(S[A] =:= S[B]) = ev.liftCo[[X] =>> Any].asInstanceOf[S[A] =:= S[B]]
type NatPlusZeroEqualsNat[n <: Nat] = (n plus O) =:= n
def trivialLemma[i <: Nat]: ((S[i] plus O) =:= S[i plus O]) =
summon[(S[i] plus O) =:= S[i plus O]]
object Proof extends NatInductionPrinciple[NatPlusZeroEqualsNat]:
val base = summon[(O plus O) =:= O]
val step: ([i <: Nat] => NatPlusZeroEqualsNat[i] => NatPlusZeroEqualsNat[S[i]]) =
[i <: Nat] => (p: NatPlusZeroEqualsNat[i]) =>
given previousStep: ((i plus O) =:= i) = p
given liftPreviousStep: (S[i plus O] =:= S[i]) =
liftCoUpperbounded[Nat, i plus O, i, S]
given definitionalEquality: ((S[i] plus O) =:= S[i plus O]) =
trivialLemma[i]
definitionalEquality.andThen(liftPreviousStep)
def demoNat(): Unit = {
println("Running demoNat...")
type two = S[S[O]]
val ev = Proof[two]
val twoInstance: two = new S[S[O]]
println(ev(twoInstance) == twoInstance)
}
Он компилируется, запускается и печатается:
true
это означает, что мы успешно вызвали рекурсивно определенный
метод в исполняемом доказательстве-термине типа two plus O =:= two
.
Некоторые дополнительные комментарии
- Это
trivialLemma
было необходимо для того, чтобыsummon
s внутри другихgiven
s случайно не создавали рекурсивные циклы, что немного раздражает. liftCo
Отдельный метод для был необходим, потому что не допускает конструкторов типов с параметрами типа с верхними ограничениями.S[_ <: U]
=:=.liftCo
compiletime.erasedValue
inline match
это потрясающе! Он автоматически генерирует какие-то вещицы во время выполнения, которые позволяют нам сопоставлять шаблоны для «стертого» типа. До того, как я узнал об этом, я пытался создать соответствующие термины-свидетели вручную, но в этом нет необходимости, это предоставляется бесплатно (см. Историю редактирования для подхода с использованием терминов-свидетелей, созданных вручную).