Присвоение одного и того же значения нескольким переменным?

12

Иногда мне нужно установить одно и то же значение для нескольких переменных.

В Python я мог бы сделать

f_loc1 = f_loc2 = "/foo/bar"

Но в элиспе я пишу

(setq f_loc1 "/foo/bar" f_loc2 "/foo/bar")

Мне интересно, есть ли способ достичь этого, используя "/foo/bar"только один раз?

ChillarAnand
источник
1
Привет, Чиллар, можешь ли ты выбрать ответ @tarsius в качестве принятого? Мой ответ демонстрирует решение, которое дает вам то, что вы хотите, но, как я уже сказал, это «своего рода упражнение», которое решает эту, возможно, искусственную задачу, но не очень полезно на практике. tarsuis приложил немало усилий, чтобы продемонстрировать это очевидное соображение в своем ответе, поэтому я думаю, что его ответ заслуживает того, чтобы быть «правильным», поэтому все снова счастливы.
Марк Карпов
1
Я бы сказал, что необходимость присваивать одно и то же значение нескольким переменным указывает на недостаток проекта, который следует исправить, а не искать синтаксический сахар для прикрытия :)
lunaryorn
1
@lunaryorn, если вы хотите установить source& targetна тот же путь в начале, и я могу изменить targetпозже, как их установить?
ChillarAnand
Покажите больше кода, чтобы мы могли сказать, что вы подразумеваете под «переменной» для вашего варианта использования; т.е. какие переменные вы пытаетесь установить. Смотрите мой комментарий для ответа @ JordonBiondo.
Дрю

Ответы:

21

setq возвращает значение, так что вы можете просто:

(setq f-loc1 (setq f-loc2 "/foo/bar"))

Если вы не хотите на это полагаться, используйте:

(setq f-loc1 "/foo/bar" f-loc2 f-loc1)

Лично я бы избежал последнего и вместо этого написал бы:

(setq f-loc1 "/foo/bar"
      f-loc2 f-loc1)

или даже

(setq f-loc1 "/foo/bar")
(setq f-loc2 f-loc1)

И самый первый подход, который я использовал бы только в довольно особых обстоятельствах, когда он фактически подчеркивал намерение, или когда альтернативой было бы использовать второй подход или иначе progn.


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


Наконец, хотя соблазнительно написать макрос типа setq-every«потому что это шутка, и мы можем», я настоятельно рекомендую не делать этого. Делая это очень мало:

(setq-every value a b)
   vs
(setq a (setq b value))

Но в то же время другим читателям становится намного труднее читать код и быть уверенным, что произойдет. setq-everyмогут быть реализованы различными способами, и другие пользователи (включая будущее, которое вы) не можете знать, какой из них выберет автор, просто взглянув на код, который его использует. [Первоначально я привел несколько примеров, но кто-то считал их саркастическими и напыщенными.]

Вы можете справляться с этими ментальными накладными расходами каждый раз, когда смотрите setq-everyили можете просто жить с одним дополнительным персонажем. (По крайней мере, в случае, когда вам нужно установить только две переменные, но я не могу вспомнить, чтобы мне когда-либо приходилось устанавливать более двух переменных одновременно на одно и то же значение. Если вам нужно сделать это, то, вероятно, что-то еще не так с вашим кодом.)

С другой стороны, если вы действительно собираетесь использовать этот макрос более полудюжины раз, то дополнительная косвенность может стоить того. Например, я использую макросы, как --when-letиз dash.elбиблиотеки. Это усложняет задачу для тех, кто впервые сталкивается с этим в моем коде, но преодоление этой трудности в понимании того стоит, потому что он используется не очень редко, и, по крайней мере, на мой взгляд, делает код более читабельным. ,

Можно утверждать, что то же самое относится и к setq-every, но я думаю, что очень маловероятно, что этот конкретный макрос будет использоваться более чем несколько раз. Хорошее эмпирическое правило заключается в том, что когда определение макроса (включая строку документа) добавляет больше кода, чем удаляет использование макроса, тогда макрос, скорее всего, является избыточным (хотя обратное не всегда верно).

