Создание функций с динамическим вводом с макросами

Моя цель - создать макрос, который автоматически создает функции со списком аргументов, созданным в другом месте. Я хочу, чтобы макрос возвращал список, состоящий из функции и списка аргументов, который она использовала (список символов). Я использую SBCL.

Создание списка аргументов

Допустим, список аргументов генерируется:

(defun input-syms ()
  (list 'in1 'in2 'in3))
;;=> (IN1 IN2 IN3)

Создание функции с этим списком аргументов

Следуя полезному ответу в Вложенный `defun` выдает повторяющееся предупреждение в Allegro Common Lisp, я использовал labels следующим образом (добавляя элементы списка просто для примера):

(defmacro create-funtest ()
  (let ((input-list (input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       #'fun-created)))
(funcall (create-funtest) 2 2 3) ;=> 7

Кажется, это работает, хотя я думаю, что может быть более простой способ сделать это. (list ,@input-list) кажется ненужным, но заменить его только на ,input-list не получится.

Возврат списка аргументов вместе с функцией

Здесь я в недоумении, и, кажется, это связано с вопросом о том, что именно означает ,input-list. Я думаю, это как-то связано с тем, что мы манипулируем символами, поэтому я попытался добавить туда symbol-value, но безуспешно.

Я бы хотел получить, выраженный в коде не работает:

(defmacro create-funtest2 ()
  (let ((input-list (input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       (list #'fun-created ,input-list))))

Что должно вернуть: (#<FUNCTION ...> (IN1 IN2 IN3)). Но вызов create-funtest2 дает ошибку компиляции The variable IN2 is unbound.. Я думаю, он пытается оценить символ вместо того, чтобы дать мне символ как есть.

Мне нужно иметь возможность получить символы, которые использовались для построения функции, я использую их при вызове функции впоследствии, чтобы узнать, какой ввод есть что. Этот список символов также можно изменить с помощью макроса create-funtest, поэтому мне действительно нужно получить его изнутри макроса.

Редактировать 1

Спасибо Райнеру Йосвигу за ответ. То, что меня беспокоит, на самом деле возвращает список символов в виде символов. Я предполагаю, что расширенный код для create-funtest2 (из приведенного вами расширения) должен выглядеть так:

(LABELS ((FUN-CREATED (IN1 IN2 IN3)
          (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED (LIST 'IN1 'IN2 'IN3)))

Так что на выходе макрос будет (#<FUNCTION ...> (IN1 IN2 IN3)). Проблема в том, что я хочу оценить input-list, но сохраняю его элементы как символы (не уверен, что правильно формулирую, извините).

Редактировать 2

Спасибо, coredump. Рабочая версия create-funtest2:

(defmacro create-funtest2 ()
  (let ((input-list (test-input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       (list #'fun-created (quote ,input-list)))))

Что дает расширение (спасибо, Райнер за фрагмент кода):

(let ((*print-circle* t)
          (*PRINT-RIGHT-MARGIN* 50))
      (pprint (copy-tree (macroexpand-1 '(create-funtest2)))))
=>
(LABELS ((FUN-CREATED (IN1 IN2 IN3)
           (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED '(IN1 IN2 IN3)))

И называется так:

(defparameter *fun-created2* (create-funtest2))
(funcall (car *fun-created2*) 1 2 3) ; => 6, OK
(second *fun-created2*) ; => (IN1 IN2 IN3), OK

Я получил удовольствие от «(list, @ input-list) кажется ненужным, но замена его просто, input-list не работает». Я обнаруживаю товарища-ныряльщика в глубоком конце. :) И я хорошо помню эту кривую обучения, так что было бы хорошо получить хорошее представление о том, что происходит, потому что один важный навык, необходимый для написания макросов, заключается в сохранении прямой разницы между расширением кода и его расширением при кодировании первого . Короче говоря, просто с помощью «(reduce '+, inputs)» вы генерируете «(reduce' + (in1 in2 in3))», а вовсе не то, что вы намеревались. Мета-совет: macroexpand-1

kennytilton 29.06.2018 03:41

Действительно, я понял это, посмотрев на дополнения.

Frank Jefferson 02.07.2018 08:15

Некоторые из моих самых больших открытий были связаны с простой печатью значений в коде расширения (чтобы я мог видеть их результат при вызове макрорасширения).

kennytilton 03.07.2018 22:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Список ввода - (IN1 IN2 IN3).

Это работает:

(reduce #'+ (list IN1 IN2 IN3))

Это не работает:

(reduce #'+ (IN1 IN2 IN3))

Причина: Нет функции IN1.

Macroexpand - ваш друг:

CL-USER 58 > (let ((*print-circle* t)
                   (*PRINT-RIGHT-MARGIN* 50))
               (pprint (copy-tree (macroexpand-1 '(create-funtest2)))))

(LABELS ((FUN-CREATED (IN1 IN2 IN3)
           (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED (IN1 IN2 IN3)))

У расширенного кода есть две проблемы:

  1. функция IN1 в последней строке не существует
  2. переменные IN2 и IN3 в последней строке не существуют

Возможно, macroexpand поможет вам решить вашу проблему.

Для получения дополнительной помощи вам нужно будет придумать немного лучшее объяснение того, что вы хотите сделать.

Ответ принят как подходящий

Более простая версия

Причина, по которой вы можете захотеть использовать reduce при наличии произвольного длинного списка входов, заключается в том, что вызовы функций ограничены CALL-ARGUMENT-LIMIT. Однако здесь вы используете свой список символов в качестве параметров функции, который ограничен LAMBDA-PARAMETERS-LIMIT. Также второй должен быть больше или равен первому. Таким образом, если список символов достаточно короткий, чтобы служить списком параметров, он также достаточно короткий, чтобы использовать его в качестве аргументов при вызове +.

(defun input-symbols ()
  '(in1 in2 in3))

(defmacro create-funtest ()
  (let ((args (input-symbols)))
    `(lambda ,args (+ ,@args))))

Здесь выше я также использовал анонимную функцию, но это не важно.

Возврат символов для оценки

Ваша вторая версия, переписанная с использованием того же подхода, что и выше, выглядит следующим образом:

(defmacro bad-create-funtest2 ()
  (let ((args (input-symbols)))
    `(list (lambda ,args (+ ,@args))
           ,args)))

Что по этому поводу говорит macroexpand?

(macroexpand '(bad-create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
         (IN1 IN2 IN3))

Здесь вы можете видеть, что вы пытаетесь вызвать in1 с аргументами in2 и in3. Вы не хотите оценивать список символов, просто передайте его без оценки.

Возвращаемые символы не оцениваются

(defmacro create-funtest2 ()
  (let ((args (input-symbols)))
    `(list (lambda ,args (+ ,@args))
           (quote ,args))))

Цитируя значение, вы можете гарантировать, что значение не будет оцениваться.

(macroexpand '(create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
         '(IN1 IN2 IN3))

Это оно ! Точка зрения, связанная с reduce, была сделана только для примера. Немного глупо, но все, что мне нужно, это (цитата, аргументы). Отредактирую свой вопрос рабочей версией create-funtest2.

Frank Jefferson 21.06.2018 14:10

Другие вопросы по теме