Дозволити користувальницькій функції брати з лексичного середовища

Я намагаюся перетворити мій код на використання лексичної прив'язки.

У мене є функція ( format-template ), яка викликає лямбда-функцію (прив'язка до заміни-внутрішньої ) зі списку, що настроюється користувачем ( шаблон-заміна -функцій ) функцій, заснованих на комбінаційній рядку. Функція-замінник-внутрішня не приймає аргументів і повертає рядок, проте може використовувати змінні foo бар та/або baz , які передаються як частина аргументу list до format-template .

(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))))

Так, наприклад, список містить три елементи, які прив'язані до foo bar і baz . ключ рядка збігу є "bananas" , який асоціюється з функцією в template-replace-functions і встановлює заміну внутрішній для:

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

На цьому етапі вищезгадана лямбда-функція повинна знати bar , що добре використовує динамічне зв'язування, але не дуже точне, використовуючи лексичну прив'язку.

Моє питання полягає в тому, як зробити це, щоб дозволити лямбда-функції, пов'язані з replacer-internal , приймати значення прив'язки foo бар і baz ?

(Просто щоб збентежити речі, функція replace-outer , яка містить replace-internal , призначена для прив'язки, оскільки вона викликається з одного з двох місць у реальній функції. Я міг би писати вбудовано тут, але включив його таким чином у випадку, якщо це додасть проблеми.)

Редагувати:

На півдорозі...

(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)

Таким чином, розширення функції лямбда в межах макросу з'являється на роботу, але не на ім'я функції. Але я хочу, щоб дозволити користувачеві лямбда або названі функції. Як обійти це?

3
@rnkn Це не функція Emacs, це лише список, який перетворюється неявно в декількох ситуаціях. Але є помітні відмінності: лямбда-списки не можуть закриватися навколишніми прив'язками, і ніколи не будуть компілюватися. Вони існують виключно з історичних причин, а не тому, що вони є гарною ідеєю. Не варто покладатися на цю функцію в сьогоднішньому коді Emacs Lisp.
додано Автор Ishmaeel, джерело
@Stefan, якщо інтерпретатор (Emacs) бачить його як функцію і користувач бачить його як функцію, то прагматично кажучи, чи не є це функцією? Хіба Emacs ніколи не буде досить приємно бачити його як функцію?
додано Автор Iker Jimenez, джерело
До речі, офіційно кажучи, ваш (lambda() адреса користувача) не є функцією. Натомість, це список з 3-х елементів, який виглядає так само, як і вихідний код функції. А Emacs досить приємно перетворити один на інший, якщо ви перейдете цей список до funcall . Але якщо ви дійсно хочете сказати, що це функція, то вам треба написати `((" email ", (lambda() адреса користувача) ...
додано Автор sds, джерело

1 Відповіді

Коротка відповідь: ви не можете. Ось чому цей вид зв'язування називається «лексичним» - він запобігає вирванню змінних зі своєї області визначення.

Один із способів вирішення цієї проблеми: ви могли б передати деяку структуру даних у функцію "банани", де "банани" знали б, як витягти з неї значення. Тобто.

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

Це передбачає, що env є хеш-таблицею.

Як бік: краще використовувати eql , а не eq . Я не думаю, що Emacs Lisp дає якісь корисні гарантії щодо того, як eq веде себе практично з будь-яким типом даних (наприклад, я не впевнений, що навіть інтерновані символи з однаковою назвою повинні бути рівними під < код> eq ).

1
додано
Я голосую за останнє зауваження про eq. Чи знаєте ви, що eq є рівноцінним для всіх типів даних, крім числових? Крім того, два незмінені символи не збігаються, навіть якщо вони поділяють одне і те ж ім'я, і, отже, не повинні бути рівними за рів.
додано Автор Ishmaeel, джерело
З усією повагою Emacs Lisp не є Common Lisp, і ніколи не буде. Можна також уникати списків, оскільки вони можуть бути видалені, коли Emacs Lisp стає більш схожим на C. Будь ласка, видаліть останній абзац з вашої відповіді; це заплутано і вводить в оману. Внутрішні символи, до речі, рівні за eq, просто тому, що в Emacs Lisp є лише один обррей.
додано Автор Ishmaeel, джерело
@wvxvw Який аргумент? Також існує причина того, що система типів Haskell теж є, але Emacs Lisp також не стане Haskell. Emacs Lisp не є Common Lisp, тому, будь ласка, перейдіть до нього і не надайте поради Emacs Lisp на основі того, як поводиться Common Lisp.
додано Автор Ishmaeel, джерело
@wvxvw Ну, що завгодно. Emacs Lisp дещо відрізняється від CL в даний час (як C ++ від C, незважаючи на історичні відносини), і ваша точка зору eq просто не стосується першого, але я думаю, що немає сенсу обговорювати це далі. Але не дивуйтеся, якщо тоді проголосували відповіді про CL.
додано Автор Ishmaeel, джерело
@lunaryorn якщо ви читаєте те, що я написав, це говорить: "Я не впевнений, що навіть інтерновані символи з однаковою назвою повинні бути рівні за eq". Я нічого не сказав про незмінені символи. Моє небажання використовувати eq пов'язано з Common Lisp, де це майже ніколи не буває добре. Я не знаю деталей реалізації Emacs Lisp, отже, це може бути просто забобоном на моїй стороні. Тим не менш, я все одно уникаю: ніколи не знаєте, можливо, майбутній Emacs Lisp буде більше схожим на Common Lisp?
додано Автор Yann Trevin, джерело
@Lunaryorn Common Lisp не робить це довільно. Є підстава для цього. Це оптимізація швидкості/пам'яті. Хто знає, можливо, колись Emacs Lisp намагатиметься додати більше оптимізацій до того, як керуватиметься його пам'яттю, то це може стати проблемою.
додано Автор Yann Trevin, джерело
@rnkn Ну, це просто по-дитячому. Звичайно, це не обмеження того, що ви можете зробити. Ви звичайно можете це зробити. Чи буде вона працювати так, як ви хочете, це інша справа.
додано Автор Yann Trevin, джерело
@ lunaryorn Emacs Lisp був історично дуже близьким родичем Common Lisp. Вона має багато подібних проблем, і вона дуже відрізняється від Haskell. Однак це не просто поведінка Common Lisp. Це те, що будь-який дизайнер будь-якої середовища виконання має відповідати в певний момент. Це також причина, чому ви повинні порівнювати рядки в Java за допомогою спеціальної функції, чому порівняння покажчиків на літеральні рядки в C не є гарною ідеєю і т.д. , і якщо вони не є зараз, то вони можуть бути в майбутньому.
додано Автор Yann Trevin, джерело
@ lunaryorn Я не міг піклуватися.
додано Автор Yann Trevin, джерело
Я можу і я буду! Я відредагував питання, щоб включити часткове рішення з використанням макросу, але це все ще не є повним рішенням.
додано Автор Iker Jimenez, джерело
Проблема з передачею env до користувацьких функцій полягає в тому, що він вимагає від користувача додавати фіксований аргумент до своїх функцій, оскільки вони не обов'язково знатимуть значення foo < код> бар і baz , і я відчуваю, що це не дуже зручно. Однак це змусило мене переосмислити проходження невідомого лексичного середовища для користувача. Може бути погано.
додано Автор Iker Jimenez, джерело