HotXLS valuta Excel LAMBDA come un vero valore funzione first-class. Un nome definito il cui testo RefersTo è una LAMBDA può essere chiamato per nome come =MyFunc(5), una closure legata dentro LET può essere chiamata come =LET(f, LAMBDA(x, x*2), f(21)), e l'ambiente lessicale catturato al momento della definizione viaggia insieme alla closure. Il testo della formula torna nella cartella di lavoro invariato in un round-trip
Questa è la funzionalità che separa un motore di formule da un parser di formule. Tutto ciò che precede LAMBDA poteva essere valutato percorrendo un albero di valori. LAMBDA richiede uno scope stack, e una volta che hai uno scope stack, un'intera classe di logica di foglio di calcolo scritta dall'utente inizia a funzionare nella tua applicazione Delphi invece che solo in Excel
Perché la maggior parte dei motori non-Excel si ferma alla keyword LAMBDA?
Perché un valutatore di foglio di calcolo classico ha esattamente un tipo di valore: un numero, una stringa, un booleano, un errore, o un riferimento a celle che contengono quei valori. Non c'è dove mettere una funzione. Quando Excel 365 ha introdotto LAMBDA, ha aggiunto un tipo di valore che porta nomi di parametro, un'espressione body, e i binding visibili dove è stata scritta. Un motore privo di quel tipo può analizzare LAMBDA(x, x*2) e memorizzare il testo, ma nel momento in cui una cella prova a chiamarla, non c'è nulla da chiamare
HotXLS implementa il pezzo mancante come un valore closure più uno scope stack a runtime. Chiamare una closure impila il suo ambiente catturato, poi impila i valori degli argomenti sotto i nomi dei parametri, valuta il body, e tronca lo stack tornando al marcatore. Quest'ordine conta, e la sezione successiva spiega perché
I tre modi in cui una LAMBDA viene chiamata
HotXLS risolve una chiamata a un nome di funzione sconosciuto attraverso tre percorsi, provati in ordine, e sapere quale si attiva spiega la maggior parte delle sorprese. Primo, un nome legato nello scope LET o LAMBDA corrente: se f è un binding locale che contiene una closure, f(21) la applica. Secondo, un nome definito a livello di cartella di lavoro il cui testo formula inizia con LAMBDA: MyFunc(5) compila il body di quel nome e lo applica. Terzo, il classico gestore di funzioni utente, invariato, per tutto ciò che i primi due percorsi non rivendicano
Un binding locale che contiene qualcosa di diverso da una closure non è chiamabile. Lega f al numero 3 e poi scrivi f(21) e ottieni un errore di valore, non un tentativo di moltiplicazione. Questo è più rigoroso di quanto sarebbe un linguaggio dinamico, e deliberatamente: un errore di battitura che trasforma una chiamata di funzione in un riferimento accidentale è una risposta sbagliata silenziosa, il peggior esito che un motore di foglio di calcolo possa produrre
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Una funzione con nome riutilizzabile, scope cartella di lavoro
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Una closure legata e applicata dentro una sola formula
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// LET annidato: ogni binding è visibile a quelli successivi
Sheet.Cells[4, 2].Formula :=
'LET(base, 100, bump, LAMBDA(v, v+base), LET(step, bump(5), step*2))';
Book.Recalculate;
Book.SaveAs('lambda-model.xlsx');
finally
Book.Free;
end;
end;
Come si risolve lo shadowing quando i nomi collidono?
Vincono i parametri. Quando HotXLS applica una closure impila per prima l'ambiente lessicale catturato e per seconda i binding degli argomenti, così un parametro chiamato rate fa shadow su un binding esterno chiamato rate e fa shadow anche su un riferimento di colonna con la stessa grafia nella formula circostante. Questo ordinamento è ciò che rende sicuro riutilizzare una funzione con nome: chi chiama non può cambiare accidentalmente cosa significa il body avendo in scope un binding con un nome simile
L'arità viene controllata prima che qualunque cosa venga valutata. Una chiamata il cui numero di argomenti non corrisponde al numero di parametri della closure restituisce immediatamente un errore di valore, invece di valutare alcuni argomenti e poi fallire, il che mantiene la valutazione priva di effetti collaterali genuinamente libera da lavoro parziale. Lo scope stack viene troncato tornando al suo marcatore di ingresso dentro un blocco finally, così un errore dentro un body non può lasciare binding obsoleti visibili alla formula successiva
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Ispeziona ciò che l'utente ha scritto prima di fidarti di un ricalcolo
Name := Book.DefinedNames.FindByName('NetOf');
if (Name <> nil) and
(UpperCase(Copy(Name.Formula, 1, 6)) = 'LAMBDA') then
Log('Named lambda found: ' + Name.Formula);
Book.Recalculate;
Log(VarToStr(Book.Sheets[1].Cells[2, 2].Value));
end;
finally
Book.Free;
end;
end;
LET non è più parziale
Le release precedenti di HotXLS implementavano LET solo abbastanza da gestire il caso comune con un singolo binding. L'implementazione attuale è completa: ogni binding è visibile a tutti i binding successivi e all'espressione body, e il LET annidato si compone normalmente, così LET(a, 1, b, a+1, LET(c, b*2, c)) viene valutato nello stesso modo in cui lo valuta Excel
Questa completezza conta più di quanto sembri. LET è il modo in cui gli utenti evitano di ricalcolare la stessa sottoespressione cinque volte in una formula, quindi le cartelle di lavoro reali lo usano esattamente nelle forme profondamente annidate che un'implementazione parziale sbaglia. Se in precedenza aggiravi le lacune espandendo i binding LET prima della valutazione, quel workaround può sparire
Virgola o punto e virgola: entrambi, ora
Il testo delle formule in HotXLS ora accetta la virgola come separatore di argomenti accanto al classico punto e virgola. Non è un'impostazione di locale; è una regola di accettazione nel parser. Conta perché le formule arrivano da posti che non controlli: incollate da un ticket di supporto, copiate dalla documentazione, generate da uno script che ha emesso la sintassi canonica di Excel, importate da un CSV di stringhe di formula
L'effetto pratico è che sia SUM(A1,A2) sia SUM(A1;A2) vengono compilate. Il round-trip preserva qualunque cosa la sorgente abbia usato, quindi una cartella di lavoro che hai caricato viene riscritta con i suoi separatori originali invece che normalizzata alle spalle dell'utente
Cosa fa round-trip, e cosa controllare
Il testo della formula viene memorizzato invariato, quindi una LAMBDA in un nome definito sopravvive intatta a un ciclo di caricamento e salvataggio e si apre in Excel come la stessa funzione. Una LAMBDA nuda memorizzata come risultato di cella, cioè una formula che valuta a una closure anziché a un valore, mantiene il comportamento esistente skip-without-value: il testo viene preservato, non viene inventato alcun risultato numerico in cache per essa. Questo è l'esito onesto, dato che non c'è alcuno scalare da mettere in cache
Due abitudini vale la pena adottare. Dai alle lambda con nome lo scope di cartella di lavoro a meno che non ci sia un motivo per non farlo, perché una funzione con scope di foglio che sparisce quando un foglio viene copiato produce un errore di nome in un punto lontano dalla causa; le regole di scope sono trattate in nomi definiti e formule cross-sheet. E quando una cartella di lavoro piena di lambda con nome è destinata a un report che deve essere stabile, valuta di congelare i risultati con ConvertFormulasToValues così i consumer a valle vedono numeri invece di funzioni che potrebbero non supportare
Per ricalcoli pesanti, i body delle LAMBDA sono espressioni ordinarie nel grafo delle dipendenze e vengono pianificati come qualunque altra formula, il che è descritto in ricalcolo incrementale e il grafo delle dipendenze. Se il tuo modello chiama una funzione con nome su migliaia di righe, il costo è il body, non il meccanismo di chiamata, e si applica lo stesso consiglio di ottimizzazione valido per qualunque formula ripetuta
HotXLS è un componente foglio di calcolo nativo per Delphi e C++Builder che legge e scrive XLS, XLSX e ODS senza Excel o alcuna automazione Office. Il motore di formule, i nomi definiti e l'API di ricalcolo sono documentati sulla pagina del componente foglio di calcolo Delphi HotXLS