In questa sezione

Questa guida mostra come dire, in Lean, che cosa una definizione esportata è dimostrato fare, così che ogni pacchetto generato lo porti con sé: un claim è un teorema registrato con @[lungo_claim], che nomina gli export di cui parla (i suoi soggetti) e le specifiche a cui li mette in relazione. Gli esempi sono i crate sort e session nella directory examples di lungo-rs.

Richiedi la libreria Lean di lungo

Gli attributi sono nella libreria Lean di lungo, il pacchetto Lake lungo nella directory lean di lungo-rs, rilasciato con lungo alla versione di lungo. Richiedilo dal lakefile del progetto:

# lakefile.toml
[[require]]
name = "lungo"
git = "https://github.com/machinefabric/lungo-rs"
rev = "v<versione di lungo>"
subDir = "lean"

(oppure path = "…/lungo-rs/lean" per un checkout), esegui lake update una volta, fai il commit del manifest e scrivi import Lungo dove dichiari i claim. Una libreria di una versione di schema diversa da quella del lungo con cui costruisci viene rifiutata (LNG0709). Gli attributi aggiungono solo record: il programma compilato è lo stesso con o senza.

Marca le specifiche

Una specifica è la dichiarazione a cui un claim mette in relazione i suoi soggetti: un contratto, un modello, una macchina a stati, un protocollo. Dalle @[lungo_spec "<tipo>"]:

import Lungo

namespace Sorting

open Lungo.Spec

/-- Numbers in ascending order: the output is ordered and a permutation of the input. -/
@[lungo_spec "lungo.contract"]
noncomputable def SortsAscending : Contract (List Nat) (List Nat) where
  pre _ := True
  post xs ys := ys.Pairwise (fun a b => decide (a ≤ b)) ∧ ys.Perm xs

I tipi di lungo sono lungo.relation, lungo.contract, lungo.model, lungo.state, lungo.protocol e lungo.property; qualsiasi altro spazio dei nomi appartiene a un pacchetto (acme.cost-model), e viene trasportato senza interpretarlo. Gli strumenti della libreria per scrivere specifiche sono Lungo.Spec.Contract, Lungo.Spec.BehavioralModel, Lungo.Spec.StateSpec, Lungo.Trace.Protocol e Lungo.Monitor (vedi la libreria Lean di lungo).

Rendi noncomputable una specifica soltanto logica: un contratto o un protocollo non ha nulla da eseguire, e una definizione calcolabile dei moduli radice viene esportata come ogni altra. Un modello eseguibile (un’implementazione di riferimento) può restare calcolabile ed essere esportato.

Dichiara il claim

Dimostra il teorema, poi registralo con la relazione, i soggetti dopo subject e le specifiche dopo spec:

def sort (xs : List Nat) : List Nat := xs.mergeSort fun a b => decide (a ≤ b)

@[lungo_claim "lungo.satisfies" subject sort spec SortsAscending]
theorem sort_satisfies : Satisfies sort SortsAscending := by
  intro xs _
  refine ⟨List.pairwise_mergeSort ?_ ?_ xs, List.mergeSort_perm xs _⟩
  · intro a b c hab hbc; simp only [decide_eq_true_eq] at *; omega
  · intro a b; simp only [Bool.or_eq_true, decide_eq_true_eq]; omega

L’attributo controlla che la dichiarazione sia un teorema, che ogni soggetto sia una definizione (non un teorema) menzionata dal suo enunciato, che ogni specifica abbia @[lungo_spec] e sia menzionata e, per le relazioni di lungo, che l’enunciato abbia la forma della relazione (dopo i suoi ∀ iniziali):

Relazione Enunciato
lungo.decides f … = true ↔ P …, con il lato sinistro che menziona ogni soggetto
lungo.equals lhs = rhs, con lhs che menziona ogni soggetto
lungo.roundtrip lhs = rhs, con lhs che menziona ogni soggetto e rhs nessuno, come decode (encode x) = some x
lungo.satisfies Lungo.Spec.Satisfies f c o Lungo.Spec.SatisfiesExcept f c
lungo.refines Lungo.Spec.Refines impl spec
lungo.preserves Lungo.Spec.StateSpec.Preserves s exec o ….Implements s exec
lungo.monitors Lungo.Monitor.Sound m p
lungo.law qualsiasi enunciato che menzioni ogni soggetto

Una relazione in un altro spazio dei nomi (acme.cost-bound) appartiene a un pacchetto: la sua forma non viene controllata. Ciò che l’attributo rifiuta è un errore in Lean; lungo controlla di nuovo i record che legge, quindi un record scritto senza l’attributo è soggetto alle stesse regole (LNG0701–LNG0705).

L’esempio session dichiara che la sua funzione di passo implementa una macchina a stati dichiarativa:

