Tekninen artikkeli

LAMBDA ja LET Delphissä: HotXLS-kaavasulkeumat

HotXLS laskee Excelin LAMBDA-arvon aitona ensiluokkaisena funktioarvona. Määritetty nimi, jonka RefersTo-teksti on LAMBDA, voidaan kutsua nimellä muodossa =MyFunc(5), LET:n sisällä sidottu sulkeuma voidaan kutsua muodossa =LET(f, LAMBDA(x, x*2), f(21)), ja määrittelyhetkellä kaapattu leksikaalinen ympäristö kulkee sulkeuman mukana. Kaavateksti kiertää työkirjaan ja takaisin sanatarkasti

Tämä on ominaisuus, joka erottaa kaavamoottorin kaavajäsentimestä. Kaikki ennen LAMBDA:a voitiin laskea kävelemällä arvopuuta läpi. LAMBDA vaatii scope-pinon, ja kun sinulla on scope-pino, kokonainen luokka käyttäjän kirjoittamaa laskentataulukkologiikkaa alkaa toimia Delphi-sovelluksessasi eikä vain Excelissä

Miksi useimmat ei-Excel-moottorit pysähtyvät LAMBDA-avainsanaan?

Koska klassisella laskentataulukon laskimella on täsmälleen yhdenlaisia arvoja: luku, merkkijono, totuusarvo, virhe tai viittaus soluihin, jotka sisältävät näitä. Funktiolle ei ole minne laittaa sitä. Kun Excel 365 esitteli LAMBDA:n, se lisäsi arvotyypin, joka kantaa parametrien nimet, rungon lausekkeen ja sidokset, jotka olivat näkyvissä siinä, missä se kirjoitettiin. Moottori ilman tuota tyyppiä voi jäsentää lausekkeen LAMBDA(x, x*2) ja tallentaa tekstin, mutta hetkellä, jolloin solu yrittää kutsua sitä, ei ole mitään kutsuttavaa

HotXLS toteuttaa puuttuvan palan sulkeuma-arvona ja ajonaikaisena scope-pinona. Sulkeuman kutsuminen työntää sen kaapatun ympäristön pinoon, sitten työntää argumenttiarvot parametrien nimien alle, laskee rungon ja katkaisee pinon takaisin merkkiin. Tällä järjestyksellä on merkitystä, ja seuraava osio selittää miksi

Kolme tapaa, joilla LAMBDA kutsutaan

HotXLS ratkaisee kutsun tuntemattomaan funktion nimeen kolmen polun kautta, joita kokeillaan järjestyksessä, ja sen tietäminen, mikä niistä laukeaa, selittää useimmat yllätykset. Ensiksi, nimi sidottu nykyisessä LET- tai LAMBDA-scopessa: jos f on paikallinen sidos, joka sisältää sulkeuman, f(21) soveltaa sitä. Toiseksi, työkirjan määritetty nimi, jonka kaavateksti alkaa LAMBDA:lla: MyFunc(5) kääntää tuon nimen rungon ja soveltaa sitä. Kolmanneksi, klassinen käyttäjäfunktiokäsittelijä, muuttumattomana, kaikelle, mihin kaksi ensimmäistä polkua eivät ulotu

Paikallinen sidos, joka sisältää jotain muuta kuin sulkeuman, ei ole kutsuttavissa. Sido f lukuun 3 ja kirjoita sitten f(21), ja saat arvovirheen, et kertolaskuyritystä. Tämä on tiukempaa kuin dynaaminen kieli olisi, ja tarkoituksella: kirjoitusvirhe, joka muuttaa funktiokutsun vahingossa viittaukseksi, on hiljainen väärä vastaus, mikä on pahin lopputulos, jonka laskentataulukkomoottori voi tuottaa

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

    // Uudelleenkäytettävä nimetty funktio, työkirjan laajuus
    Book.DefinedNames.Add('NetOf', 'LAMBDA(amount, rate, amount*(1-rate))');

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

    // Sulkeuma sidottu ja sovellettu yhden kaavan sisällä
    Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';

    // Sisäkkäinen LET: jokainen sidos on näkyvissä sen jälkeisille
    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;

Miten varjostus ratkeaa, kun nimet törmäävät?

Parametrit voittavat. Kun HotXLS soveltaa sulkeumaa, se työntää kaapatun leksikaalisen ympäristön ensin ja argumenttisidokset toisena, joten rate-niminen parametri varjostaa ulomman rate-nimisen sidoksen ja varjostaa myös samannimisen sarakeviittauksen ympäröivässä kaavassa. Tämä järjestys on se, mikä tekee nimetyn funktion turvalliseksi uudelleenkäyttöön: kutsuja ei voi vahingossa muuttaa sitä, mitä runko tarkoittaa, pitämällä samannimistä sidosta scopessa

