Τεχνικό Άρθρο

LAMBDA και LET στο Delphi: HotXLS Formula Closures

Το HotXLS υπολογίζει το LAMBDA του Excel ως πραγματική συνάρτηση πρώτης τάξης. Ένα ονομαστικό εύρος του οποίου το κείμενο RefersTo είναι ένα LAMBDA μπορεί να κληθεί με το όνομά του ως =MyFunc(5), ένα closure δεμένο μέσα σε LET μπορεί να κληθεί ως =LET(f, LAMBDA(x, x*2), f(21)), και το λεκτικό περιβάλλον που δεσμεύτηκε τη στιγμή του ορισμού ταξιδεύει μαζί με το closure. Το κείμενο της φόρμουλας κάνει round-trip στο βιβλίο εργασίας αυτούσιο

Αυτό είναι το χαρακτηριστικό που ξεχωρίζει μια μηχανή φορμουλών από έναν parser φορμουλών. Ό,τι υπήρχε πριν το LAMBDA μπορούσε να υπολογιστεί διατρέχοντας ένα δέντρο τιμών. Το LAMBDA απαιτεί μια στοίβα scope, και μόλις έχετε μια στοίβα scope, μια ολόκληρη κατηγορία λογικής spreadsheet γραμμένης από χρήστες αρχίζει να λειτουργεί στην εφαρμογή σας Delphi αντί μόνο στο Excel

Γιατί οι περισσότερες μηχανές εκτός Excel σταματούν στη λέξη-κλειδί LAMBDA;

Επειδή ένας κλασικός evaluator spreadsheet έχει ακριβώς ένα είδος τιμής: έναν αριθμό, ένα string, ένα boolean, ένα σφάλμα, ή μια αναφορά σε κελιά που κρατούν αυτές τις τιμές. Δεν υπάρχει πουθενά να βάλετε μια συνάρτηση. Όταν το Excel 365 εισήγαγε το LAMBDA, πρόσθεσε έναν τύπο τιμής που φέρει ονόματα παραμέτρων, μια έκφραση σώματος, και τις δεσμεύσεις ορατές εκεί όπου γράφτηκε. Μια μηχανή χωρίς αυτόν τον τύπο μπορεί να αναλύσει το LAMBDA(x, x*2) και να αποθηκεύσει το κείμενο, αλλά τη στιγμή που ένα κελί προσπαθεί να το καλέσει, δεν υπάρχει τίποτα να κληθεί

Το HotXLS υλοποιεί το κομμάτι που λείπει ως τιμή closure συν μια στοίβα scope χρόνου εκτέλεσης. Η κλήση ενός closure προωθεί το δεσμευμένο περιβάλλον του, μετά προωθεί τις τιμές ορισμάτων κάτω από τα ονόματα παραμέτρων, υπολογίζει το σώμα, και περικόπτει τη στοίβα πίσω στο σημάδι. Αυτή η σειρά έχει σημασία, και η επόμενη ενότητα εξηγεί γιατί

Οι τρεις τρόποι με τους οποίους καλείται ένα LAMBDA

Το HotXLS επιλύει μια κλήση σε άγνωστο όνομα συνάρτησης μέσα από τρεις διαδρομές, δοκιμασμένες με σειρά, και το να ξέρετε ποια ενεργοποιείται εξηγεί τις περισσότερες εκπλήξεις. Πρώτον, ένα όνομα δεμένο στο τρέχον scope LET ή LAMBDA: αν το f είναι μια τοπική δέσμευση που κρατά ένα closure, το f(21) το εφαρμόζει. Δεύτερον, ένα ονομαστικό εύρος βιβλίου εργασίας του οποίου το κείμενο φόρμουλας ξεκινά με LAMBDA: το MyFunc(5) μεταγλωττίζει το σώμα εκείνου του ονόματος και το εφαρμόζει. Τρίτον, ο κλασικός handler συναρτήσεων χρήστη, αμετάβλητος, για ό,τι δεν διεκδικούν οι δύο πρώτες διαδρομές

