Tehnički članak

LAMBDA i LET u Delphiju: HotXLS closure formule

HotXLS evaluira Excel LAMBDA kao pravu vrijednost funkcije prve klase. Definirano ime čiji je tekst RefersTo LAMBDA može se pozvati po imenu kao =MyFunc(5), closure vezan 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 definicije putuje zajedno s closureom. Tekst formule vraća se u radnu knjigu doslovno, nepromijenjen

Ovo je značajka koja razdvaja mehanizam za formule od običnog parsera formula. Sve prije LAMBDA-e moglo se evaluirati prolaskom kroz stablo vrijednosti. LAMBDA zahtijeva stog opsega, a čim imate stog opsega, cijela klasa korisnički napisane logike proračunskih tablica počinje raditi u vašoj Delphi aplikaciji, a ne samo u Excelu

Zašto većina mehanizama koji nisu Excel stane kod ključne riječi LAMBDA?

Zato što klasični evaluator proračunske tablice ima točno jednu vrstu vrijednosti: broj, niz znakova, boolean, pogrešku ili referencu na ćelije koje sadrže te vrijednosti. Nema mjesta gdje bi se smjestila funkcija. Kada je Excel 365 uveo LAMBDA-u, dodao je tip vrijednosti koji nosi imena parametara, izraz tijela i vezanja vidljiva na mjestu gdje je napisana. Mehanizam bez tog tipa može parsirati LAMBDA(x, x*2) i pohraniti tekst, ali u trenutku kada ćelija pokuša tu funkciju pozvati, nema što pozvati

HotXLS implementira taj nedostajući dio kao vrijednost closurea plus stog opsega tijekom izvođenja. Pozivanje closurea gura na stog njegovo zahvaćeno okruženje, zatim gura vrijednosti argumenata pod imenima parametara, evaluira tijelo i skraćuje stog natrag na oznaku. Taj redoslijed ima značenje, a sljedeći odjeljak objašnjava zašto

Tri načina na koja se LAMBDA poziva

HotXLS razrješava poziv nepoznatom imenu funkcije kroz tri putanje, isprobane redom, a znanje koja se od njih aktivira objašnjava većinu iznenađenja. Prvo, ime vezano u trenutnom LET ili LAMBDA opsegu: ako je f lokalno vezanje koje sadrži closure, f(21) ga primjenjuje. Drugo, definirano ime radne knjige čiji tekst formule počinje s LAMBDA: MyFunc(5) kompajlira tijelo tog imena i primjenjuje ga. Treće, klasični rukovatelj korisničkim funkcijama, nepromijenjen, za sve što prve dvije putanje ne pokriju

Lokalno vezanje koje sadrži nešto drugo osim closurea nije pozivljivo. Vežite f uz broj 3, a zatim napišite f(21) i dobit ćete pogrešku vrijednosti, a ne pokušaj množenja. Ovo je strože nego što bi bio dinamički jezik, i to namjerno: pravopisna pogreška koja poziv funkcije pretvori u slučajnu referencu tihi je pogrešan odgovor, a to je najgori mogući ishod koji mehanizam proračunske tablice može proizvesti

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

    // Funkcija za višekratnu upotrebu, opseg radne knjige
    Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');

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

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

    // Ugniježđeni LET: svako je vezanje vidljivo onima koji slijede
    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 razrješava zasjenjivanje kada se imena sudare?

Parametri pobjeđuju. Kada HotXLS primjenjuje closure, prvo gura zahvaćeno leksičko okruženje, a zatim vezanja argumenata, pa parametar imenom rate zasjenjuje vanjsko vezanje istog imena rate, a zasjenjuje i istoimenu referencu na stupac u okolnoj formuli. Taj redoslijed čini imenovanu funkciju sigurnom za ponovnu upotrebu: pozivatelj ne može slučajno promijeniti značenje tijela funkcije time što u opsegu ima slično imenovano vezanje

Aritet se provjerava prije nego što se bilo što evaluira. Poziv čiji se broj argumenata ne podudara s brojem parametara closurea odmah vraća pogrešku vrijednosti, umjesto da evaluira neke argumente i tek onda ne uspije, čime evaluacija bez nuspojava ostaje istinski bez djelomično obavljenog posla. Stog opsega skraćuje se natrag na oznaku ulaska unutar bloka finally, tako da pogreška unutar tijela ne može ostaviti zastarjela vezanja vidljivima sljedećoj formuli