Ariteetti tarkistetaan ennen kuin mitään lasketaan. Kutsu, jonka argumenttien määrä ei täsmää sulkeuman parametrien määrän kanssa, palauttaa arvovirheen välittömästi sen sijaan, että se laskisi joitakin argumentteja ja sitten epäonnistuisi, mikä pitää sivuvaikutuksettoman laskennan aidosti vapaana osittaisesta työstä. Scope-pino katkaistaan takaisin sisääntulomerkkiinsä finally-lohkossa, joten rungon sisäinen virhe ei voi jättää vanhentuneita sidoksia näkyville seuraavalle kaavalle

var
  Book: TXLSXWorkbook;
  Name: TXLSXDefinedName;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('customer-model.xlsx') = 1 then
    begin
      // Tarkasta, mitä käyttäjä kirjoitti, ennen kuin luotat uudelleenlaskentaan
      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 ei ole enää osittainen

Aiemmat HotXLS-julkaisut toteuttivat LET:n vain sen verran, että se kattoi yleisen yksisidoksisen tapauksen. Nykyinen toteutus on täydellinen: jokainen sidos on näkyvissä kaikille myöhemmille sidoksille ja rungon lausekkeelle, ja sisäkkäinen LET yhdistyy normaalisti, joten LET(a, 1, b, a+1, LET(c, b*2, c)) laskee samalla tavalla kuin Excel laskee sen

Tuo täydellisyys on tärkeämpää kuin miltä se kuulostaa. LET on tapa, jolla käyttäjät välttävät saman alilausekkeen laskemisen viisi kertaa yhdessä kaavassa, joten todelliset työkirjat käyttävät sitä täsmälleen niissä syvästi sisäkkäisissä muodoissa, jotka osittainen toteutus tekee väärin. Jos kierrsit aiemmin aukkoja laajentamalla LET-sidoksia ennen laskentaa, tuosta kiertotiestä voi luopua

Pilkku vai puolipiste: molemmat, nyt

HotXLS:n kaavateksti hyväksyy nyt pilkun argumenttierottimena klassisen puolipisteen rinnalla. Tämä ei ole lokaaliasetus; se on jäsentimen hyväksymissääntö. Sillä on merkitystä, koska kaavat saapuvat paikoista, joita et hallitse: liitetty tukipyynnöstä, kopioitu dokumentaatiosta, generoitu skriptillä, joka tuotti Excelin kanonisen syntaksin, tuotu kaavamerkkijonojen CSV-tiedostosta

Käytännön vaikutus on se, että sekä SUM(A1,A2) että SUM(A1;A2) kääntyvät. Kierto säilyttää sen, mitä lähde käytti, joten lataamasi työkirja kirjoitetaan takaisin alkuperäisillä erottimillaan sen sijaan, että se normalisoitaisiin käyttäjän selän takana

Mikä kiertää, ja mitä tarkistaa

Kaavateksti tallennetaan sanatarkasti, joten määritetyssä nimessä oleva LAMBDA selviää lataus- ja tallennuskierroksesta ehjänä ja avautuu Excelissä samana funktiona. Paljas LAMBDA, joka on tallennettu solutuloksena, eli kaava, joka laskee sulkeumaksi eikä arvoksi, säilyttää olemassa olevan ohita-ilman-arvoa-käyttäytymisen: teksti säilyy, eikä sille keksitä välimuistiin tallennettua numeerista tulosta. Tämä on rehellinen lopputulos, koska ei ole skalaaria välimuistitettavaksi

Kaksi tapaa kannattaa omaksua. Anna nimetyille lambdoille työkirjan laajuus, ellei ole syytä olla tekemättä niin, koska taulukon laajuinen funktio, joka katoaa, kun taulukko kopioidaan, tuottaa nimivirheen paikassa, joka on kaukana syystä; laajuussäännöt käsitellään artikkelissa määritetyt nimet ja taulukoiden väliset kaavat. Ja kun nimettyjä lambdoja täynnä oleva työkirja on tarkoitettu raportille, jonka täytyy olla vakaa, harkitse tulosten jäädyttämistä ConvertFormulasToValues-metodilla, jotta jatkokäyttäjät näkevät numeroita eivätkä funktioita, joita he eivät ehkä tue

Raskaassa uudelleenlaskennassa LAMBDA-rungot ovat tavallisia lausekkeita riippuvuusgraafissa ja aikataulutetaan kuin mikä tahansa muu kaava, mikä on kuvattu artikkelissa inkrementaalinen uudelleenlaskenta ja riippuvuusgraafi. Jos mallisi kutsuu yhtä nimettyä funktiota tuhansien rivien yli, kustannus on runko, ei kutsukoneisto, ja samat optimointineuvot pätevät kuin mihin tahansa toistuvaan kaavaan

HotXLS on natiivi Delphi- ja C++Builder-laskentataulukkokomponentti, joka lukee ja kirjoittaa XLS-, XLSX- ja ODS-tiedostoja ilman Exceliä tai mitään Office-automaatiota. Kaavamoottori, määritetyt nimet ja uudelleenlaskenta-API on dokumentoitu sivulla HotXLS Delphi spreadsheet component page