Присвоєння однакового значення декільком змінним?


12

Іноді мені потрібно встановити одне значення для кількох змінних.

У Python я міг би зробити

f_loc1 = f_loc2 = "/foo/bar"

Але в Еліспі я пишу

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

Мені цікаво, чи є спосіб досягти цього, використовуючи "/foo/bar"лише один раз?


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, але я думаю, що дуже малоймовірно, що саме цей макрос буде використовуватися більше ніж кілька разів. Хорошим правилом є те, що коли визначення макросу (включаючи doc-рядок) додає більше коду, ніж використання макросу видаляє, тоді макрос дуже ймовірно є надмірним (хоча зворотне не обов'язково відповідає дійсності).


1
після того, як ви встановите один var, setqви можете посилатися на це нове значення, тож ви можете використовувати і цю жахливість: (setq a 10 b a c a d a e a)яка встановлює abcd і e на 10.
Jordon Biondo

1
@JordonBiondo, Так, але я думаю, що мета ОП - зменшити повторення його коду :-)
Марк Карпов

3
Я хотів би дати вам +10 за попередження про зловживання макросом!
місячник

Щойно створений квест про макро-кращу практику. emacs.stackexchange.com/questions/21015 . Надія @tarsius та інші дають чудові відповіді.
Yasushi Shoji

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)

Як це працює

Оскільки людям цікаво, як це працює (за коментарями), ось пояснення. Щоб дійсно навчитися писати макроси, виберіть хорошу загальну книгу Lisp (так, Common Lisp, ви зможете робити те саме в Emacs Lisp, але Common Lisp трохи потужніший і має кращі книги, IMHO).

Макроси працюють на необробленому коді. Макроси не оцінюють їх аргументи (на відміну від функцій). Отже, ми маємо тут неоцінену valueі колекцію vars, які для нашого макросу є просто символами.

prognгрупує кілька setqформ в одну. Ця річ:

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

Просто формує список setqформ, використовуючи приклад ОП, це буде:

((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, я не: (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: Це правда, але це може бути те, що хоче / потребує ОП. Якщо s? Він не використовує лексичні змінні, то це, безумовно, шлях. Ти маєш рацію, що "змінна" є неоднозначною, тому загалом питання може означати будь-яку змінну. Фактичний випадок використання недостатньо представлений, IMO.
Дрю
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.