var
  Book: TXLSXWorkbook;
  Name: TXLSXDefinedName;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('customer-model.xlsx') = 1 then
    begin
      // Pregledajte ono što je korisnik napisao prije nego što povjerujete ponovnom izračunu
      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 djelomičan

Ranija izdanja HotXLS-a implementirala su LET samo dovoljno da pokriju uobičajen slučaj s jednim vezanjem. Trenutna je implementacija potpuna: svako je vezanje vidljivo svim kasnijim vezanjima i izrazu tijela, a ugniježđeni LET-ovi se normalno sastavljaju, pa se LET(a, 1, b, a+1, LET(c, b*2, c)) evaluira jednako kao što ga evaluira Excel

Ta potpunost znači više nego što zvuči. LET je način na koji korisnici izbjegavaju ponovno izračunavanje istog podizraza pet puta unutar jedne formule, pa ga stvarne radne knjige koriste upravo u onim duboko ugniježđenim oblicima koje djelomična implementacija pogrešno obradi. Ako ste ranije zaobilazili te nedostatke tako da ste razvijali LET vezanja prije evaluacije, to zaobilaženje sada možete ukloniti

Zarez ili točka-zarez: sada oboje

Tekst formule u HotXLS-u sada prihvaća zarez kao razdjelnik argumenata uz klasičnu točku-zarez. To nije postavka lokalizacije; to je pravilo prihvaćanja u parseru. Ima značenje jer formule stižu s mjesta koja ne kontrolirate: zalijepljene iz prijave podrške, kopirane iz dokumentacije, generirane skriptom koja je emitirala Excelovu kanonsku sintaksu, uvezene iz CSV-a nizova formula

Praktičan je učinak da se i SUM(A1,A2) i SUM(A1;A2) podjednako kompajliraju. Kružni ciklus čuva ono što je izvor koristio, pa se radna knjiga koju ste učitali zapisuje natrag s izvornim razdjelnicima, umjesto da se normalizira iza korisnikovih leđa

Što se čuva kroz kružni ciklus, i što provjeriti

Tekst formule pohranjuje se doslovno, pa LAMBDA u definiranom imenu preživi ciklus učitavanja i spremanja netaknuta i otvara se u Excelu kao ista funkcija. Gola LAMBDA pohranjena kao rezultat ćelije, odnosno formula koja se evaluira u closure umjesto u vrijednost, zadržava postojeće ponašanje preskoči-bez-vrijednosti: tekst se čuva, a za nju se ne izmišlja predmemorirani brojčani rezultat. To je iskren ishod, budući da nema skalara koji bi se predmemorirao

Vrijedi usvojiti dvije navike. Dajte imenovanim lambdama opseg radne knjige osim ako postoji razlog da to ne učinite, jer funkcija s opsegom lista koja nestane kada se list kopira proizvodi pogrešku imena na mjestu daleko od uzroka; pravila opsega obrađena su u članku definirana imena i formule preko više listova. A kada je radna knjiga puna imenovanih lambdi namijenjena izvještaju koji mora biti stabilan, razmotrite zamrzavanje rezultata pomoću ConvertFormulasToValues, tako da potrošači nizvodno vide brojeve umjesto funkcija koje možda ne podržavaju

Kod intenzivnog ponovnog izračuna, tijela LAMBDA-e obični su izrazi u grafu ovisnosti i raspoređuju se kao i svaka druga formula, što je opisano u članku inkrementalni ponovni izračun i graf ovisnosti. Ako vaš model poziva jednu imenovanu funkciju kroz tisuće redaka, trošak je tijelo funkcije, a ne mehanizam poziva, a vrijede iste smjernice za optimizaciju kao i za bilo koju ponovljenu formulu

HotXLS je izvorna Delphi i C++Builder komponenta za proračunske tablice koja čita i piše XLS, XLSX i ODS bez Excela ili bilo kakve automatizacije Officea. API za mehanizam formula, definirana imena i ponovni izračun dokumentiran je na stranici HotXLS Delphi komponente za proračunske tablice