@[lungo_spec "lungo.state"]
noncomputable def Spec : StateSpec Sess Op Bool where
  init s := s = { isOpen := false, count := 0 }
  invariant _ := True
  step s op accepted s' := …

@[lungo_claim "lungo.preserves" subject step spec Spec]
theorem step_implements : Spec.Implements step := by …

Rendi esplicite le assunzioni

Quando un claim vale solo se l’host si comporta bene (il suo orologio avanza, il suo archivio conserva ciò che riceve), dichiaralo come assunzione (@[lungo_assumption], vedi come dichiarare facility e assunzioni) e prendila come ipotesi del teorema:

@[lungo_claim "lungo.roundtrip" subject toTicks toSeconds]
theorem toSeconds_toTicks (h : Ticks) (seconds : Nat) : toSeconds (toTicks seconds) = seconds :=
  Nat.mul_div_cancel seconds h

Il claim resta dimostrato; viene riportato come condizionato a Timing.Ticks, in ogni pacchetto. Un’assunzione è una proposizione, mai un assioma, quindi una dimostrazione può usarla solo come ipotesi, dove il rapporto la vede.

Richiedi i claim nella build

Fai fallire la build quando un export non ha un claim dimostrato:

// build.rs
fn main() -> lungo_build::Result<()> {
    lungo_build::configure().require_claims("Sorting.sort").require_claims("Sorting.rank").compile_lean("lean")
}
# lungo.toml
[assurance]
require-claims = ["Sorting.sort", "Sorting.rank"]

Un percorso seleziona l’export che nomina e ogni export al suo interno come spazio dei nomi; "." seleziona ogni export. Un export selezionato che non è soggetto di alcun claim dimostrato fa fallire la build con LNG0602; un percorso che non seleziona nulla è un errore di configurazione.

La politica di fiducia si applica anche all’evidenza: un claim il cui teorema dipende da sorry fa fallire la build sotto deny_sorry (attivo per impostazione predefinita) con LNG0601, e deny_axioms rifiuta l’evidenza che poggia su assiomi non standard. Con deny_sorry(false) un claim del genere è riportato come incomplete, e non conta come dimostrato per require-claims. forbid-assumptions rifiuta i claim che poggiano su un’assunzione (LNG0603).

Tieni le leggi fuori dal programma

I teoremi sugli export possono stare in un modulo che il programma non importa, come un modulo Laws che importa il programma. Nominalo come modulo di garanzia: lungo lo carica per i suoi record e non lo compila mai nel programma.

# lungo.toml
[lean]
assurance-modules = ["Formal.Laws"]

(Builder::assurance_module("Formal.Laws"), oppure --assurance-module Formal.Laws). Un modulo radice non può essere anche un modulo di garanzia: i suoi record vengono letti comunque. Un programma che avrebbe bisogno di un valore calcolato solo dall’inizializzazione di un modulo di garanzia viene rifiutato (LNG0710).

Rileggi i claim

lungo assurance riporta per ogni export i suoi claim, ciò che le sue dimostrazioni non coprono e ciò che i suoi claim assumono:

$ lungo assurance Sorting.rank
program sorting: Lean 4.34.1 (…), lungo 1.88.0, assurance schema 1

Sorting.rank
  == Claims ==
    [proved] lungo.satisfies Sorting.rank_satisfies (of Sorting.RanksByScore)
    [proved] lungo.law Sorting.rank_stable
  == Trust ==
    (none)
  == Assumptions ==
    (none)
  == Facilities ==
    (none)

--format json lo scrive per gli strumenti; --claim-kind, --facility, --assumption e --trust-issue selezionano gli export; --policy controlla le politiche ed esce con stato 2 in caso di violazione (vedi il comando lungo). lungo check conta i claim, e lungo inspect <dichiarazione> li mostra per una dichiarazione.

Ogni pacchetto generato porta gli stessi record come assurance.json (vedi il documento di garanzia), e li legge in modo nativo:

Linguaggio Il documento I claim di un export
Rust formal::__meta::assurance() formal::__meta::declaration("Formal.step").unwrap().assurance.claims
Go pkg.Assurance() pkg.Assurance().Export("Sorting.rank").Claims
Python pkg.ASSURANCE pkg.ASSURANCE.export("Sorting.rank").claims
Swift assurance (e assuranceJSON) assurance.export("Sorting.rank")?.claims
TypeScript ASSURANCE ASSURANCE.exports.find((e) => e.name === "Sorting.rank").claims
C <id>_assurance_json() (il testo JSON)
let claim = sort::__meta::assurance().claim("Sorting.sort_satisfies").unwrap();
assert_eq!(claim.status, lungo::ClaimStatus::Proved);
assert_eq!(claim.specifications, ["Sorting.SortsAscending"]);