HotXLS wylicza Excelowe LAMBDA jako prawdziwą wartość funkcyjną pierwszej klasy. Nazwa zdefiniowana, której tekst RefersTo jest LAMBDA, może być wywołana po nazwie jako =MyFunc(5), domknięcie związane wewnątrz LET może być wywołane jako =LET(f, LAMBDA(x, x*2), f(21)), a środowisko leksykalne przechwycone w momencie definicji podróżuje razem z domknięciem. Tekst formuły odtwarza się wiernie po zapisie i odczycie
To jest funkcja, która odróżnia silnik formuł od parsera formuł. Wszystko przed LAMBDA dało się wyliczyć, przechodząc po drzewie wartości. LAMBDA wymaga stosu zakresów, a gdy masz już stos zakresów, w twojej aplikacji Delphi zaczyna działać cała klasa logiki arkuszy autorstwa użytkowników, a nie tylko w Excelu
Dlaczego większość silników spoza Excela zatrzymuje się na słowie kluczowym LAMBDA?
Ponieważ klasyczny ewaluator arkusza kalkulacyjnego ma dokładnie jeden rodzaj wartości: liczbę, łańcuch znaków, wartość logiczną, błąd albo odwołanie do komórek trzymających te wartości. Nie ma gdzie umieścić funkcji. Gdy Excel 365 wprowadził LAMBDA, dodał typ wartości niosący nazwy parametrów, wyrażenie ciała oraz powiązania widoczne w miejscu, gdzie funkcja została napisana. Silnik bez tego typu potrafi sparsować LAMBDA(x, x*2) i zapisać tekst, ale w chwili, gdy komórka próbuje ją wywołać, nie ma czego wywołać
HotXLS implementuje brakujący element jako wartość domknięcia plus stos zakresów w czasie wykonania. Wywołanie domknięcia zdejmuje na stos jego przechwycone środowisko, potem zdejmuje na stos wartości argumentów pod nazwami parametrów, wylicza ciało, a następnie przycina stos z powrotem do znacznika. Ta kolejność ma znaczenie i wyjaśnia to następna sekcja
Trzy sposoby wywołania LAMBDA
HotXLS rozwiązuje wywołanie nieznanej nazwy funkcji przez trzy ścieżki, sprawdzane po kolei, a wiedza, która się uruchamia, wyjaśnia większość zaskoczeń. Po pierwsze, nazwa związana w bieżącym zakresie LET albo LAMBDA: jeśli f jest lokalnym wiązaniem trzymającym domknięcie, f(21) je stosuje. Po drugie, nazwa zdefiniowana w skoroszycie, której tekst formuły zaczyna się od LAMBDA: MyFunc(5) kompiluje ciało tej nazwy i je stosuje. Po trzecie, klasyczny obsługiwacz funkcji użytkownika, niezmieniony, dla wszystkiego, czego pierwsze dwie ścieżki nie roszczą sobie
Lokalne wiązanie trzymające coś innego niż domknięcie nie jest wywoływalne. Zwiąż f z liczbą 3, a potem napisz f(21), a dostaniesz błąd wartości, a nie próbę mnożenia. Jest to bardziej rygorystyczne, niż zachowałby się język dynamiczny, i celowo: literówka, która zamienia wywołanie funkcji w przypadkowe odwołanie, to cicha błędna odpowiedź, czyli najgorszy wynik, jaki może wyprodukować silnik arkusza kalkulacyjnego
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Funkcja nazwana wielokrotnego użytku, zakres skoroszytu
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Domknięcie związane i zastosowane wewnątrz jednej formuły
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// Zagnieżdżone LET: każde wiązanie jest widoczne dla tych po nim
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 rozwiązuje się cieniowanie, gdy nazwy się zderzają?
Wygrywają parametry. Gdy HotXLS stosuje domknięcie, zdejmuje na stos najpierw przechwycone środowisko leksykalne, a wiązania argumentów na drugim miejscu, więc parametr o nazwie rate przesłania zewnętrzne wiązanie o nazwie rate, a także przesłania identycznie nazwane odwołanie do kolumny w otaczającej formule. Ta kolejność sprawia, że nazwaną funkcję można bezpiecznie ponownie wykorzystać: wywołujący nie może przypadkowo zmienić znaczenia ciała, mając w zakresie podobnie nazwane wiązanie
Liczba argumentów jest sprawdzana, zanim cokolwiek zostanie wyliczone. Wywołanie, którego liczba argumentów nie zgadza się z liczbą parametrów domknięcia, natychmiast zwraca błąd wartości, zamiast wyliczyć część argumentów i dopiero potem zawieść, co utrzymuje wyliczanie wolne od skutków ubocznych naprawdę wolne od częściowej pracy. Stos zakresów jest przycinany z powrotem do znacznika wejścia w bloku finally, więc błąd wewnątrz ciała nie może pozostawić nieaktualnych wiązań widocznych dla następnej formuły
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Sprawdź, co napisał użytkownik, zanim zaufasz ponownemu przeliczeniu
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 nie jest już częściowe
Wcześniejsze wydania HotXLS implementowały LET tylko na tyle, by obsłużyć typowy przypadek pojedynczego wiązania. Obecna implementacja jest kompletna: każde wiązanie jest widoczne dla wszystkich późniejszych wiązań i dla wyrażenia ciała, a zagnieżdżone LET komponuje się normalnie, więc LET(a, 1, b, a+1, LET(c, b*2, c)) wylicza się dokładnie tak, jak wylicza to Excel
Ta kompletność ma większe znaczenie, niż mogłoby się wydawać. LET to sposób, w jaki użytkownicy unikają ponownego obliczania tego samego podwyrażenia pięć razy w jednej formule, więc prawdziwe skoroszyty używają go dokładnie w tych głęboko zagnieżdżonych kształtach, które częściowa implementacja psuje. Jeśli wcześniej obchodziłeś luki, rozwijając wiązania LET przed wyliczeniem, to obejście można teraz porzucić
Przecinek czy średnik: teraz oba
Tekst formuły w HotXLS akceptuje teraz przecinek jako separator argumentów obok klasycznego średnika. To nie jest ustawienie regionalne; to reguła akceptacji w parserze. Ma znaczenie, ponieważ formuły przychodzą z miejsc, których nie kontrolujesz: wklejone ze zgłoszenia wsparcia, skopiowane z dokumentacji, wygenerowane przez skrypt, który wyemitował kanoniczną składnię Excela, zaimportowane z pliku CSV zawierającego łańcuchy formuł
Praktyczny efekt jest taki, że zarówno SUM(A1,A2), jak i SUM(A1;A2) się kompilują. Odtwarzanie po zapisie zachowuje to, czego użyło źródło, więc skoroszyt, który wczytałeś, zapisuje się z powrotem z oryginalnymi separatorami, zamiast być po cichu znormalizowany za plecami użytkownika
Co się odtwarza po zapisie, a co warto sprawdzić
Tekst formuły jest przechowywany dosłownie, więc LAMBDA w nazwie zdefiniowanej przetrwa cykl wczytania i zapisu nienaruszona i otwiera się w Excelu jako ta sama funkcja. Goła LAMBDA zapisana jako wynik komórki, czyli formuła, która wylicza się do domknięcia zamiast do wartości, zachowuje dotychczasowe zachowanie pomijania bez wartości: tekst jest zachowany, żaden buforowany wynik liczbowy nie zostaje dla niej wymyślony. To uczciwy wynik, ponieważ nie ma tam żadnej wartości skalarnej do zbuforowania
Warto przyjąć dwa nawyki. Nadawaj nazwanym lambdom zakres skoroszytu, chyba że masz powód, by tego nie robić, ponieważ funkcja o zakresie arkusza, która znika, gdy arkusz zostaje skopiowany, tworzy błąd nazwy w miejscu odległym od przyczyny; reguły zakresu są omówione w nazwach zdefiniowanych i formułach między arkuszami. A gdy skoroszyt pełen nazwanych lambd jest przeznaczony do raportu, który musi być stabilny, rozważ zamrożenie wyników za pomocą ConvertFormulasToValues, aby konsumenci dalszej części potoku widzieli liczby, a nie funkcje, których mogą nie obsługiwać
Przy ciężkim przeliczaniu ciała LAMBDA są zwykłymi wyrażeniami w grafie zależności i są planowane tak samo jak każda inna formuła, co opisano w przyrostowym przeliczaniu i grafie zależności. Jeśli twój model wywołuje jedną nazwaną funkcję w tysiącach wierszy, kosztem jest ciało, nie mechanizm wywołania, i obowiązują te same porady optymalizacyjne co dla każdej powtarzanej formuły
HotXLS to natywny komponent arkusza kalkulacyjnego dla Delphi i C++Builder, który odczytuje i zapisuje XLS, XLSX i ODS bez Excela ani żadnej automatyzacji Office. Silnik formuł, nazwy zdefiniowane i API przeliczania są udokumentowane na stronie komponentu arkusza kalkulacyjnego HotXLS dla Delphi