HotXLS evalúa LAMBDA de Excel como un valor de función de primera clase real. Un nombre definido cuyo texto RefersTo sea una LAMBDA se puede llamar por su nombre como =MyFunc(5), una clausura vinculada dentro de LET se puede llamar como =LET(f, LAMBDA(x, x*2), f(21)), y el entorno léxico capturado en el momento de la definición viaja con la clausura. El texto de la fórmula se conserva íntegro en el libro
Esta es la función que separa un motor de fórmulas de un simple analizador de fórmulas. Todo lo anterior a LAMBDA se podía evaluar recorriendo un árbol de valores. LAMBDA requiere una pila de ámbitos, y en cuanto se tiene una pila de ámbitos, toda una clase de lógica de hoja de cálculo escrita por el usuario empieza a funcionar en su aplicación Delphi y no solo en Excel
¿Por qué se detienen la mayoría de los motores no Excel justo en la palabra LAMBDA?
Porque un evaluador de hoja de cálculo clásico tiene exactamente un tipo de valor: un número, una cadena, un booleano, un error o una referencia a celdas que contienen esos tipos. No hay dónde poner una función. Cuando Excel 365 introdujo LAMBDA, añadió un tipo de valor que lleva nombres de parámetros, una expresión de cuerpo y los enlaces visibles donde se escribió. Un motor sin ese tipo puede analizar LAMBDA(x, x*2) y almacenar el texto, pero en el momento en que una celda intenta llamarla, no hay nada que llamar
HotXLS implementa la pieza que faltaba como un valor de clausura más una pila de ámbitos en tiempo de ejecución. Llamar a una clausura apila su entorno capturado, luego apila los valores de argumento bajo los nombres de parámetro, evalúa el cuerpo y recorta la pila de vuelta a la marca. Ese orden importa, y la siguiente sección explica por qué
Las tres formas en que se llama a una LAMBDA
HotXLS resuelve una llamada a un nombre de función desconocido mediante tres rutas, probadas en orden, y saber cuál se activa explica la mayoría de las sorpresas. Primero, un nombre vinculado en el ámbito actual de LET o LAMBDA: si f es un enlace local que contiene una clausura, f(21) la aplica. Segundo, un nombre definido en el libro cuyo texto de fórmula empiece por LAMBDA: MyFunc(5) compila el cuerpo de ese nombre y lo aplica. Tercero, el gestor clásico de funciones de usuario, sin cambios, para todo lo que las dos primeras rutas no reclaman
Un enlace local que contiene algo distinto de una clausura no es llamable. Vincule f al número 3 y luego escriba f(21) y obtendrá un error de valor, no un intento de multiplicación. Esto es más estricto que lo que sería un lenguaje dinámico, y deliberadamente: un error tipográfico que convierte una llamada de función en una referencia accidental es una respuesta incorrecta silenciosa, el peor resultado que puede producir un motor de hoja de cálculo
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Una función reutilizable con nombre, ámbito de libro
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Una clausura vinculada y aplicada dentro de una fórmula
Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';
// LET anidado: cada enlace es visible para los que vienen después
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;
¿Cómo se resuelve el shadowing cuando los nombres coinciden?
Ganan los parámetros. Cuando HotXLS aplica una clausura, apila primero el entorno léxico capturado y en segundo lugar los enlaces de argumento, así que un parámetro llamado rate hace shadowing sobre un enlace externo también llamado rate y también sobre una referencia de columna con la misma grafía en la fórmula circundante. Ese orden es lo que hace segura la reutilización de una función con nombre: quien la llama no puede cambiar accidentalmente lo que significa el cuerpo teniendo en su ámbito un enlace de nombre parecido
La aridad se comprueba antes de evaluar nada. Una llamada cuyo número de argumentos no coincide con el número de parámetros de la clausura devuelve un error de valor de inmediato, en lugar de evaluar algunos argumentos y fallar después, lo que mantiene genuinamente libre de trabajo parcial la evaluación sin efectos secundarios. La pila de ámbitos se recorta de vuelta a su marca de entrada dentro de un bloque finally, así que un error dentro de un cuerpo no puede dejar enlaces obsoletos visibles para la siguiente fórmula
var
Book: TXLSXWorkbook;
Name: TXLSXDefinedName;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('customer-model.xlsx') = 1 then
begin
// Inspeccionar lo que el usuario escribió antes de confiar en un 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;
LET ya no está incompleto
Las versiones anteriores de HotXLS implementaban LET solo hasta cubrir el caso habitual de un único enlace. La implementación actual es completa: cada enlace es visible para todos los enlaces posteriores y para la expresión de cuerpo, y el LET anidado se compone con normalidad, así que LET(a, 1, b, a+1, LET(c, b*2, c)) se evalúa igual que lo evalúa Excel
Esa completitud importa más de lo que parece. LET es la forma en que los usuarios evitan recalcular la misma subexpresión cinco veces en una fórmula, así que los libros reales lo usan justo en las formas profundamente anidadas donde una implementación parcial falla. Si antes rodeaba las lagunas expandiendo los enlaces de LET antes de la evaluación, ese rodeo ya se puede retirar
Coma o punto y coma: ahora ambos
El texto de fórmula en HotXLS acepta ahora la coma como separador de argumentos junto al punto y coma clásico. No es un ajuste regional; es una regla de aceptación en el analizador. Importa porque las fórmulas llegan de sitios que usted no controla: pegadas de una incidencia de soporte, copiadas de la documentación, generadas por un script que emitió la sintaxis canónica de Excel, importadas de un CSV de cadenas de fórmula
El efecto práctico es que SUM(A1,A2) y SUM(A1;A2) compilan ambas. El ciclo de carga y guardado conserva lo que usara el origen, así que un libro que cargó se escribe de vuelta con sus separadores originales en lugar de normalizarse a espaldas del usuario
Qué se conserva íntegro, y qué comprobar
El texto de la fórmula se almacena literalmente, así que una LAMBDA en un nombre definido sobrevive intacta a un ciclo de carga y guardado y se abre en Excel como la misma función. Una LAMBDA desnuda almacenada como resultado de celda, es decir, una fórmula que evalúa a una clausura en lugar de a un valor, conserva el comportamiento existente de omitir sin valor: el texto se preserva, no se inventa ningún resultado numérico en caché para ella. Ese es el resultado honesto, ya que no hay ningún escalar que almacenar
Merece la pena adoptar dos hábitos. Dé a las lambdas con nombre ámbito de libro salvo que haya un motivo para no hacerlo, porque una función con ámbito de hoja que desaparece cuando se copia una hoja produce un error de nombre en un lugar lejano de la causa; las reglas de ámbito se cubren en los nombres definidos y las fórmulas entre hojas. Y cuando un libro lleno de lambdas con nombre está destinado a un informe que debe ser estable, considere congelar los resultados con ConvertFormulasToValues para que los consumidores posteriores vean números en lugar de funciones que quizá no soporten
Para recálculos pesados, los cuerpos de LAMBDA son expresiones ordinarias en el grafo de dependencias y se programan como cualquier otra fórmula, algo descrito en el recálculo incremental y el grafo de dependencias. Si su modelo llama a una función con nombre a través de miles de filas, el coste está en el cuerpo, no en la maquinaria de la llamada, y se aplica el mismo consejo de optimización que para cualquier fórmula repetida
HotXLS es un componente de hoja de cálculo nativo para Delphi y C++Builder que lee y escribe XLS, XLSX y ODS sin Excel ni ninguna automatización de Office. El motor de fórmulas, los nombres definidos y la API de recálculo están documentados en la página de HotXLS para Delphi