HotXLS evaluerer Excel LAMBDA som en reel førsteklasses funktionsværdi. Et navngivet navn, hvis RefersTo-tekst er en LAMBDA, kan kaldes ved navn som =MyFunc(5), en closure, bundet inde i LET, kan kaldes som =LET(f, LAMBDA(x, x*2), f(21)), og det leksikalske miljø, indfanget på definitionstidspunktet, følger med closuren. Formelteksten rundtures verbatim ind i projektmappen
Dette er den funktion, der skiller en formelmotor fra en formelparser. Alt før LAMBDA kunne evalueres ved at gennemgå et træ af værdier. LAMBDA kræver en scope-stak, og når du har en scope-stak, begynder en hel klasse af brugerskrevet regnearkslogik at fungere i din Delphi-applikation frem for kun i Excel
Hvorfor stopper de fleste ikke-Excel-motorer ved LAMBDA-nøgleordet?
Fordi en klassisk regnearksevaluator har præcis én slags værdi: et tal, en streng, en boolsk værdi, en fejl eller en reference til celler, der holder dem. Der er ingen plads til en funktion. Da Excel 365 introducerede LAMBDA, tilføjede det en værditype, der bærer parameternavne, et body-udtryk og de bindinger, der er synlige, hvor det blev skrevet. En motor uden den type kan parse LAMBDA(x, x*2) og gemme teksten, men i det øjeblik en celle forsøger at kalde den, er der intet at kalde
HotXLS implementerer den manglende brik som en closure-værdi plus en runtime-scope-stak. At kalde en closure skubber dens indfangede miljø, derefter skubber argumentværdierne under parameternavnene, evaluerer body'en og afkorter stakken tilbage til mærket. Den rækkefølge betyder noget, og næste afsnit forklarer hvorfor
De tre måder, en LAMBDA kaldes på
HotXLS løser et kald til et ukendt funktionsnavn gennem tre stier, prøvet i rækkefølge, og at vide, hvilken der udløses, forklarer de fleste overraskelser. Først, et navn bundet i det aktuelle LET- eller LAMBDA-scope: hvis f er en lokal binding, der holder en closure, anvender f(21) den. For det andet, et projektmappe-defineret navn, hvis formeltekst begynder med LAMBDA: MyFunc(5) kompilerer det navns body og anvender den. For det tredje, den klassiske brugerfunktionshåndtering, uændret, for alt, de to første stier ikke gør krav på
En lokal binding, der holder noget andet end en closure, kan ikke kaldes. Bind f til tallet 3, og skriv derefter f(21), og du får en værdifejl, ikke et forsøg på at gange. Dette er strengere, end et dynamisk sprog ville være, og bevidst sådan: en stavefejl, der gør et funktionskald til en utilsigtet reference, er et lydløst forkert svar, hvilket er det værste resultat, en regnearksmotor kan producere
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// En genbrugelig navngivet funktion, projektmappe-scope
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// En closure bundet og anvendt inde i én formel
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// Indlejret LET: hver binding er synlig for dem, der 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;
Hvordan løses skygge, når navne kolliderer?
Parametre vinder. Når HotXLS anvender en closure, skubber den det indfangede leksikalske miljø først og argumentbindingerne dernæst, så en parameter ved navn rate skygger for en ydre binding ved navn rate og skygger også for en samme-stavet kolonnereference i den omgivende formel. Den rækkefølge er det, der gør en navngivet funktion sikker at genbruge: kalderen kan ikke ved et uheld ændre, hvad body'en betyder, ved at have en lignende navngivet binding i scope
Aritet tjekkes, før noget evalueres. Et kald, hvis antal argumenter ikke matcher closurens antal parametre, returnerer en værdifejl med det samme, frem for at evaluere nogle argumenter og derefter fejle, hvilket holder side-effekt-fri evaluering reelt fri for delvist arbejde. Scope-stakken afkortes tilbage til sit indgangsmærke i en finally-blok, så en fejl inde i en body ikke kan efterlade forældede bindinger synlige for den næste formel
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Inspicér, hvad brugeren har forfattet, før du stoler på en genberegning
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 er ikke længere delvist
Tidligere HotXLS-udgivelser implementerede kun LET langt nok til at håndtere det almindelige tilfælde med én binding. Den nuværende implementering er komplet: hver binding er synlig for alle senere bindinger og for body-udtrykket, og indlejret LET sammensættes normalt, så LET(a, 1, b, a+1, LET(c, b*2, c)) evalueres, som Excel evaluerer det
Den fuldstændighed betyder mere, end det lyder. LET er, hvordan brugere undgår at genberegne det samme underudtryk fem gange i én formel, så rigtige projektmapper bruger det netop i de dybt indlejrede former, en delvis implementering får galt. Hvis du tidligere har omgået huller ved at udfolde LET-bindinger før evaluering, kan den workaround udgå
Komma eller semikolon: begge, nu
Formeltekst i HotXLS accepterer nu kommaet som en argumentseparator ved siden af det klassiske semikolon. Dette er ikke en lokalitetsindstilling; det er en accept-regel i parseren. Det betyder noget, fordi formler ankommer fra steder, du ikke kontrollerer: indsat fra en supportsag, kopieret fra dokumentation, genereret af et script, der udsendte Excels kanoniske syntaks, importeret fra en CSV af formelstrenge
Den praktiske effekt er, at SUM(A1,A2) og SUM(A1;A2) begge kompilerer. Rundtur bevarer, hvad kilden brugte, så en projektmappe, du indlæste, skrives tilbage med sine originale separatorer frem for at blive normaliseret bag brugerens ryg
Hvad rundturer, og hvad du skal tjekke
Formelteksten gemmes verbatim, så en LAMBDA i et defineret navn overlever en indlæs-og-gem-cyklus intakt og åbner i Excel som den samme funktion. En bar LAMBDA gemt som et celleresultat, det vil sige en formel, der evalueres til en closure frem for til en værdi, beholder den eksisterende spring-uden-værdi-adfærd: teksten bevares, ingen cachet numerisk resultat opfindes til den. Det er det ærlige udfald, da der ikke er nogen skalar at cache
To vaner er værd at tillægge sig. Giv navngivne lambdaer projektmappe-scope, medmindre der er en grund til det modsatte, fordi en ark-scope funktion, der forsvinder, når et ark kopieres, producerer en navnefejl et sted langt fra årsagen; scoping-reglerne dækkes i definerede navne og tværark-formler. Og når en projektmappe fuld af navngivne lambdaer er bestemt til en rapport, der skal være stabil, kan du overveje at fastfryse resultaterne med ConvertFormulasToValues, så downstream-forbrugere ser tal frem for funktioner, de måske ikke understøtter
For tung genberegning er LAMBDA-bodies almindelige udtryk i afhængighedsgrafen og planlægges som enhver anden formel, hvilket beskrives i inkrementel genberegning og afhængighedsgrafen. Hvis din model kalder én navngivet funktion på tværs af tusindvis af rækker, er omkostningen body'en, ikke kaldemaskineriet, og det samme optimeringsråd gælder som for enhver gentaget formel
HotXLS er en native Delphi- og C++Builder-regnearkskomponent, der læser og skriver XLS, XLSX og ODS uden Excel eller nogen Office-automatisering. Formelmotoren, definerede navne og genberegnings-API'en er dokumenteret på HotXLS' produktside for Delphi-regnearkskomponenten