Toestaan ​​dat de aangepaste functie uit de lexicale omgeving komt

Ik probeer mijn code te converteren om lexicale binding te gebruiken.

Ik heb een functie ( format-template ) die een lambda-functie (let-bound to replacer-inner ) uit een door de gebruiker aan te passen lijst ( sjabloon-vervangt -functies ) van functies op basis van een match-string. De functie replacer-inner neemt geen argumenten aan en retourneert een tekenreeks, maar may moet variabelen foo bar en/of gebruiken baz , die worden doorgegeven als onderdeel van het argument lijst tot opmaaksjabloon .

(setq lexical-binding t)

(defcustom template-replace-functions
  '(("email"
     (lambda() user-mail-address)
    ("apples"
     (lambda ()
       (cond ((= foo 1)
              "one")
             ((= foo 2)
              "two")
             ((= foo 3)
              "three")
             (t "four"))))
    ("bananas"
     (lambda ()
       (if (eq bar 'move)
           "movement" "sit still")))))
  "Association list of replacement functions.

For each STRING, the corresponding FUNCTION is called with no
arguments and must return a string."
  :type '(repeat (group string function))
  :group 'spice-girls)

(defun format-template (list)
  (let* ((foo (nth 0 list))
         (bar (nth 1 list))
         (baz (nth 2 list))
         template
         (replacer-outer
          (lambda ()
            (replace-regexp-in-string
             "\${\([^\s\t\n]*?\)}"
             (lambda (match)
               (let* ((key (match-string 1 match))
                      (replacer-inner
                       (cadr (assoc key template-replace-functions))))
                 (if (and replacer-inner
                          (stringp (funcall replacer-inner)))
                     (funcall replacer-inner) "")))
             template t t))))
    (setq template
          ...)
    (if template (funcall replacer-outer))))

Dus, bijvoorbeeld, lijst bevat drie elementen, die zijn gebonden aan foo bar en baz . De -code van de overeenkomstreeks is "bananen" die aan een functie in sjabloon-vervang-functies koppelt en vervangt innerlijke om:

(lambda ()
       (if (eq bar 'move)
           "movement" "sit still"))

Op dit punt moet de bovenstaande lambda-functie bar kennen, wat prima is met behulp van dynamische binding, maar niet zo goed met lexicale binding.

Mijn vraag is, hoe kan ik dit doen om de lambda-functie gebonden aan replacer-inner te laten nemen van de let-bound-waarden van foo bar en baz ?

(Gewoon om dingen te verwarren, de functie replace-outer , die replace-inner bevat, is let-bound omdat deze wordt aangeroepen vanuit een van de twee plaatsen in de echte functie. Ik zou hier inline kunnen hebben geschreven, maar ik heb het op deze manier toegevoegd voor het geval het aan het probleem toevoegt.)

Bewerken

Halverwege...

(setq lexical-binding t
  foo 0
  bar 0)

(setq inline-fun-1
      '(lambda ()
         (setq return
               (if (eq foo 1)
                   "Pass" "Fail"))))

(defmacro lex-fun ()
  `(let* ((foo 1)
          (bar 1)
          return)
     (funcall ,inline-fun-1)))

(lex-fun)    ; -> "Pass"

(defun inline-fun-2 ()
  (setq return
        (if (eq bar 1)
            "Pass" "Fail")))

(defmacro lex-fun ()
  `(let* ((foo 1)
          (bar 1)
          return)
     (funcall ,inline-fun-2)))

(lex-fun)    ; -> Lisp error: (void-variable inline-fun-2)

Dus uitbreiding van de lambda-functie binnen een macro lijkt te werken, maar geen functie met een naam. Maar ik wil de gebruiker lambda of benoemde functies toestaan. Hoe kom je hier omheen?

