HotXLS는 Excel LAMBDA를 진정한 일급 함수 값으로 평가합니다. RefersTo 텍스트가 LAMBDA인 정의된 이름은 =MyFunc(5)처럼 이름으로 호출할 수 있고, LET 안에 바인딩된 클로저는 =LET(f, LAMBDA(x, x*2), f(21))처럼 호출할 수 있으며, 정의 시점에 캡처된 어휘적 환경이 그 클로저와 함께 이동합니다. 수식 텍스트는 통합 문서 안으로 그대로 왕복됩니다
이것이 수식 엔진과 수식 파서를 가르는 기능입니다. LAMBDA 이전의 모든 것은 값들의 트리를 순회하는 것만으로 평가할 수 있었습니다. LAMBDA는 스코프 스택을 요구하며, 일단 스코프 스택을 갖추고 나면 사용자가 작성한 스프레드시트 로직의 한 부류 전체가 Excel 안에서만이 아니라 여러분의 Delphi 애플리케이션 안에서도 작동하기 시작합니다
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라는 이름의 외부 바인딩을 가리며, 주변 수식에서 철자가 같은 열 참조도 가립니다. 바로 이 순서가 이름 붙은 함수를 안전하게 재사용할 수 있게 해줍니다. 호출자는 스코프 안에 비슷한 이름의 바인딩을 두는 것만으로 본문의 의미를 실수로 바꿀 수 없습니다
인자 개수(arity)는 무엇이든 평가되기 전에 검사됩니다. 인자 개수가 클로저의 매개변수 개수와 맞지 않는 호출은 일부 인자를 평가하고 나서 실패하는 대신 즉시 값 오류를 반환하며, 이는 부수 효과 없는 평가를 부분 작업으로부터 진정으로 자유롭게 유지합니다. 스코프 스택은 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을 사용합니다. 이전에 평가 전에 LET 바인딩을 펼쳐서 그 빈틈을 우회했다면, 이제 그 우회책은 없애도 됩니다
쉼표인가 세미콜론인가: 이제 둘 다
이제 HotXLS의 수식 텍스트는 기존의 세미콜론과 함께 쉼표도 인자 구분자로 받아들입니다. 이는 로케일 설정이 아니라 파서 안의 수용 규칙입니다. 이것이 중요한 이유는 수식이 여러분이 통제하지 않는 곳에서 들어오기 때문입니다. 지원 티켓에서 붙여넣거나, 문서에서 복사하거나, Excel의 표준 문법을 내보내는 스크립트가 생성하거나, 수식 문자열의 CSV에서 가져온 경우입니다
실질적인 효과는 SUM(A1,A2)와 SUM(A1;A2)가 둘 다 컴파일된다는 것입니다. 왕복 처리는 원본이 사용한 것을 그대로 보존하므로, 여러분이 로드한 통합 문서는 사용자 몰래 정규화되는 대신 원래의 구분자 그대로 다시 기록됩니다
무엇이 왕복되며, 무엇을 확인해야 하는가
수식 텍스트는 그대로 저장되므로, 정의된 이름 안의 LAMBDA는 로드와 저장 주기를 온전히 견뎌내고 Excel에서 같은 함수로 열립니다. 셀 결과로 저장된 순수 LAMBDA, 즉 값이 아니라 클로저로 평가되는 수식은 기존의 값 없이 건너뛰는 동작을 유지합니다. 텍스트는 보존되고, 그것을 위해 캐시된 숫자 결과가 지어내지지 않습니다. 캐시할 스칼라가 없으므로 그것이 정직한 결과입니다
습관으로 삼을 만한 것이 두 가지 있습니다. 그렇지 않을 이유가 없다면 이름 붙은 람다에는 통합 문서 범위를 부여하십시오. 시트가 복사될 때 사라지는 시트 범위 함수는 원인에서 멀리 떨어진 곳에서 이름 오류를 만들어내기 때문입니다. 범위 규칙은 정의된 이름과 시트 간 수식에서 다룹니다. 그리고 이름 붙은 람다로 가득한 통합 문서가 안정적이어야 하는 보고서로 향할 때는, 다운스트림 소비자가 지원하지 않을 수도 있는 함수 대신 숫자를 보도록 ConvertFormulasToValues로 결과를 고정하는 것을 고려하십시오
대량 재계산의 경우, LAMBDA 본문은 의존성 그래프 안에서 평범한 표현식이며 다른 어떤 수식과도 마찬가지로 스케줄링됩니다. 이는 증분 재계산과 의존성 그래프에서 설명합니다. 여러분의 모델이 수천 개의 행에 걸쳐 이름 붙은 함수 하나를 호출한다면, 비용은 호출 메커니즘이 아니라 본문에 있으며, 반복되는 어떤 수식에나 적용되는 것과 같은 최적화 조언이 적용됩니다
HotXLS는 Excel이나 어떤 Office 자동화도 없이 XLS, XLSX, ODS를 읽고 쓰는 네이티브 Delphi와 C++Builder 스프레드시트 컴포넌트입니다. 수식 엔진, 정의된 이름, 재계산 API는 HotXLS Delphi 스프레드시트 컴포넌트 페이지에 문서화되어 있습니다