Tehnički članak

LAMBDA i LET u Delphi-ju: HotXLS zatvaranja formula

HotXLS izračunava Excel LAMBDA kao pravu funkcijsku vrednost prve klase (first-class). Definisano ime čiji je RefersTo tekst LAMBDA može se pozvati po imenu kao =MyFunc(5), zatvaranje (closure) vezano unutar LET-a može se pozvati kao =LET(f, LAMBDA(x, x*2), f(21)), a leksičko okruženje zahvaćeno u trenutku definisanja putuje zajedno sa zatvaranjem. Tekst formule se vraća u radnu svesku bukvalno, bez izmena

Ovo je funkcija koja razdvaja mehanizam za formule od parsera formula. Sve pre LAMBDA-e moglo je da se izračuna prolaskom kroz stablo vrednosti. LAMBDA zahteva steg opsega (scope stack), a čim imate steg opsega, čitava klasa logike koju su korisnici sami napisali u tabeli počinje da radi u vašoj Delphi aplikaciji, a ne samo u Excel-u

Zašto se većina mehanizama van Excel-a zaustavlja na ključnoj reči LAMBDA?

Zato što klasičan mehanizam za izračunavanje tabela ima tačno jednu vrstu vrednosti: broj, nisku, boolean, grešku, ili referencu na ćelije koje sadrže te vrednosti. Nema gde da se smesti funkcija. Kada je Excel 365 uveo LAMBDA-u, dodao je tip vrednosti koji nosi imena parametara, izraz tela funkcije, i veze vidljive tamo gde je napisana. Mehanizam bez tog tipa može da parsira LAMBDA(x, x*2) i sačuva tekst, ali u trenutku kada ćelija pokuša da je pozove, nema šta da se pozove

HotXLS implementira nedostajući deo kao vrednost zatvaranja plus runtime steg opsega. Pozivanje zatvaranja gura njegovo zahvaćeno okruženje, zatim gura vrednosti argumenata pod imenima parametara, izračunava telo, i skraćuje steg nazad do markera. Taj redosled je bitan, a naredni odeljak objašnjava zašto

Tri načina na koja se LAMBDA poziva

HotXLS razrešava poziv nepoznatog imena funkcije kroz tri putanje, isprobane redom, a znanje koja se od njih aktivira objašnjava većinu iznenađenja. Prvo, ime vezano u trenutnom opsegu LET-a ili LAMBDA-e: ako je f lokalna veza koja drži zatvaranje, f(21) ga primenjuje. Drugo, definisano ime radne sveske čiji tekst formule počinje sa LAMBDA: MyFunc(5) kompajlira telo tog imena i primenjuje ga. Treće, klasičan rukovalac korisničkim funkcijama, nepromenjen, za sve ono što prve dve putanje ne preuzmu

Lokalna veza koja drži nešto drugo osim zatvaranja nije pozivljiva. Vežite f za broj 3, a zatim napišite f(21), i dobićete grešku vrednosti, a ne pokušaj množenja. Ovo je strože nego što bi bio dinamički jezik, i to namerno: greška u kucanju koja pretvori poziv funkcije u slučajnu referencu jeste tiho pogrešan odgovor, a to je najgori mogući ishod koji mehanizam za tabele može da proizvede

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Model');

    // Funkcija za ponovnu upotrebu, imenovana, opseg radne sveske
    Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');

    Sheet.Cells[2, 2].Formula := 'NetOf(1250, 0.19)';

    // Zatvaranje vezano i primenjeno unutar jedne formule
    Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';

    // Ugnežđen LET: svaka veza je vidljiva onima posle nje
    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;

Kako se rešava zasenčenje kada se imena sudare?

Parametri pobeđuju. Kada HotXLS primenjuje zatvaranje, prvo gura zahvaćeno leksičko okruženje, a zatim veze argumenata, tako da parametar imenovan rate zasenjuje spoljnu vezu imenovanu rate, a takođe zasenjuje referencu kolone istog naziva u okolnoj formuli. Taj redosled je ono što čini imenovanu funkciju bezbednom za ponovnu upotrebu: pozivalac ne može slučajno da promeni šta telo znači time što ima slično imenovanu vezu u opsegu

