HotXLS изчислява Excel LAMBDA като истинска функционална стойност от първи клас. Именувано определение, чийто текст RefersTo е LAMBDA, може да бъде извикано по име като =MyFunc(5), closure, обвързан вътре в LET, може да бъде извикан като =LET(f, LAMBDA(x, x*2), f(21)), а лексикалната среда, прихваната в момента на дефиниране, пътува заедно с closure-а. Текстът на формулата се съхранява дословно
Това е функцията, която отличава формулен движок от формулен парсър. Всичко преди LAMBDA можеше да бъде изчислено чрез обхождане на дърво от стойности. LAMBDA изисква стек от обхвати, а щом имате стек от обхвати, цял клас логика, авторска от потребители на електронни таблици, започва да работи във вашето Delphi приложение, а не само в Excel
Защо повечето не-Excel движоци спират до ключовата дума LAMBDA?
Защото класически изчислител на електронни таблици има точно един вид стойност: число, низ, булева стойност, грешка или референция към клетки, съдържащи тях. Няма къде да се постави функция. Когато Excel 365 въведе LAMBDA, той добави тип стойност, който носи имена на параметри, тяло-израз и обвързванията, видими там, където е бил написан. Движок без този тип може да парсне LAMBDA(x, x*2) и да съхрани текста, но в момента, в който клетка се опита да го извика, няма какво да се извика
HotXLS имплементира липсващата част като closure стойност плюс runtime стек от обхвати. Извикването на closure бута обвитата среда, после бута стойностите на аргументите под имената на параметрите, изчислява тялото и връща стека до маркера. Този ред има значение, а следващият раздел обяснява защо
Трите начина, по които LAMBDA бива извикана
HotXLS разрешава извикване на непознато име на функция през три пътя, изпробвани поред, а знанието кой се задейства обяснява повечето изненади. Първо, име, обвързано в текущия обхват на LET или LAMBDA: ако f е локално обвързване, съдържащо closure, f(21) го прилага. Второ, именувано определение в книгата, чийто текст на формула започва с LAMBDA: MyFunc(5) компилира тялото на това определение и го прилага. Трето, класическият user-function handler, непроменен, за всичко, което първите два пътя не претендират
Локално обвързване, съдържащо нещо различно от closure, не е извикваемо. Обвържете f с числото 3 и после напишете f(21) и ще получите грешка стойност, не опит за умножение. Това е по-строго от онова, което динамичен език би направил, и то умишлено: правописна грешка, превръщаща извикване на функция в случайна референция, е тиха грешен отговор, което е най-лошият резултат, който движок за електронни таблици може да произведе
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Преизползваема именувана функция, обхват на книгата
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Closure, обвързан и приложен в една формула
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// Вложен LET: всяко обвързване е видимо за онези след него
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;
Как се разрешава shadowing, когато имена се сблъскат?
Параметрите печелят. Когато HotXLS приложи closure, той първо бута прихванатата лексикална среда, а второ — обвързванията на аргументите, така че параметър, именуван rate, засенчва външно обвързване, именувано rate, а също засенчва еднакво изписана референция към колона в околната формула. Точно този ред прави именувана функция безопасна за преизползване: извикващият не може случайно да промени какво означава тялото, като има подобно именувано обвързване в обхват
Арността се проверява, преди каквото и да е да се изчисли. Извикване, чийто брой аргументи не съвпада с броя на параметрите на closure-а, връща грешка стойност веднага, вместо да изчисли някои аргументи и после да се провали, което пази изчислението без странични ефекти наистина свободно от частична работа. Стекът от обхвати се връща до маркера си при влизане вътре в блок finally, така че грешка в тяло не може да остави остарели обвързвания видими за следващата формула
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Проверете какво е написал потребителят, преди да се доверите на преизчисление
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 вече не е частичен
По-ранните версии на HotXLS имплементираха LET само дотолкова, доколкото да обработят обичайния случай с едно обвързване. Текущата имплементация е пълна: всяко обвързване е видимо за всички следващи обвързвания и за тялото-израз, а вложеният LET се съчетава нормално, така че LET(a, 1, b, a+1, LET(c, b*2, c)) се изчислява така, както Excel го изчислява
Тази пълнота има по-голямо значение, отколкото звучи. LET е начинът, по който потребителите избягват преизчисляването на един и същ подизраз пет пъти в една формула, така че реални книги го използват точно в дълбоко вложените форми, при които частична имплементация греши. Ако преди сте заобикаляли пропуски чрез разгъване на LET обвързванията преди изчисление, тази заобиколка вече може да отпадне
Запетая или точка и запетая: и двете вече
Текстът на формулите в HotXLS вече приема запетая като разделител на аргументи наред с класическата точка и запетая. Това не е локализационна настройка; това е правило за приемане в парсъра. Има значение, защото формули пристигат от места, които не контролирате: копирани от заявка за поддръжка, взети от документация, генерирани от скрипт, излъчил каноничния синтаксис на Excel, импортирани от CSV с текстове на формули
Практическият ефект е, че SUM(A1,A2) и SUM(A1;A2) и двете се компилират. Round-trip-ът запазва каквото е използвал източникът, така че книга, която сте заредили, се записва обратно с оригиналните си разделители, вместо да бъде нормализирана зад гърба на потребителя
Какво се съхранява round-trip и какво да проверите
Текстът на формулата се съхранява дословно, така че LAMBDA в именувано определение оцелява цикъл на зареждане и запис непокътнат и се отваря в Excel като същата функция. Гола LAMBDA, съхранена като резултат на клетка, тоест формула, изчисляваща се до closure, а не до стойност, запазва съществуващото поведение skip-without-value: текстът се запазва, не се измисля кеширан числов резултат за нея. Това е честният изход, тъй като няма скалар за кеширане
Два навика си струва да се възприемат. Давайте на именуваните lambda обхват на книгата, освен ако няма причина да не го правите, защото функция с обхват на лист, която изчезва при копиране на лист, произвежда грешка за име на място далеч от причината; правилата за обхват са разгледани в именуваните определения и формулите между листове. А когато книга, пълна с именувани lambda, е предназначена за отчет, който трябва да е стабилен, обмислете замразяването на резултатите с ConvertFormulasToValues, така че downstream потребителите да виждат числа, а не функции, които може да не поддържат
За тежко преизчисление телата на LAMBDA са обикновени изрази в графа на зависимостите и се планират като всяка друга формула, което е описано в инкременталното преизчисление и графа на зависимостите. Ако моделът ви извиква една именувана функция през хиляди редове, разходът е тялото, не машинерията на извикването, и важи същият съвет за оптимизация, както за всяка повтаряща се формула
HotXLS е native Delphi и C++Builder компонент за електронни таблици, който чете и записва XLS, XLSX и ODS без Excel или каквато и да е Office automation. Формулният движок, именуваните определения и API-то за преизчисление са документирани на страницата на HotXLS Delphi компонента за електронни таблици