HotXLS vyhodnocuje Excel LAMBDA jako skutečnou hodnotu funkce první třídy. Definovaný název, jehož text RefersTo je LAMBDA, lze zavolat podle jména jako =MyFunc(5), uzávěr vázaný uvnitř LET lze zavolat jako =LET(f, LAMBDA(x, x*2), f(21)) a lexikální prostředí zachycené v okamžiku definice cestuje spolu s uzávěrem. Text vzorce se do sešitu ukládá a zase načítá doslovně beze změny
Toto je funkce, která odděluje engine vzorců od parseru vzorců. Všechno před LAMBDA šlo vyhodnotit procházením stromu hodnot. LAMBDA vyžaduje zásobník rozsahů, a jakmile máte zásobník rozsahů, celá třída uživatelsky napsané tabulkové logiky začne fungovat ve vaší aplikaci Delphi místo jen v Excelu
Proč se většina enginů mimo Excel zastaví u klíčového slova LAMBDA?
Protože klasický vyhodnocovač tabulek zná přesně jeden druh hodnoty: číslo, řetězec, booleovskou hodnotu, chybu nebo odkaz na buňky, které tyto hodnoty drží. Není kam funkci uložit. Když Excel 365 přinesl LAMBDA, přidal typ hodnoty, který nese jména parametrů, tělo výrazu a vazby viditelné v místě, kde byl zapsán. Engine bez tohoto typu umí LAMBDA(x, x*2) naparsovat a uložit jeho text, ale v okamžiku, kdy se ho buňka pokusí zavolat, není co volat
HotXLS implementuje tento chybějící kus jako hodnotu uzávěru plus běhový zásobník rozsahů. Volání uzávěru vloží na zásobník jeho zachycené prostředí, pak vloží hodnoty argumentů pod jmény parametrů, vyhodnotí tělo a zásobník ořízne zpět na značku. Na tomto pořadí záleží a proč, vysvětluje další oddíl
Tři způsoby, jak se LAMBDA volá
HotXLS řeší volání neznámého názvu funkce třemi cestami, které vyzkouší postupně, a vědět, která se spustí, vysvětlí většinu překvapení. Zaprvé, název vázaný v aktuálním rozsahu LET nebo LAMBDA: pokud je f lokální vazba držící uzávěr, f(21) ho aplikuje. Zadruhé, definovaný název sešitu, jehož text vzorce začíná LAMBDA: MyFunc(5) zkompiluje tělo tohoto názvu a aplikuje ho. Zatřetí, klasický obslužný program uživatelských funkcí, beze změny, pro všechno, co si nenárokují první dvě cesty
Lokální vazba, která drží něco jiného než uzávěr, není volatelná. Svážete-li f s číslem 3 a pak napíšete f(21), dostanete chybu hodnoty, ne pokus o násobení. To je přísnější, než by byl dynamický jazyk, a to záměrně: překlep, který promění volání funkce v náhodný odkaz, je tichá špatná odpověď, a to je nejhorší výsledek, jaký může tabulkový engine vyprodukovat
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// znovupoužitelná pojmenovaná funkce, rozsah sešitu
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// uzávěr vázaný a aplikovaný uvnitř jednoho vzorce
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// vnořené LET: každá vazba je viditelná pro ty následující
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;
Jak se řeší stínování, když se názvy střetnou?
Vyhrávají parametry. Když HotXLS aplikuje uzávěr, vloží na zásobník nejdřív zachycené lexikální prostředí a teprve pak vazby argumentů, takže parametr pojmenovaný rate zastíní vnější vazbu pojmenovanou rate a zastíní i stejně pojmenovaný odkaz na sloupec v okolním vzorci. Právě toto pořadí dělá pojmenovanou funkci bezpečnou k opakovanému použití: volající nemůže nechtěně změnit význam těla tím, že by měl v rozsahu podobně pojmenovanou vazbu
Arita se kontroluje dřív, než se cokoli vyhodnotí. Volání, jehož počet argumentů neodpovídá počtu parametrů uzávěru, vrátí okamžitě chybu hodnoty, místo aby vyhodnotilo některé argumenty a pak selhalo, což udržuje vyhodnocení bez vedlejších efektů skutečně bez dílčí práce. Zásobník rozsahů se ořízne zpět na svou vstupní značku v bloku finally, takže chyba uvnitř těla nemůže nechat zastaralé vazby viditelné pro další vzorec
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// prohlédněte, co uživatel napsal, než budete důvěřovat přepočtu
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 už není jen zčásti implementovaný
Dřívější vydání HotXLS implementovala LET jen natolik, aby zvládla běžný případ s jedinou vazbou. Aktuální implementace je úplná: každá vazba je viditelná pro všechny pozdější vazby i pro výraz těla a vnořené LET se skládá normálně, takže LET(a, 1, b, a+1, LET(c, b*2, c)) se vyhodnotí stejně, jako to vyhodnotí Excel
Tato úplnost má větší význam, než jak zní. LET je způsob, jakým se uživatelé vyhýbají pětinásobnému přepočítávání stejného podvýrazu v jednom vzorci, takže skutečné sešity ho používají přesně v těch hluboce vnořených podobách, které dílčí implementace kazí. Pokud jste dřív obcházeli mezery rozbalováním vazeb LET před vyhodnocením, tento obchvat už můžete zrušit
Čárka, nebo středník: teď obojí
Text vzorce v HotXLS teď akceptuje čárku jako oddělovač argumentů vedle klasického středníku. Nejde o nastavení lokalizace; je to pravidlo akceptace v parseru. Má to význam, protože vzorce přicházejí z míst, která nemáte pod kontrolou: vložené ze supportního tiketu, zkopírované z dokumentace, vygenerované skriptem, který vyprodukoval kanonickou syntaxi Excelu, importované z CSV souboru řetězců vzorců
Praktický efekt je, že se zkompiluje jak SUM(A1,A2), tak SUM(A1;A2). Zápis a zpětné čtení zachovává to, co použil zdroj, takže sešit, který jste načetli, se zapíše zpět s původními oddělovači, místo aby se normalizoval za zády uživatele
Co se zachová při zápisu a co zkontrolovat
Text vzorce se ukládá doslovně, takže LAMBDA v definovaném názvu přežije cyklus načtení a uložení nedotčená a v Excelu se otevře jako stejná funkce. Holá LAMBDA uložená jako výsledek buňky, tedy vzorec, který se vyhodnotí na uzávěr místo na hodnotu, si zachovává stávající chování „přeskočit bez hodnoty": text se zachová, žádný cachovaný číselný výsledek se pro ni nevymýšlí. To je poctivý výsledek, protože není žádná skalární hodnota, kterou by šlo cachovat
Vyplatí se osvojit si dva zvyky. Dávejte pojmenovaným lambdám rozsah sešitu, pokud pro to není důvod neudělat, protože funkce s rozsahem na úrovni listu, která zmizí při zkopírování listu, vyprodukuje chybu názvu na místě daleko od příčiny; pravidla rozsahu popisuje článek o definovaných názvech a vzorcích napříč listy. A když je sešit plný pojmenovaných lambd určen pro report, který musí zůstat stabilní, zvažte zmrazení výsledků pomocí ConvertFormulasToValues, aby navazující spotřebitelé viděli čísla místo funkcí, které nemusí podporovat
Při náročném přepočtu jsou těla LAMBDA obyčejné výrazy v grafu závislostí a plánují se stejně jako každý jiný vzorec, jak popisuje článek o přírůstkovém přepočtu a grafu závislostí. Pokud váš model volá jednu pojmenovanou funkci napříč tisíci řádky, náklad je v těle funkce, ne v mechanismu volání, a platí stejné doporučení pro optimalizaci jako u kteréhokoli opakovaného vzorce
HotXLS je nativní tabulková komponenta pro Delphi a C++Builder, která čte a zapisuje XLS, XLSX a ODS bez Excelu nebo jakékoli automatizace Office. API pro engine vzorců, definované názvy i přepočet jsou zdokumentované na stránce HotXLS Delphi spreadsheet component