Aritet se proverava pre nego što se bilo šta izračuna. Poziv čiji se broj argumenata ne poklapa sa brojem parametara zatvaranja odmah vraća grešku vrednosti, umesto da izračuna neke argumente pa tek onda otkaže, što čini izračunavanje bez sporednih efekata zaista oslobođeno od delimičnog posla. Steg opsega se skraćuje nazad do ulaznog markera unutar finally bloka, tako da greška unutar tela ne može da ostavi zastarele veze vidljive sledećoj formuli

var
  Book: TXLSXWorkbook;
  Name: TXLSXDefinedName;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('customer-model.xlsx') = 1 then
    begin
      // Proverite šta je korisnik napisao pre nego što poverujete ponovnom izračunavanju
      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 više nije delimičan

Ranija HotXLS izdanja implementirala su LET samo dovoljno da pokriju uobičajen slučaj sa jednom vezom. Trenutna implementacija je potpuna: svaka veza je vidljiva svim kasnijim vezama i izrazu tela, a ugnežđen LET se normalno sastavlja, tako da se LET(a, 1, b, a+1, LET(c, b*2, c)) izračunava onako kako ga izračunava i Excel

Ta potpunost je važnija nego što zvuči. LET je način na koji korisnici izbegavaju ponovno izračunavanje istog podizraza pet puta u jednoj formuli, pa ga stvarne radne sveske koriste baš u onim duboko ugnežđenim oblicima koje delimična implementacija pogrešno obrađuje. Ako ste ranije zaobilazili praznine tako što ste širili LET veze pre izračunavanja, to zaobilaženje sada može da ode

Zarez ili tačka-zapeta: oboje, sada

Tekst formule u HotXLS-u sada prihvata zarez kao separator argumenata pored klasične tačke-zapete. Ovo nije podešavanje lokalizacije; to je pravilo prihvatanja u parseru. Bitno je jer formule stižu sa mesta koja ne kontrolišete: nalepljene iz tiketa podrške, kopirane iz dokumentacije, generisane skriptom koja je emitovala Excel-ovu kanonsku sintaksu, uvezene iz CSV-a niski formula

Praktičan efekat je da se i SUM(A1,A2) i SUM(A1;A2) kompajliraju. Vraćanje u izvoran oblik čuva ono što je izvor koristio, tako da se radna sveska koju ste učitali upisuje nazad sa svojim originalnim separatorima, umesto da bude normalizovana iza leđa korisnika

Šta se vraća u izvoran oblik, i šta proveriti

Tekst formule se čuva bukvalno, tako da LAMBDA u definisanom imenu preživi ciklus učitavanja i snimanja netaknuta i otvara se u Excel-u kao ista funkcija. Gola LAMBDA sačuvana kao rezultat ćelije, odnosno formula koja se izračunava u zatvaranje umesto u vrednost, zadržava postojeće ponašanje preskakanja-bez-vrednosti: tekst se čuva, a za nju se ne izmišlja nikakav keširan numerički rezultat. To je pošten ishod, pošto nema skalara koji bi se keširao

Vredi usvojiti dve navike. Dajte imenovanim lambdama opseg radne sveske, osim ako postoji razlog da to ne uradite, jer funkcija sa opsegom lista koja nestane kada se list kopira proizvodi grešku imena na mestu daleko od uzroka; pravila opsega pokrivena su u definisanim imenima i formulama preko više listova. A kada je radna sveska puna imenovanih lambdi namenjena izveštaju koji mora biti stabilan, razmotrite zamrzavanje rezultata pomoću ConvertFormulasToValues, tako da nizvodni potrošači vide brojeve umesto funkcija koje možda ne podržavaju

Za intenzivno ponovno izračunavanje, tela LAMBDA-e su obični izrazi u grafu zavisnosti i planiraju se kao bilo koja druga formula, što je opisano u inkrementalnom ponovnom izračunavanju i grafu zavisnosti. Ako vaš model poziva jednu imenovanu funkciju kroz hiljade redova, trošak je telo, a ne mehanizam poziva, i primenjuje se ista optimizaciona preporuka kao i za bilo koju ponovljenu formulu

HotXLS je izvorna Delphi i C++Builder komponenta za tabele koja čita i piše XLS, XLSX i ODS bez Excel-a ili bilo kakve automatizacije Office-a. Mehanizam za formule, definisana imena i API za ponovno izračunavanje dokumentovani su na stranici HotXLS Delphi komponente za tabele