O HotXLS avalia o LAMBDA do Excel como um verdadeiro valor de função de primeira classe. Um nome definido cujo texto RefersTo seja um LAMBDA pode ser chamado pelo nome como =MyFunc(5), uma closure vinculada dentro de LET pode ser chamada como =LET(f, LAMBDA(x, x*2), f(21)), e o ambiente léxico capturado no momento da definição viaja com a closure. O texto da fórmula é preservado integralmente na ida e volta pela pasta de trabalho
Esta é a funcionalidade que separa um motor de fórmulas de um simples analisador de fórmulas. Tudo antes do LAMBDA podia ser avaliado percorrendo uma árvore de valores. O LAMBDA exige uma pilha de âmbitos, e assim que se tem uma pilha de âmbitos, toda uma classe de lógica de folha de cálculo criada por utilizadores começa a funcionar na sua aplicação Delphi e não apenas no Excel
Porque param a maioria dos motores fora do Excel na palavra-chave LAMBDA?
Porque um avaliador de folha de cálculo clássico tem exatamente um tipo de valor: um número, uma string, um booleano, um erro, ou uma referência a células que contêm esses valores. Não há onde colocar uma função. Quando o Excel 365 introduziu o LAMBDA, acrescentou um tipo de valor que transporta nomes de parâmetros, uma expressão de corpo, e as vinculações visíveis no local onde foi escrito. Um motor sem esse tipo consegue analisar LAMBDA(x, x*2) e guardar o texto, mas no momento em que uma célula tenta chamá-lo, não há nada para chamar
O HotXLS implementa a peça em falta como um valor de closure mais uma pilha de âmbitos em tempo de execução. Chamar uma closure empilha o seu ambiente capturado, depois empilha os valores dos argumentos sob os nomes dos parâmetros, avalia o corpo, e trunca a pilha de volta até à marca. Essa ordem importa, e a secção seguinte explica porquê
As três formas de chamar um LAMBDA
O HotXLS resolve uma chamada a um nome de função desconhecido através de três caminhos, tentados por ordem, e saber qual deles se ativa explica a maioria das surpresas. Primeiro, um nome vinculado no âmbito atual de LET ou LAMBDA: se f for uma vinculação local que contém uma closure, f(21) aplica-a. Segundo, um nome definido na pasta de trabalho cujo texto de fórmula comece por LAMBDA: MyFunc(5) compila o corpo desse nome e aplica-o. Terceiro, o processador clássico de funções de utilizador, inalterado, para tudo o que os dois primeiros caminhos não reivindiquem
Uma vinculação local que contenha algo diferente de uma closure não é invocável. Vincule f ao número 3 e depois escreva f(21) e obterá um erro de valor, não uma tentativa de multiplicação. Isto é mais rigoroso do que uma linguagem dinâmica seria, e deliberadamente: um erro ortográfico que transforma uma chamada de função numa referência acidental é uma resposta errada silenciosa, o pior resultado que um motor de folha de cálculo pode produzir
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Uma função nomeada reutilizável, âmbito de pasta de trabalho
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Uma closure vinculada e aplicada dentro de uma única fórmula
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// LET aninhado: cada vinculação é visível para as seguintes
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;
Como se resolve o sombreamento quando os nomes colidem?
Os parâmetros ganham. Quando o HotXLS aplica uma closure, empilha primeiro o ambiente léxico capturado e depois as vinculações dos argumentos, pelo que um parâmetro chamado rate sombreia uma vinculação exterior chamada rate e também sombreia uma referência de coluna com a mesma grafia na fórmula circundante. Essa ordem é o que torna segura a reutilização de uma função nomeada: quem chama não pode alterar acidentalmente o significado do corpo por ter uma vinculação com nome semelhante em âmbito
A aridade é verificada antes de qualquer avaliação. Uma chamada cujo número de argumentos não corresponda ao número de parâmetros da closure devolve imediatamente um erro de valor, em vez de avaliar alguns argumentos e depois falhar, o que mantém a avaliação sem efeitos secundários genuinamente livre de trabalho parcial. A pilha de âmbitos é truncada de volta à sua marca de entrada num bloco finally, pelo que um erro dentro de um corpo não pode deixar vinculações obsoletas visíveis para a fórmula seguinte
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Inspecionar o que o utilizador escreveu antes de confiar num recálculo
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;
O LET deixou de ser parcial
As versões anteriores do HotXLS implementavam o LET apenas até ao ponto de tratar o caso comum de vinculação única. A implementação atual é completa: cada vinculação é visível para todas as vinculações posteriores e para a expressão do corpo, e o LET aninhado compõe-se normalmente, pelo que LET(a, 1, b, a+1, LET(c, b*2, c)) se avalia da mesma forma que o Excel a avalia
Essa completude importa mais do que parece. O LET é a forma como os utilizadores evitam recalcular a mesma subexpressão cinco vezes numa única fórmula, pelo que as pastas de trabalho reais utilizam-no exatamente nas formas profundamente aninhadas em que uma implementação parcial falha. Se anteriormente contornava lacunas expandindo as vinculações LET antes da avaliação, esse contorno já não é necessário
Vírgula ou ponto e vírgula: agora, ambos
O texto de fórmulas no HotXLS aceita agora a vírgula como separador de argumentos ao lado do clássico ponto e vírgula. Isto não é uma definição regional; é uma regra de aceitação no analisador. Importa porque as fórmulas chegam de locais que não controla: coladas de um pedido de suporte, copiadas de documentação, geradas por um script que emitiu a sintaxe canónica do Excel, importadas de um CSV de strings de fórmulas
O efeito prático é que SUM(A1,A2) e SUM(A1;A2) compilam ambos. A preservação na ida e volta mantém o que a origem utilizou, pelo que uma pasta de trabalho carregada é gravada novamente com os seus separadores originais em vez de ser normalizada sem o conhecimento do utilizador
O que se preserva, e o que verificar
O texto da fórmula é armazenado literalmente, pelo que um LAMBDA num nome definido sobrevive a um ciclo de carregamento e gravação intacto e abre no Excel como a mesma função. Um LAMBDA puro guardado como resultado de célula, ou seja, uma fórmula que se avalia como uma closure em vez de um valor, mantém o comportamento existente de ignorar sem valor: o texto é preservado, não se inventa nenhum resultado numérico em cache para ele. Esse é o resultado honesto, uma vez que não há qualquer escalar para colocar em cache
Vale a pena adotar dois hábitos. Dê às lambdas nomeadas âmbito de pasta de trabalho, salvo se houver uma razão para não o fazer, porque uma função com âmbito de folha que desaparece quando uma folha é copiada produz um erro de nome num local distante da causa; as regras de âmbito estão abordadas em nomes definidos e fórmulas entre folhas. E quando uma pasta de trabalho cheia de lambdas nomeadas se destina a um relatório que tem de ser estável, considere fixar os resultados com ConvertFormulasToValues, para que os consumidores a jusante vejam números em vez de funções que podem não suportar
Para recálculo intensivo, os corpos de LAMBDA são expressões comuns no grafo de dependências e são agendados como qualquer outra fórmula, o que é descrito em recálculo incremental e o grafo de dependências. Se o seu modelo chamar uma função nomeada em milhares de linhas, o custo está no corpo, não no mecanismo de chamada, e aplica-se o mesmo conselho de otimização válido para qualquer fórmula repetida
O HotXLS é um componente de folha de cálculo nativo para Delphi e C++Builder que lê e escreve XLS, XLSX e ODS sem Excel nem qualquer automação do Office. O motor de fórmulas, os nomes definidos e a API de recálculo estão documentados na página do componente HotXLS para Delphi