HotXLS обчислює Excel LAMBDA як справжнє функціональне значення першого класу. Іменоване ім'я, чий текст RefersTo є LAMBDA, можна викликати за іменем як =MyFunc(5), замикання, зв'язане всередині LET, можна викликати як =LET(f, LAMBDA(x, x*2), f(21)), а лексичне середовище, захоплене в момент визначення, подорожує разом із замиканням. Текст формули записується назад у книгу дослівно
Це саме та функція, яка відрізняє рушій формул від парсера формул. Усе, що було до LAMBDA, можна було обчислити, обходячи дерево значень. LAMBDA вимагає стека областей видимості, і щойно у вас з'являється стек областей видимості, у вашому Delphi-застосунку починає працювати цілий клас логіки електронних таблиць, написаної користувачем, а не лише в Excel
Чому більшість рушіїв поза Excel зупиняються на ключовому слові LAMBDA?
Тому що класичний обчислювач електронних таблиць має рівно один тип значення: число, рядок, булеве значення, помилку або посилання на клітинки, що містять ці значення. Функцію нема куди покласти. Коли Excel 365 запровадив LAMBDA, він додав тип значення, що несе імена параметрів, вираз тіла та прив'язки, видимі там, де його написано. Рушій без такого типу може розібрати LAMBDA(x, x*2) і зберегти текст, але в момент, коли клітинка намагається її викликати, викликати нічого
HotXLS реалізує відсутню частину як значення замикання плюс стек областей видимості під час виконання. Виклик замикання проштовхує його захоплене середовище, потім проштовхує значення аргументів під іменами параметрів, обчислює тіло й обрізає стек назад до позначки. Цей порядок важливий, і наступний розділ пояснює чому
Три способи виклику LAMBDA
HotXLS розв'язує виклик невідомого імені функції через три шляхи, випробувані по черзі, і знання того, який саме спрацював, пояснює більшість сюрпризів. По-перше, ім'я, зв'язане в поточній області видимості LET або LAMBDA: якщо f — локальна прив'язка, що містить замикання, f(21) застосовує її. По-друге, іменоване ім'я на рівні книги, чий текст формули починається з LAMBDA: MyFunc(5) компілює тіло цього імені й застосовує його. По-третє, класичний обробник функцій користувача, без змін, для всього, що не покрили перші два шляхи
Локальна прив'язка, що містить щось інше, ніж замикання, не викликається. Прив'яжіть 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)';
// Замикання, зв'язане й застосоване всередині однієї формули
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;
Як вирішується затінення, коли імена збігаються?
Перемагають параметри. Коли HotXLS застосовує замикання, воно проштовхує захоплене лексичне середовище першим, а прив'язки аргументів — другими, тож параметр з іменем rate затінює зовнішню прив'язку з іменем rate, а також затінює однойменне посилання на стовпець у навколишній формулі. Саме цей порядок робить безпечним повторне використання іменованої функції: викликач не може випадково змінити значення тіла, маючи в області видимості схоже за іменем прив'язку
Арність перевіряється до того, як щось узагалі обчислюється. Виклик, чия кількість аргументів не збігається з кількістю параметрів замикання, одразу повертає помилку значення, замість того щоб обчислити частину аргументів, а потім провалитися, — це тримає обчислення без побічних ефектів справді вільним від часткової роботи. Стек областей видимості обрізається назад до позначки входу в блоці 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) обидва компілюються. Кругова консистентність запису зберігає те, що використовував першоджерело, тож завантажена вами книга записується назад з оригінальними роздільниками, а не нормалізується за спиною користувача
Що зберігається при повторному запису, а що варто перевірити
Текст формули зберігається дослівно, тож LAMBDA в іменованому імені переживає цикл завантаження й збереження неушкодженою й відкривається в Excel як та сама функція. Гола LAMBDA, збережена як результат клітинки, тобто формула, що обчислюється в замикання, а не в значення, зберігає наявну поведінку «пропустити без значення»: текст зберігається, жоден кешований числовий результат для нього не вигадується. Це чесний результат, адже там немає скаляра, який можна було б кешувати
Варто прийняти дві звички. Надавайте іменованим lambda область видимості книги, якщо немає причини робити інакше, бо функція з областю видимості аркуша, що зникає при копіюванні аркуша, спричиняє помилку імені в місці, далекому від причини; правила області видимості розглянуті в статті про іменовані імена та міжаркушеві формули. А коли книга, повна іменованих lambda, призначена для звіту, який має бути стабільним, розгляньте заморожування результатів через ConvertFormulasToValues, щоб подальші споживачі бачили числа, а не функції, які вони можуть не підтримувати
Для важких перерахунків тіла LAMBDA — це звичайні вирази в графі залежностей, і вони плануються так само, як будь-яка інша формула, що описано в статті про інкрементний перерахунок і граф залежностей. Якщо ваша модель викликає одну іменовану функцію на тисячах рядків, вартість — це тіло функції, а не механізм виклику, і тут застосовується та сама порада щодо оптимізації, що й для будь-якої повторюваної формули
HotXLS — це нативний компонент електронних таблиць для Delphi та C++Builder, що читає та записує XLS, XLSX і ODS без Excel чи будь-якої автоматизації Office. Рушій формул, іменовані імена та API перерахунку задокументовані на сторінці HotXLS Delphi spreadsheet component