HotXLS evalúa LAMBDA de Excel como un valor de función de primera clase real. Un nombre definido cuyo texto RefersTo es un LAMBDA puede llamarse por nombre como =MyFunc(5), un closure vinculado dentro de LET puede llamarse como =LET(f, LAMBDA(x, x*2), f(21)), y el entorno léxico capturado en el momento de la definición viaja con el closure. El texto de la fórmula se preserva íntegramente en el round-trip hacia el libro de trabajo
Esta es la función que separa un motor de fórmulas de un simple analizador de fórmulas. Todo antes de LAMBDA podía evaluarse recorriendo un árbol de valores. LAMBDA requiere una pila de alcances (scope stack), y una vez que usted tiene una pila de alcances, toda una clase de lógica de hojas de cálculo escrita por el usuario empieza a funcionar en su aplicación Delphi y no solo en Excel
¿Por qué la mayoría de los motores que no son Excel se detienen en la palabra clave LAMBDA?
Porque un evaluador de hojas 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 valores. No hay dónde poner una función. Cuando Excel 365 introdujo LAMBDA, agregó un tipo de valor que lleva nombres de parámetros, una expresión de cuerpo, y los enlaces visibles donde fue escrito. Un motor sin ese tipo puede analizar LAMBDA(x, x*2) y almacenar el texto, pero en el momento en que una celda intenta llamarlo, no hay nada que llamar
HotXLS implementa la pieza faltante como un valor closure más una pila de alcances en tiempo de ejecución. Llamar a un closure empuja su entorno capturado, luego empuja los valores de los argumentos bajo los nombres de parámetro, evalúa el cuerpo, y trunca 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 un LAMBDA
HotXLS resuelve una llamada a un nombre de función desconocido a través de tres rutas, probadas en orden, y saber cuál se activa explica la mayoría de las sorpresas. Primero, un nombre vinculado en el alcance LET o LAMBDA actual: si f es un enlace local que contiene un closure, f(21) lo aplica. Segundo, un nombre definido del libro de trabajo cuyo texto de fórmula comienza con LAMBDA: MyFunc(5) compila el cuerpo de ese nombre y lo aplica. Tercero, el manejador clásico de función de usuario, sin cambios, para todo lo que las primeras dos rutas no reclaman
Un enlace local que contiene algo distinto de un closure no es invocable. 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 de lo que sería un lenguaje dinámico, y de forma deliberada: un error de tipeo que convierte una llamada a función en una referencia accidental es una respuesta incorrecta silenciosa, que es el peor resultado que puede producir un motor de hojas de cálculo
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Model');
// Una función con nombre reutilizable, alcance de libro de trabajo
Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');
Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';
// Un closure vinculado y aplicado dentro de una sola 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 colisionan?
Ganan los parámetros. Cuando HotXLS aplica un closure, empuja primero el entorno léxico capturado y segundo los enlaces de argumentos, así que un parámetro llamado rate hace sombra a un enlace externo llamado rate y también hace sombra a una referencia de columna con el mismo nombre en la fórmula circundante. Ese orden es lo que hace segura la reutilización de una función con nombre: quien llama no puede cambiar accidentalmente el significado del cuerpo teniendo en su alcance un enlace con nombre similar
La aridad se verifica antes de evaluar nada. Una llamada cuyo número de argumentos no coincide con el número de parámetros del closure devuelve inmediatamente un error de valor, en lugar de evaluar algunos argumentos y luego fallar, lo cual mantiene genuinamente libre de trabajo parcial la evaluación sin efectos secundarios. La pila de alcances se trunca 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
Versiones anteriores de HotXLS implementaban LET solo hasta cubrir el caso común de un solo enlace. La implementación actual es completa: cada enlace es visible para todos los enlaces posteriores y para la expresión del cuerpo, y el LET anidado compone con normalidad, así que LET(a, 1, b, a+1, LET(c, b*2, c)) se evalúa de la misma forma en que Excel lo evalúa
Esa completitud importa más de lo que suena. LET es cómo los usuarios evitan recalcular la misma subexpresión cinco veces en una fórmula, así que los libros de trabajo reales lo usan exactamente en las formas profundamente anidadas donde una implementación parcial falla. Si antes evitaba estos vacíos expandiendo los enlaces LET antes de la evaluación, ese workaround ya puede eliminarse
Coma o punto y coma: ahora ambos
El texto de fórmula en HotXLS ahora acepta la coma como separador de argumentos junto al punto y coma clásico. Esto no es una configuración regional; es una regla de aceptación en el analizador. Importa porque las fórmulas llegan de lugares que usted no controla: pegadas desde un ticket de soporte, copiadas desde documentación, generadas por un script que emitió la sintaxis canónica de Excel, importadas desde un CSV de cadenas de fórmula
El efecto práctico es que tanto SUM(A1,A2) como SUM(A1;A2) compilan. El round-trip preserva el separador que use la fuente, así que un libro de trabajo que usted cargó se guarda de vuelta con sus separadores originales en lugar de normalizarse a espaldas del usuario
Qué se preserva en el round-trip, y qué verificar
El texto de la fórmula se almacena textualmente, así que un LAMBDA en un nombre definido sobrevive un ciclo de carga y guardado intacto, y abre en Excel como la misma función. Un LAMBDA sin nombre almacenado como resultado de celda, es decir, una fórmula que evalúa a un closure en lugar de a un valor, conserva el comportamiento existente de omitir-sin-valor: el texto se preserva, y no se inventa ningún resultado numérico en caché para él. Ese es el resultado honesto, ya que no hay ningún escalar que cachear
Vale la pena adoptar dos hábitos. Dé alcance de libro de trabajo a los lambdas con nombre a menos que haya una razón para no hacerlo, porque una función con alcance de hoja que desaparece cuando se copia una hoja produce un error de nombre en un lugar muy lejos de la causa; las reglas de alcance se cubren en nombres definidos y fórmulas entre hojas. Y cuando un libro de trabajo lleno de lambdas con nombre está destinado a un reporte que debe ser estable, considere congelar los resultados con ConvertFormulasToValues para que los consumidores posteriores vean números en lugar de funciones que tal vez no soporten
Para recálculos intensivos, los cuerpos de LAMBDA son expresiones ordinarias en el grafo de dependencias y se programan como cualquier otra fórmula, lo cual se describe en 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 costo es el cuerpo, no la maquinaria de llamada, y aplica el mismo consejo de optimización que para cualquier fórmula repetida
HotXLS es un componente de hojas 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 del componente de hojas de cálculo HotXLS Delphi