Tarsius
источник
1
после того, как вы установили одну переменную в, setqвы можете ссылаться на ее новое значение, так что вы также можете использовать это чудовище: (setq a 10 b a c a d a e a)которое устанавливает abcd и e в 10.
Джордон Биондо
1
@JordonBiondo, да, но я думаю, что цель OP - уменьшить количество повторений в своем коде :-)
Марк Карпов
3
Я хотел бы дать вам +10 за предупреждение от злоупотребления макросами!
lunaryorn
Только что создал квест о наилучшей практике макросов. emacs.stackexchange.com/questions/21015 . Надеюсь, @tarsius и другие дают отличные ответы.
Ясуши Сёдзи
13

Макрос, чтобы делать то, что вы хотите

Как своего рода упражнение:

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE."
  `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))

Теперь попробуйте это:

(setq-every "/foo/bar" f-loc1 f-loc2)

Как это работает

Поскольку людям любопытно, как это работает (согласно комментариям), вот объяснение. Чтобы по-настоящему научиться писать макросы, выберите хорошую книгу по Common Lisp (да, Common Lisp, вы сможете делать то же самое в Emacs Lisp, но Common Lisp немного мощнее и имеет лучшие книги, IMHO).

Макросы работают с необработанным кодом. Макросы не оценивают свои аргументы (в отличие от функций). Итак, мы имеем здесь недооцененные valueи коллекции vars, которые для нашего макроса являются просто символами.

prognгруппирует несколько setqформ в одну. Эта вещь:

(mapcar (lambda (x) (list 'setq x value)) vars)

Просто генерирует список setqформ, на примере OP это будет:

((setq f-loc1 "/foo/bar") (setq f-loc2 "/foo/bar"))

Видите ли, форма находится внутри формы обратной кавычки и начинается с запятой ,. Внутри обратной кавычки все цитируется как обычно, но , « mapcarвключенная » оценка временно, поэтому все оценивается во время макроразложения.

Наконец, @удаляет внешнюю скобку из списка с помощью setqs, поэтому мы получаем:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Макросы могут произвольно преобразовать ваш исходный код, не правда ли?

Предостережение

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

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Видите ли, если у вас есть переменная или строка здесь все в порядке, но если вы напишите что-то вроде этого:

(setq-every (my-function-with-side-effects) f-loc1 f-loc2)

Тогда ваша функция будет вызываться более одного раза. Это может быть нежелательно. Вот как это исправить once-only(доступно в пакете MMT ):

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE.

VALUE is only evaluated once."
  (mmt-once-only (value)
    `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars))))

И проблема ушла.

Марк Карпов
источник
1
Было бы неплохо, если бы вы могли добавить некоторые объяснения, как это работает и что делает этот код, более подробно, чтобы в следующий раз люди могли написать что-то подобное самим :)
clemera
Вы не хотите оценивать valueво время расширения макроса.
Tarsius
@tarsius, я не: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Если бы valueони оценивались во время макроразложения, то это было бы заменено фактической строкой. Вы можете поместить вызов функции с побочными эффектами, вместо этого valueон не будет оцениваться, пока не будет запущен расширенный код, попробуйте. valueкак аргумент макроса не оценивается автоматически. Реальная проблема заключается в том, что valueбудет оцениваться несколько раз, что может быть нежелательным, once-onlyможет помочь здесь.
Марк Карпов
1
Вместо mmt-once-only(который требует внешнего депо), вы можете использовать macroexp-let2.
YoungFrog
2
Тьфу. Вопрос требует использования итерации set, а не для переноса setqв макрос. Или просто используйте setqс несколькими аргументами, включая повторение аргументов.
Дрю
7

Поскольку вы можете использовать и манипулировать символами в lisp, вы можете просто перебрать список символов и использовать set.

(dolist (var '(foo bar baz)) (set var 10))

(mapc (lambda (var) (set var 10)) '(foo bar baz))

(loop for var in '(foo bar baz) do (set var 11))

(--each '(foo bar baz) (set it 10))
Джордон Биондо
источник
2
Это не работает для лексически связанных переменных, хотя
npostavs
@npostavs: Да, но это может быть тем, что ОП хочет / нуждается. Если он не использует лексические переменные, то это определенно верный путь. Вы правы, однако, что «переменная» неоднозначна, поэтому в общем случае вопрос может означать любую переменную. Фактический вариант использования не очень хорошо представлен, ИМО.
Дрю