Μια τοπική δέσμευση που κρατά κάτι διαφορετικό από closure δεν είναι κλητή. Δέστε το f στον αριθμό 3 και μετά γράψτε f(21) και παίρνετε σφάλμα τιμής, όχι μια προσπάθεια πολλαπλασιασμού. Αυτό είναι αυστηρότερο απ' ό,τι θα ήταν μια δυναμική γλώσσα, και σκόπιμα: ένα ορθογραφικό λάθος που μετατρέπει μια κλήση συνάρτησης σε τυχαία αναφορά είναι μια σιωπηλή λανθασμένη απάντηση, το χειρότερο αποτέλεσμα που μπορεί να παράξει μια μηχανή spreadsheet

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)';

    // Ένα closure δεμένο και εφαρμοσμένο μέσα σε μία φόρμουλα
    Sheet.Cells[3, 2].Formula := 'LET(double, LAMBDA(x, x*2), double(21))';

    // Nested 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 εφαρμόζει ένα closure προωθεί πρώτα το δεσμευμένο λεκτικό περιβάλλον και δεύτερα τις δεσμεύσεις ορισμάτων, οπότε μια παράμετρος ονόματος rate σκιάζει μια εξωτερική δέσμευση με το όνομα rate και σκιάζει επίσης μια αναφορά στήλης με το ίδιο όνομα στη γύρω φόρμουλα. Αυτή η σειρά είναι αυτή που κάνει ασφαλή την επαναχρησιμοποίηση μιας ονομαστικής συνάρτησης: ο καλών δεν μπορεί να αλλάξει κατά λάθος το νόημα του σώματος έχοντας μια παρόμοιου ονόματος δέσμευση στο scope

Ο αριθμός ορισμάτων ελέγχεται πριν υπολογιστεί οτιδήποτε. Μια κλήση της οποίας ο αριθμός ορισμάτων δεν ταιριάζει με τον αριθμό παραμέτρων του closure επιστρέφει αμέσως σφάλμα τιμής, αντί να υπολογίσει μερικά ορίσματα και μετά να αποτύχει, κάτι που κρατά τον χωρίς παρενέργειες υπολογισμό γνησίως ελεύθερο από μερική δουλειά. Η στοίβα scope περικόπτεται πίσω στο σημάδι εισόδου της μέσα σε ένα finally block, οπότε ένα σφάλμα μέσα σε ένα σώμα δεν μπορεί να αφήσει παλιές δεσμεύσεις ορατές στην επόμενη φόρμουλα

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 μόνο μέχρι το σημείο που χειριζόταν τη συνηθισμένη περίπτωση μίας δέσμευσης. Η τρέχουσα υλοποίηση είναι πλήρης: κάθε δέσμευση είναι ορατή σε όλες τις επόμενες δεσμεύσεις και στην έκφραση σώματος, και το nested LET συντίθεται κανονικά, οπότε το LET(a, 1, b, a+1, LET(c, b*2, c)) υπολογίζεται με τον τρόπο που το υπολογίζει το Excel

Αυτή η πληρότητα μετράει περισσότερο απ' όσο ακούγεται. Το LET είναι ο τρόπος με τον οποίο οι χρήστες αποφεύγουν να επαναϋπολογίζουν την ίδια υποέκφραση πέντε φορές σε μία φόρμουλα, οπότε πραγματικά βιβλία εργασίας το χρησιμοποιούν ακριβώς στα βαθιά ένθετα σχήματα όπου μια μερική υλοποίηση κάνει λάθος. Αν παλαιότερα παρακάμπτατε κενά επεκτείνοντας δεσμεύσεις LET πριν τον υπολογισμό, αυτή η παράκαμψη μπορεί να φύγει

Κόμμα ή άνω-κάτω τελεία: και τα δύο, πλέον

