HotXLS utvärderar Excel LAMBDA som ett riktigt förstaklassvärde av funktionstyp. Ett definierat namn vars RefersTo-text är en LAMBDA kan anropas med namn som =MyFunc(5), en closure bunden inuti LET kan anropas som =LET(f, LAMBDA(x, x*2), f(21)), och den lexikala miljön som fångades vid definitionstillfället följer med closuren. Formeltexten går oförändrad tur och retur genom arbetsboken
Det är den funktionen som skiljer en formelmotor från en formeltolkare. Allt före LAMBDA kunde utvärderas genom att vandra genom ett träd av värden. LAMBDA kräver en scope-stack, och när du väl har en scope-stack börjar en hel klass av användarförfattad kalkylbladslogik fungera i din Delphi-applikation i stället för bara i Excel
Varför stannar de flesta icke-Excel-motorer vid nyckelordet LAMBDA?
Därför att en klassisk kalkylbladsutvärderare har exakt en sorts värde: ett tal, en sträng, en boolean, ett fel, eller en referens till celler som håller de sakerna. Det finns ingenstans att lägga en funktion. När Excel 365 introducerade LAMBDA lade det till en värdetyp som bär parameternamn, ett kroppsuttryck, och de bindningar som var synliga där den skrevs. En motor utan den typen kan tolka LAMBDA(x, x*2) och lagra texten, men i det ögonblick en cell försöker anropa den finns det ingenting att anropa
HotXLS implementerar den saknade delen som ett closure-värde plus en scope-stack vid körning. Att anropa en closure pushar dess fångade miljö, pushar sedan argumentvärdena under parameternamnen, utvärderar kroppen, och trunkerar stacken tillbaka till märket. Den ordningen spelar roll, och nästa avsnitt förklarar varför
De tre sätten en LAMBDA blir anropad
HotXLS löser ett anrop till ett okänt funktionsnamn genom tre vägar, provade i ordning, och att veta vilken som utlöses förklarar de flesta överraskningar. Först, ett namn bundet i det aktuella LET- eller LAMBDA-scopet: om f är en lokal bindning som håller en closure, tillämpar f(21) den. För det andra, ett arbetsboksdefinierat namn vars formeltext börjar med LAMBDA: MyFunc(5) kompilerar det namnets kropp och tillämpar den. För det tredje, den klassiska hanteraren för användarfunktioner, oförändrad, för allt de två första vägarna inte gör anspråk på
En lokal bindning som håller något annat än en closure går inte att anropa. Bind f till talet 3 och skriv sedan f(21) och du får ett värdefel, inte ett försök att multiplicera. Det här är strängare än ett dynamiskt språk skulle vara, och medvetet så: ett stavfel som förvandlar ett funktionsanrop till en oavsiktlig referens är ett tyst fel svar, vilket är det värsta utfall en kalkylbladsmotor kan producera
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// En återanvändbar namngiven funktion, arbetsbokens scope
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// En closure bunden och tillämpad inuti en formel
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// Nästlad LET: varje bindning är synlig för de som kommer efter
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;
Hur löses skuggning när namn krockar?
Parametrar vinner. När HotXLS tillämpar en closure pushar den den fångade lexikala miljön först och argumentbindningarna sedan, så en parameter vid namn rate skuggar en yttre bindning vid namn rate och skuggar också en likstavad kolumnreferens i den omgivande formeln. Den ordningen är det som gör en namngiven funktion säker att återanvända: anroparen kan inte oavsiktligt ändra vad kroppen betyder genom att ha en liknande namngiven bindning i scope
Aritet kontrolleras innan något utvärderas. Ett anrop vars argumentantal inte matchar closurens parameterantal returnerar omedelbart ett värdefel, i stället för att utvärdera vissa argument och sedan misslyckas, vilket håller sidoeffektfri utvärdering genuint fri från partiellt arbete. Scope-stacken trunkeras tillbaka till sitt ingångsmärke i ett finally-block, så ett fel inuti en kropp kan inte lämna kvar föråldrade bindningar synliga för nästa formel
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Inspektera vad användaren skrev innan du litar på en omräkning
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 är inte längre partiell
Tidigare HotXLS-utgåvor implementerade LET bara tillräckligt långt för att hantera det vanliga enkelbindningsfallet. Den nuvarande implementationen är fullständig: varje bindning är synlig för alla senare bindningar och för kroppsuttrycket, och nästlad LET komponeras normalt, så LET(a, 1, b, a+1, LET(c, b*2, c)) utvärderas på samma sätt som Excel utvärderar det
Den fullständigheten spelar större roll än det låter. LET är hur användare undviker att räkna om samma deluttryck fem gånger i en formel, så verkliga arbetsböcker använder det i just de djupt nästlade former som en partiell implementation gör fel på. Om du tidigare kringgick luckor genom att expandera LET-bindningar före utvärdering, kan den lösningen försvinna
Komma eller semikolon: båda, nu
Formeltext i HotXLS accepterar nu kommat som en argumentseparator vid sidan av det klassiska semikolonet. Det här är inte en lokalinställning; det är en acceptansregel i tolkaren. Det spelar roll eftersom formler anländer från platser du inte styr: inklistrade från ett supportärende, kopierade ur dokumentation, genererade av ett skript som sände ut Excels kanoniska syntax, importerade från en CSV med formelsträngar
Den praktiska effekten är att både SUM(A1,A2) och SUM(A1;A2) kompilerar. Tur-och-retur-hanteringen bevarar vad källan än använde, så en arbetsbok du läste in skrivs tillbaka med sina ursprungliga separatorer i stället för att normaliseras bakom ryggen på användaren
Vad som går tur och retur, och vad som bör kontrolleras
Formeltexten lagras ordagrant, så en LAMBDA i ett definierat namn överlever en läs- och sparcykel intakt och öppnas i Excel som samma funktion. En bar LAMBDA lagrad som ett cellresultat, det vill säga en formel som utvärderas till en closure snarare än till ett värde, behåller det befintliga hoppa-över-utan-värde-beteendet: texten bevaras, inget cachat numeriskt resultat hittas på för den. Det är det ärliga utfallet, eftersom det inte finns någon skalär att cacha
Två vanor är värda att anta. Ge namngivna lambdas arbetsboksscope om det inte finns en anledning att inte göra det, eftersom en bladbunden funktion som försvinner när ett blad kopieras ger ett namnfel på en plats långt från orsaken; scope-reglerna täcks i definierade namn och bladöverskridande formler. Och när en arbetsbok full av namngivna lambdas är avsedd för en rapport som måste vara stabil, överväg att frysa resultaten med ConvertFormulasToValues så att nedströmskonsumenter ser tal i stället för funktioner de kanske inte stöder
Vid tung omräkning är LAMBDA-kroppar vanliga uttryck i beroendegrafen och schemaläggs som vilken annan formel som helst, vilket beskrivs i inkrementell omräkning och beroendegrafen. Om din modell anropar en namngiven funktion över tusentals rader är kostnaden kroppen, inte anropsmaskineriet, och samma optimeringsråd gäller som för vilken upprepad formel som helst
HotXLS är en nativ kalkylarkskomponent för Delphi och C++Builder som läser och skriver XLS, XLSX och ODS utan Excel eller någon Office-automatisering. Formelmotorn, definierade namn och omräknings-API:et dokumenteras på sidan för HotXLS Delphi kalkylarkskomponent