lungo generate --<language>_out=DIR scrive un pacchetto che contiene il programma (come C,
oppure per TypeScript come WebAssembly) e la sua API nel linguaggio. Si compila con gli
strumenti del linguaggio stesso e gira sul runtime di lungo, che prende dalla release di lungo
(o da una distribuzione locale) esattamente alla versione che lo ha
generato. L’output Rust è descritto in codice generato e in
I tipi Lean in Rust.
Di seguito, l’id del programma è il suo nome come identificatore C (polyglot per un
pacchetto Lake polyglot).
Nomi
Una dichiarazione esportata prende il nome dal suffisso più corto del suo nome Lean con cui
non termina nessun’altra dichiarazione del suo genere: Geometry.Shape.area è area, a meno
che un’altra funzione termini anch’essa con area, nel qual caso entrambe mantengono un
componente in più (shape_area, other_area). Il suffisso viene convertito secondo le
convenzioni del linguaggio (snake_case in C e Python, UpperCamelCase per Go e per tutti i
tipi, lowerCamelCase per i membri Swift e TypeScript) ed escapato dove è una parola chiave.
Una funzione il cui nome risulta uguale a quello di un tipo (o di un costruttore, o di un
descrittore) mantiene un componente in più, come fanno due funzioni che terminano allo stesso
modo; il tipo mantiene il proprio nome. In Go, dove entrambi sono UpperCamelCase in un solo
package, Gate.acquire accanto al tipo Acquire è GateAcquire. Due funzioni che otterrebbero
comunque lo stesso nome (foo e Foo), o due tipi, sono un errore
(LNG0107) che le indica entrambe: rinominane una in Lean.
Tipi Lean
| Lean | C | Go | Python | Swift | TypeScript |
|---|---|---|---|---|---|
Nat |
lungo_value |
*big.Int |
int |
LungoNat |
bigint |
Int |
lungo_value |
*big.Int |
int |
LungoInt |
bigint |
Bool |
lungo_value |
bool |
bool |
Bool |
boolean |
UInt8–UInt32, Int8–Int32 |
lungo_value |
uint8–int32 |
int |
UInt8–Int32 |
number |
UInt64, USize, Int64, ISize |
lungo_value |
uint64, int64 |
int |
UInt64, Int64 |
bigint |
Float, Float32 |
lungo_value |
float64, float32 |
float |
Double, Float |
number |
Char |
lungo_value |
rune |
str di lunghezza 1 |
Unicode.Scalar |
string di un solo code point |
String |
lungo_value |
string |
str |
String |
string |
Unit |
lungo_value |
lungo.Unit |
None |
LungoUnit |
null |
ByteArray |
lungo_value |
[]byte |
bytes |
[UInt8] |
Uint8Array |
FloatArray |
lungo_value |
[]float64 |
list[float] |
[Double] |
number[] |
Option α |
lungo_value |
lungo.Option[A] |
A oppure None |
A? |
A oppure null |
List α, Array α |
lungo_value |
[]A |
list[A] |
[A] |
A[] |
α × β |
lungo_value |
lungo.Pair[A, B] |
tuple[A, B] |
(A, B) |
[A, B] |
Except ε α |
lungo_value |
lungo.Except[E, A] |
lungo_py.Ok / lungo_py.Err |
LungoExcept<E, A> |
{ ok, value } / { ok, error } |
α → β |
lungo_value |
func(A) (B, error) |
callable | (A) throws -> B |
funzione |
| una structure | lungo_value |
struct | dataclass immutabile | struct | oggetto |
| un tipo induttivo | lungo_value |
interfaccia sigillata, una struct per costruttore | classe base, una dataclass immutabile per costruttore | indirect enum |
oggetti etichettati con kind |
| un tipo polimorfo | argomento lungo_type |
tipo generico | classe Generic |
tipo generico | tipo generico |
| un tipo opaco | lungo_value |
struct attorno a lungo.Opaque |
sottoclasse di lungo_py.Opaque |
struct attorno a LungoOpaque |
sottoclasse di Opaque |
| qualsiasi altro tipo | lungo_value |
lungo.Opaque |
lungo_py.Opaque |
LungoOpaque |
Opaque |
I numeri vengono controllati quando entrano in Lean: un Nat negativo, un int32 Go fuori
dall’intervallo di un UInt16, una stringa che non è Unicode valido, un valore nil di un tipo
induttivo vengono rifiutati (vedi errori), mai troncati. Un Option i cui valori
possono essere a loro volta None/null (Option (Option α), Option Unit) è Some(value)
(Python, TypeScript) dove è some. Una structure che contiene sé stessa per valore (tramite
opzioni, coppie o altre structure) è un puntatore in Go e una classe final in Swift. I valori a
cui Lean non può dare un tipo del linguaggio (azioni IO memorizzate come dati, un Type, un
valore di un tipo che il programma non descrive) sono opachi: handle che mantengono vivo il
valore Lean finché non vengono chiusi o raccolti.
Tipi opachi
Un tipo induttivo i cui valori può costruire solo il codice Lean (per esempio uno con una
dimostrazione tra i suoi campi, come una structure fatta di un valore e della dimostrazione che
è ben formato) è opaco: i suoi valori attraversano il confine come handle, che le funzioni
del programma creano e leggono. Resta comunque un tipo a sé: Wf in Go è
struct{ lungo.Opaque }, una sottoclasse di lungo_py.Opaque in Python, una struct in Swift,
una sottoclasse di Opaque in TypeScript, così il valore di un tipo opaco non può essere
passato dove se ne aspetta un altro. I parametri di tipo di un tipo opaco non fanno parte del
suo tipo nel binding. I tipi che non sono induttivi, o che hanno indici, sono semplici valori
opachi.
Impronte del layout
Ogni tipo con nome registra l’impronta del suo layout: <Name>Fingerprint (Go),
<Class>.__lungo_fingerprint__ (Python), <Type>.lungoFingerprint (Swift),
leanTypes["<Lean name>"].fingerprint (TypeScript), <ID>_<TYPE>_FINGERPRINT (C), e
lungo::LeanLayout::FINGERPRINT in Rust. Un pacchetto che usa il tipo di un altro come
tipo extern la controlla prima di essere eseguito.
Una funzione polimorfa riceve per prima cosa un descrittore di tipo per ogni parametro di tipo:
Size(lungo.NatType, tree) (Go), size(lungo_py.NAT, tree) (Python), size(Lungo.nat,
tree) (Swift), p.size(L.NAT, tree) (TypeScript), polyglot_size(nat_type, tree, &result,
&error) (C). Ogni tipo con nome ha una funzione descrittore (TreeType(a), tree_type(a),
Tree.lungoType(a), treeType(a), polyglot_tree_type(a)).
Errori
| Risultato Lean | C (stato) | Go | Python | Swift | TypeScript |
|---|---|---|---|---|---|
| un valore | LUNGO_OK |
(T, nil) |
il valore | il valore | il valore |
IO α che fallisce |
LUNGO_FAILED, LUNGO_ERROR_IO |
*lungo.IOError |
lungo_py.LeanIOError |
LungoIOError |
LeanIOError |
EIO ε α che fallisce |
LUNGO_FAILED, LUNGO_ERROR_VALUE |
*lungo.Error[E] |
lungo_py.LeanError |
LungoError<E> |
LeanError |
| argomenti che Lean non può rappresentare | LUNGO_MALFORMED |
*lungo.MalformedError |
lungo_py.MalformedError |
LungoMalformed |
MalformedError |
Una funzione il cui risultato è Unit non restituisce nulla (solo il suo errore) in Go, Swift
e TypeScript.
Facility
Gli extern che l’host implementa sono le operazioni delle facility del programma, che il suo
codice Lean dichiara (@[lungo_facility "ns.name"], @[lungo_operation C]; vedi
come chiamare codice dell’host da Lean). Ogni facility
è un tipo del linguaggio, che prende il nome dall’ultimo componente del suo identificatore
(polyglot.scaler è Scaler), con un metodo per operazione, e l’host installa
un’implementazione di ciascuna:
| Linguaggio | Una facility | Come si installa | Una chiamata prima che ogni facility sia installata |
|---|---|---|---|
| C | funzioni <id>_implement_<facility>_<operation>(f, ctx, drop) |
ciascuna funzione, prima che il programma si inizializzi | termina il processo, indicando la facility e l’operazione |
| Go | interfaccia Scaler |
SetScaler(impl) |
restituisce *lungo.MissingFacilityError (Facility, Operation) |
| Python | typing.Protocol Scaler |
set_scaler(impl) |
solleva lungo_py.MissingFacilityError (facility, operation) |
| Swift | protocollo <Module>Scaler |
setScaler(_:) |
lancia LungoMissingFacility (facility, operation) |
| TypeScript | interfaccia <Program>Scaler, membro di <Program>Facilities |
load({ facilities: { scaler: … } }) |
load rifiuta con MissingFacilityError (facility, operation) |
Ogni facility dichiarata dai moduli collegati del programma va installata, che una data chiamata la raggiunga o no, così un programma fallisce alla sua prima chiamata invece che nel mezzo di una. Il commento di documentazione di una facility indica le assunzioni che i claim del programma fanno sull’implementazione dell’host; il programma non può verificarle. Un metodo il cui tipo Lean è puro deve rispondere allo stesso modo agli stessi argomenti: Lean può valutare una chiamata una sola volta e conservarne il valore.
Export asincroni
Un export che restituisce Lungo.Async.Program Op α chiede all’host, mentre viene eseguito, di
compiere operazioni della facility asincrona su Op, e attende ogni risposta. È una funzione
asincrona del linguaggio che prende per primo un handler: un oggetto con un metodo per ogni
costruttore di Op, che prende i campi del costruttore e restituisce (o risolve con) la
risposta.
| Linguaggio | Handler | L’export | Rinunciare al programma |
|---|---|---|---|
| C | — | int32_t <id>_<fn>(arguments…, lungo_step *step, lungo_error **error) restituisce il primo passo del programma; l’host risponde a ciascuno con lungo_resume (vedi l’API C) |
lungo_async_cancel |
| Go | interfaccia <Op>Handler, metodi che prendono prima un context.Context e restituiscono (answer, error) |
F(ctx, handler, arguments…) (T, error) |
ctx annullato, l’errore di un metodo o un panic |
| Python | typing.Protocol <Op>Handler; un metodo restituisce la risposta o un awaitable di essa |
async def f(handler, arguments…) |
il task annullato, o l’eccezione di un metodo |
| Swift | protocollo <Module><Op>Handler (Sendable), metodi async throws |
func f(_ handler: some <Module><Op>Handler, arguments…) async throws -> T |
il task annullato, o l’errore di un metodo |
| TypeScript | interfaccia <Op>Handler; un metodo restituisce la risposta o una promise di essa |
p.f(handler, arguments…, { signal }) restituisce una promise |
signal interrotto, o il rifiuto di un metodo |
Un programma a cui si rinuncia viene rilasciato dal runtime, e la chiamata fallisce con l’errore
del metodo (o con l’annullamento). lungo.Outstanding() (Go), lungo_py.outstanding()
(Python), lungoOutstanding() (Swift), p.outstanding() (TypeScript, per programma) e
lungo_async_outstanding() (C) contano i programmi in attesa di una risposta: zero quando ogni
chiamata è terminata. Un processo può terminare con programmi in attesa.
Garanzia
Ogni pacchetto contiene il documento di garanzia del
programma, assurance.json, lo stesso file per ogni linguaggio, e lo legge con i tipi del
linguaggio:
| Linguaggio | Il documento |
|---|---|
| C | const char *<id>_assurance_json(void): il suo testo, byte per byte |
| Go | Assurance() *lungo.Assurance (incorporato con //go:embed); Claim(name), Export(name); lungo.ParseAssurance ne legge un altro |
| Python | ASSURANCE, un lungo_py.Assurance; claim(name), export(name); lungo_py.Assurance.from_json ne legge un altro |
| Swift | assurance, un LungoAssurance, e assuranceJSON, il suo testo byte per byte; claim(_:), export(_:) |
| TypeScript | ASSURANCE, un L.Assurance congelato; L.parseAssurance ne legge un altro |
Ogni lettore rifiuta un documento di un’altra versione dello schema, o con un campo che non conosce o che manca, a qualunque profondità (dettagli).
Thread
Il programma può essere chiamato da un numero qualsiasi di thread contemporaneamente
(goroutine Go, thread Python, task e code Swift, thread C); le chiamate rilasciano il GIL di
Python. Le funzioni dell’host possono essere chiamate su qualsiasi thread, compresi i thread su
cui girano i task di Lean. WebAssembly ha un solo thread: i task di Lean vengono eseguiti quando
vengono creati, e il programma non può usare processi figli, socket, segnali o timer
(LNG0408).
Un processo ha un solo runtime di lungo e un solo host: la libreria di supporto del suo linguaggio. Le funzioni dell’host dell’API C dei valori e la libreria di supporto di un altro linguaggio non possono essere usate nello stesso processo.
C
CMakeLists.txt the library <id> (target <id>::<id>) and the runtime
include/<id>.h the API
src/<id>.c
program/ the program: lungo.h, <id>_program.h, <id>_program.c,
<id>_boundary.c, modules/*.c
Il progetto CMake scarica l’archivio del runtime della release di lungo per il target, ne
controlla il digest SHA-256 (URL_HASH) e collega la sua libreria statica (lungo::runtime).
Con -D<ID>_FETCH_LUNGO=OFF usa invece un runtime installato (find_package(lungo <version>
EXACT); CMAKE_PREFIX_PATH o lungo_DIR lo individuano); -D<ID>_LUNGO_TARGET=<triple>
sceglie l’archivio quando il target non può essere dedotto. Includilo con add_subdirectory.
Ogni valore è un lungo_value * dell’API C. L’API aggiunge, per ogni tipo, il suo
descrittore (<id>_<type>_type(...)), una funzione costruttore per ogni costruttore
(<id>_<type>_<ctor>(fields…), che prende possesso dei campi), una macro con l’indice di ogni
costruttore (<ID>_<TYPE>_<CTOR>) e un accessore per campo (<id>_<type>_<field>(v), oppure
<id>_<type>_<ctor>_<field>(v) per i tipi con più costruttori); per ogni funzione
int32_t <id>_<function>(type arguments…, arguments…, lungo_value **result, lungo_error
**error) (un export asincrono prende lungo_step *step invece di result);
<id>_implement_<facility>_<operation>(f, ctx, drop); <id>_assurance_json();
<id>_initialize() e <id>_run_main(argc, argv). Objective-C usa la stessa API (vedi Swift).
Go
<id>.go the package (cgo)
*.c, *.h the program; module files are module_<stem>_lean.c
Il pacchetto importa github.com/machinefabric/lungo-go e richiede esattamente la versione del
modulo della release di lungo che lo ha generato (const _ = lungo.EnforceVersion<version> non
compila con un’altra). Il modulo contiene il runtime per linux/amd64, linux/arm64,
darwin/amd64, darwin/arm64 e windows/amd64 e lo collega una sola volta per binario. Compila con
cgo abilitato e un compilatore C (su Windows, GCC di MinGW-w64). Il nome del pacchetto è l’id
del programma (opzione package).
Le funzioni restituiscono (T, error); i tipi sono tipi Go, ciascuno con una funzione
descrittore (PointType(), TreeType[A](typeA)); Set<Facility>(impl) installa ogni
facility; gli export asincroni prendono un context.Context e un handler;
Assurance() è il documento di garanzia; RunMain(args) (int, error) esegue main.
Python
pyproject.toml scikit-build-core; requires lungo-py
CMakeLists.txt builds the program as the shared library of the package
program/
src/<package>/__init__.py, py.typed
pip install DIR compila una wheel (py3-none-<platform>: il programma è una libreria
condivisa caricata con ctypes, non un’estensione CPython, quindi una sola wheel serve ogni
Python 3). Dipende esattamente dal lungo-py della release, che contiene il runtime. Le
funzioni sono funzioni del modulo (gli export asincroni sono async def); i tipi sono
dataclass immutabili; set_<facility>(impl) installa ogni facility; ASSURANCE
è il documento di garanzia; run_main(args) esegue main.
Swift
Package.swift depends on lungo-swift at exactly the release's version
Sources/<Module>Program/ the program and the C API (include/<id>.h: for C and Objective-C)
Sources/<Module>/<Module>.swift
Il pacchetto ha due prodotti: <Module> (l’API Swift) e <Module>Program (l’API C, che
Objective-C importa con @import <Module>Program;). lungo-swift fornisce LungoKit e il
runtime come XCFramework (macOS 12 e successivi, iOS 15 e successivi, il simulatore iOS). Le
funzioni sono funzioni del modulo che lanciano (throw; gli export asincroni sono
async throws); i tipi sono struct e indirect enum; set<Facility>(_:) installa ogni
facility; assurance è il documento di garanzia; runMain(_:) esegue main.
TypeScript
package.json depends on lungo-ts at exactly the release's version
index.js, index.d.ts ES module and its declarations
program.wasm the program and the runtime, for wasm32-wasip1
lungo generate collega program.wasm con il wasi-sdk fissato. await load(options) lo
istanzia (su Node.js 20 e successivi con node:wasi; nei browser con BrowserWasi di lungo-ts,
o con qualsiasi WASI passato come options.wasi) e restituisce un oggetto i cui metodi sono le
funzioni del programma (gli export asincroni restituiscono promise); options.facilities
implementa le facility, e ASSURANCE è il documento di garanzia. I programmi che usano
primitive che mancano a WebAssembly non possono essere generati
(LNG0408).
Pacchetti incorporati
Una libreria che è già un pacchetto nel suo linguaggio (una distribuzione Python, un pacchetto
npm, un pacchetto Swift o un progetto CMake) incorpora il programma al suo interno con
l’opzione del generatore embed, invece di dipendere da un pacchetto proprio del programma.
lungo possiede la directory di output e la sostituisce per intero, quindi è una directory
dell’host, mai la sua radice. Il manifest dell’host fornisce ciò che avrebbe fornito quello
generato:
| Linguaggio | La directory di output | L’host fornisce |
|---|---|---|
C (embed=true) |
include/, src/, program/, e un CMakeLists.txt che definisce la libreria <id> |
il target lungo::runtime (find_package(lungo <version> EXACT CONFIG)) prima di add_subdirectory; il collegamento dei pacchetti che forniscono tipi extern a <id> |
Python (embed=<module>) |
il modulo: __init__.py, program/, e un CMakeLists.txt che compila il programma e lo installa nel modulo |
una build scikit-build-core il cui CMakeLists.txt aggiunge la directory con add_subdirectory; lungo-py==<version> in build-system.requires e in dependencies |
Swift (embed=true) |
<Module>Program/ e <Module>/, i due target |
i target, tramite path: <Module>Program che dipende da LungoKit con cSettings: [.headerSearchPath("program")], e <Module> che dipende da esso e da LungoKit; lungo-swift esattamente alla versione della release |
TypeScript (embed=true) |
index.js, index.d.ts, program.wasm |
lungo-ts esattamente alla versione della release, e program.wasm tra i file pubblicati |