Το κείμενο φόρμουλας στο HotXLS πλέον δέχεται το κόμμα ως διαχωριστικό ορισμάτων παράλληλα με την κλασική άνω-κάτω τελεία. Αυτό δεν είναι ρύθμιση τοπικότητας· είναι κανόνας αποδοχής στον parser. Έχει σημασία επειδή οι φόρμουλες φτάνουν από σημεία που δεν ελέγχετε: επικολλημένες από ένα ticket υποστήριξης, αντιγραμμένες από τεκμηρίωση, παραγόμενες από ένα script που εξέπεμψε την κανονική σύνταξη του Excel, εισαγόμενες από ένα CSV κειμένων φόρμουλας

Το πρακτικό αποτέλεσμα είναι ότι τόσο το SUM(A1,A2) όσο και το SUM(A1;A2) μεταγλωττίζονται. Το round-trip διατηρεί ό,τι χρησιμοποίησε η πηγή, οπότε ένα βιβλίο εργασίας που φορτώσατε γράφεται πίσω με τους αρχικούς του διαχωριστές αντί να κανονικοποιείται πίσω από την πλάτη του χρήστη

Τι κάνει round-trip, και τι να ελέγξετε

Το κείμενο της φόρμουλας αποθηκεύεται αυτούσιο, οπότε ένα LAMBDA σε ονομαστικό εύρος επιβιώνει από έναν κύκλο φόρτωσης και αποθήκευσης άθικτο και ανοίγει στο Excel ως η ίδια συνάρτηση. Ένα γυμνό LAMBDA αποθηκευμένο ως αποτέλεσμα κελιού, δηλαδή μια φόρμουλα που υπολογίζεται σε closure αντί σε τιμή, διατηρεί την υπάρχουσα συμπεριφορά παράλειψης-χωρίς-τιμή: το κείμενο διατηρείται, δεν επινοείται κανένα cached αριθμητικό αποτέλεσμα γι' αυτό. Αυτό είναι το τίμιο αποτέλεσμα, αφού δεν υπάρχει scalar προς cache

Δύο συνήθειες αξίζει να υιοθετηθούν. Δώστε στα ονομαστικά lambdas εμβέλεια βιβλίου εργασίας εκτός αν υπάρχει λόγος να μην το κάνετε, επειδή μια συνάρτηση εμβέλειας φύλλου που εξαφανίζεται όταν ένα φύλλο αντιγράφεται παράγει σφάλμα ονόματος σε σημείο μακριά από την αιτία· οι κανόνες εμβέλειας καλύπτονται στο ονομαστικά εύρη και φόρμουλες μεταξύ φύλλων. Και όταν ένα βιβλίο εργασίας γεμάτο ονομαστικά lambdas προορίζεται για μια αναφορά που πρέπει να είναι σταθερή, εξετάστε το ενδεχόμενο να παγώσετε τα αποτελέσματα με την ConvertFormulasToValues ώστε οι κατάντη καταναλωτές να βλέπουν αριθμούς αντί για συναρτήσεις που μπορεί να μην υποστηρίζουν

Για βαρύ επανυπολογισμό, τα σώματα LAMBDA είναι κοινές εκφράσεις στο γράφημα εξαρτήσεων και προγραμματίζονται όπως κάθε άλλη φόρμουλα, κάτι που περιγράφεται στο επαυξητικός επανυπολογισμός και το γράφημα εξαρτήσεων. Αν το μοντέλο σας καλεί μία ονομαστική συνάρτηση σε χιλιάδες γραμμές, το κόστος είναι το σώμα, όχι ο μηχανισμός κλήσης, και ισχύει η ίδια συμβουλή βελτιστοποίησης όπως για κάθε επαναλαμβανόμενη φόρμουλα

Το HotXLS είναι ένα εγγενές spreadsheet component για Delphi και C++Builder που διαβάζει και γράφει XLS, XLSX και ODS χωρίς Excel ή οποιονδήποτε αυτοματισμό Office. Η μηχανή φορμουλών, τα ονομαστικά εύρη και το API επανυπολογισμού τεκμηριώνονται στη σελίδα του HotXLS Delphi spreadsheet component