3
@rnkn Het is geen functie voor Emacs, het is gewoon een lijst die toevallig in een paar situaties geconverteerd wordt. Maar er zijn opmerkelijke verschillen: Lambda-lijsten kunnen de omringende bindingen niet sluiten en worden nooit byte gecompileerd. Ze bestaan ​​louter om historische redenen, niet omdat ze een goed idee zijn. Je moet niet op deze functie vertrouwen in de Emacs Lisp-code van vandaag.
toegevoegd de auteur Ishmaeel, de bron
@Stefan als de interpreter (Emacs) het als een functie ziet en de gebruiker het als een functie ziet, dan is het pragmatisch gezien geen functie? Zullen Emacs ooit niet aardig genoeg zijn om het als een functie te zien?
toegevoegd de auteur Iker Jimenez, de bron
Trouwens, formeel gesproken is je (lambda() user-mail-adres) geen functie. In plaats daarvan is het een lijst met drie elementen, die er hetzelfde uitzien als de broncode voor een functie. En Emacs is aardig genoeg om de een in de ander te veranderen als je die lijst doorgeeft aan funcall . Maar als je echt wilt zeggen dat het een functie is, dan moet je `((" email ", (lambda() user-mail-adres) schrijven ...
toegevoegd de auteur sds, de bron

1 antwoord

Kort antwoord: dat kan niet. Dit is de reden waarom dit soort binding 'lexical' wordt genoemd - het voorkomt dat variabelen uit hun definitiegebied ontsnappen.

Een manier om dit probleem aan te pakken: u had een bepaalde datastructuur kunnen doorgeven aan de functie "bananen", waarbij "bananen" zouden weten hoe waarden eruit kunnen worden geëxtraheerd. D.w.z.

(defcustom template-replace-functions
  '(("bananas" . (lambda (env) 
                   (let ((x (gethash "x" env "")))
                     (do-replacements-with x))))
    ...)))

Waarvan uitgegaan wordt dat env een hashtabel is.

Terzijde: het is beter om eql te gebruiken in plaats van eq . Ik denk niet dat Emacs Lisp enige bruikbare garanties geeft over hoe eq zich met bijna elk type gegevens gedraagt ​​(bijv. Ik ben er niet zeker van dat zelfs geïnterneerde symbolen met dezelfde naam onder < code> eq ).

1
toegevoegd
Ik heb een negatieve stem uitgebracht op de laatste opmerking over eq. Weet je dat eq exact hetzelfde is voor alle gegevenstypen behalve voor numerieke? Bovendien zijn twee niet-geïnterneerde symbolen niet hetzelfde, zelfs als ze dezelfde naam hebben, en dus niet gelijk mogen zijn onder eq.
toegevoegd de auteur Ishmaeel, de bron
Met alle respect, Emacs Lisp is geen Common Lisp en zal dat ook nooit zijn. Je kunt net zo goed lijsten vermijden, omdat ze mogelijk worden verwijderd als Emacs Lisp meer op C. lijkt. Verwijder alsjeblieft de laatste alinea uit je antwoord; het is verwarrend en misleidend. Interne symbolen zijn trouwens gelijk onder eq, simpelweg omdat er maar één obscure is in Emacs Lisp.
toegevoegd de auteur Ishmaeel, de bron
@wvxvw Wat voor argument is dat? Er zit ook een reden achter het type systeem van Haskell, maar Emacs Lisp zal ook Haskell niet worden. Emacs Lisp is geen Common Lisp, dus kom er alsjeblieft over en geef Emacs Lisp geen advies op basis van hoe Common Lisp zich gedraagt.
toegevoegd de auteur Ishmaeel, de bron
@wvxvw Nou, wat dan ook. Emacs Lisp is tegenwoordig enigszins anders dan CL (zoals C ++ is van C ondanks de historische relatie), en jouw punt over eq is simpelweg niet van toepassing op de eerste, maar ik denk dat het geen zin heeft om dit verder te bespreken. Maar wees niet verbaasd als antwoorden op CL dan worden gestemd.
toegevoegd de auteur Ishmaeel, de bron
@lunaryorn als je leest wat ik heb geschreven staat er: "Ik weet niet zeker of zelfs geïnterneerde symbolen met dezelfde naam onder eq" gelijk moeten zijn. Ik zei niets over niet-geïnterneerde symbolen. Mijn tegenzin om eq te gebruiken is te wijten aan Common Lisp, waar dit bijna nooit een goede zaak is om te doen. Ik ken de implementatiedetails van Emacs Lisp niet, dus het zou gewoon een bijgeloof aan mijn kant kunnen zijn. Toch zou ik het nog steeds vermijden: je weet maar nooit, misschien dat toekomstige Emacs Lisp meer op Common Lisp zal lijken?
toegevoegd de auteur Yann Trevin, de bron
@lunaryorn Common Lisp doet het niet willekeurig. Er is een reden voor. Het is een snelheid/geheugen-optimalisatie. Wie weet probeert Emacs Lisp op een dag meer optimalisaties toe te voegen aan de manier waarop het geheugen wordt beheerd, dan kan het een probleem worden.
toegevoegd de auteur Yann Trevin, de bron
@rnkn Nou, dat is gewoon kinderachtig. Het is natuurlijk geen beperking voor wat u kan doen. Je kunt het zeker doen. Of het zal werken zoals u het wilt is een andere zaak.
toegevoegd de auteur Yann Trevin, de bron
@lunaryorn Emacs Lisp is van oudsher een zeer naaste verwant van Common Lisp. Het heeft veel vergelijkbare zorgen, en het is heel anders dan Haskell. Dit is echter niet alleen een gedrag van Common Lisp. Dit is iets dat elke ontwerper van elke runtime op een gegeven moment zou moeten tegenkomen. Dit is ook de reden waarom je strings in Java moet vergelijken met een speciale functie, waarom het vergelijken van pointers met letterlijke strings in C geen goed idee is enz. Wat ik wil zeggen is dat er een reden is voor dingen zoals ik heb beschreven en als ze dat nu niet zijn, dan zijn ze mogelijk in de toekomst.
toegevoegd de auteur Yann Trevin, de bron
@lunaryorn Het kan me niet schelen.
toegevoegd de auteur Yann Trevin, de bron
Ik kan en ik zal! Ik heb de vraag bewerkt om een ​​gedeeltelijke oplossing op te nemen met een macro, maar het is nog steeds geen volledige oplossing.
toegevoegd de auteur Iker Jimenez, de bron
Het probleem met het doorgeven van env aan de aangepaste functies is dat de gebruiker een dummy-argument aan zijn functies moet toevoegen, omdat deze niet noodzakelijk de waarden van foo < code> bar en baz , en ik denk dat dat niet gebruiksvriendelijk genoeg is. Dit heeft me echter doen besluiten om een ​​onbekende lexicale omgeving door te geven aan de gebruiker. Kan slecht zijn.
toegevoegd de auteur Iker Jimenez, de bron