Przypisywanie tej samej wartości do wielu zmiennych?

12

Czasami muszę ustawić tę samą wartość dla wielu zmiennych.

W Pythonie mogłem

f_loc1 = f_loc2 = "/foo/bar"

Ale w elisp piszę

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

Zastanawiam się, czy istnieje sposób na osiągnięcie tego celu "/foo/bar"tylko raz?

ChillarAnand
źródło
1
Cześć, Chillar, czy możesz wybrać odpowiedź @tarsius jako zaakceptowaną? Moja odpowiedź pokazuje rozwiązanie, które daje ci to, czego chcesz, ale jak powiedziałem, jest to „rodzaj ćwiczeń”, które rozwiązują to prawdopodobnie sztuczne wyzwanie, ale niezbyt przydatne w praktyce. tarsuis włożył wiele wysiłku w wykazanie tego oczywistego rozważania w swojej odpowiedzi, więc myślę, że jego odpowiedź zasługuje na „poprawność”, więc wszyscy znów są szczęśliwi.
Mark Karpov
1
Argumentowałbym, że potrzeba przypisania tej samej wartości do wielu zmiennych wskazuje na wadę projektową, którą należy naprawić, zamiast szukać cukru syntaktycznego na pokrycie :)
lunaryorn
1
@lunaryorn, jeśli chcesz ustawić sourcei tę targetsamą ścieżkę na początku, a ja mogę zmienić targetpóźniej, jak je ustawić?
ChillarAnand
Pokaż więcej kodu, abyśmy mogli powiedzieć, co rozumiesz przez „zmienną” dla twojego przypadku użycia; tzn. jakie zmienne próbujesz ustawić. Zobacz mój komentarz do odpowiedzi @ JordonBiondo.
Drew

Odpowiedzi:

21

setq zwraca wartość, dzięki czemu możesz po prostu:

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

Jeśli nie chcesz na tym polegać, użyj:

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

Osobiście unikałbym tego drugiego i zamiast tego napisałbym:

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

lub nawet

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

I pierwsze podejście zastosowałbym tylko w dość szczególnych okolicznościach, w których faktycznie podkreśla intencję, lub gdy alternatywą byłoby użycie drugiego podejścia, albo inaczej progn.


Możesz przestać czytać tutaj, jeśli powyższe sprawia ci przyjemność. Ale myślę, że to dobra okazja, aby ostrzec ciebie i innych przed nadużywaniem makr.


Wreszcie, choć kuszące jest pisanie makra typu setq-every„ponieważ jest to seplenienie i możemy”, zdecydowanie odradzam to. Zyskujesz bardzo niewiele, robiąc to:

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

Ale jednocześnie znacznie utrudniasz innym czytelnikom czytanie kodu i upewnienie się, co się stanie. setq-everymoże być zaimplementowany na różne sposoby, a inni użytkownicy (w tym przyszli, których jesteś) nie mogą wiedzieć, który autor wybrał, po prostu patrząc na kod, który go używa. [Pierwotnie podałem tutaj kilka przykładów, ale zostały one przez kogoś uznane za sarkastyczne i rant.]

Możesz poradzić sobie z tym mentalnym kosztem za każdym razem, gdy na niego patrzysz, setq-everylub możesz po prostu żyć z jedną dodatkową postacią. (Przynajmniej w przypadku, gdy musisz ustawić tylko dwie zmienne, ale nie pamiętam, że kiedykolwiek musiałem ustawić więcej niż dwie zmienne na tę samą wartość. Jeśli musisz to zrobić, to prawdopodobnie jest coś innego złego z twoim kodem.)

Z drugiej strony, jeśli naprawdę zamierzasz używać tego makra więcej niż może kilkanaście razy, dodatkowa pośrednia może być tego warta. Na przykład używam makr jak --when-letz dash.elbiblioteki. Utrudnia to tym, którzy po raz pierwszy napotykają go w moim kodzie, ale warto przezwyciężyć tę trudność ze zrozumieniem, ponieważ jest on używany więcej niż tylko kilka razy, a przynajmniej moim zdaniem sprawia, że ​​kod jest bardziej czytelny .

Można argumentować, że to samo dotyczy setq-every, ale myślę, że jest bardzo mało prawdopodobne, aby to konkretne makro zostało użyte więcej niż kilka razy. Dobrą zasadą jest to, że gdy definicja makra (w tym ciąg doc) dodaje więcej kodu niż użycie makra usuwa, wówczas makro najprawdopodobniej jest nadmiernie zabójcze (choć sytuacja odwrotna nie musi być prawdziwa).

Tarsjusz
źródło
1
po ustawieniu jednego wariantu setqmożesz odnieść się do jego nowej wartości, więc możesz także użyć tej potworności: (setq a 10 b a c a d a e a)która ustawia abcd i e na 10.
Jordon Biondo
1
@JordonBiondo, Tak, ale myślę, że celem OP jest ograniczenie liczby powtórzeń w jego kodzie :-)
Mark Karpov
3
Chciałbym dać ci +10 za ostrzeżenie przed nadużyciami makr!
lunaryorn
Właśnie utworzyłem zapytanie o najlepszą praktykę makr. emacs.stackexchange.com/questions/21015 . Hope @tarsius i inni dają świetne odpowiedzi.
Yasushi Shoji,
13

Makro do robienia tego, co chcesz

Jako swego rodzaju ćwiczenie:

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

Teraz spróbuj:

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

Jak to działa

Ponieważ ludzie są ciekawi, jak to działa (zgodnie z komentarzami), oto wyjaśnienie. Aby naprawdę nauczyć się pisać makra, wybierz dobrą książkę Common Lisp (tak, Common Lisp, będziesz mógł robić te same rzeczy w Emacs Lisp, ale Common Lisp jest nieco potężniejszy i ma lepsze książki, IMHO).

Makra działają na surowym kodzie. Makra nie oceniają swoich argumentów (w przeciwieństwie do funkcji). Mamy więc tutaj nieocenioną valuei kolekcję vars, które dla naszego makra są tylko symbolami.

progngrupuje kilka setqformularzy w jeden. Ta rzecz:

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

Po prostu generuje listę setqformularzy, na przykładzie OP będzie to:

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

Widzisz, formularz znajduje się w formularzu cudzysłowu i jest poprzedzony przecinkiem ,. Wewnątrz cudzysłowu wszystko jest cytowane jak zwykle, ale , ocena „włącza” tymczasowo, więc całość mapcarjest oceniana w czasie makroekspresji.

Wreszcie @usuwa zewnętrzny nawias z listy za pomocą setqs, więc otrzymujemy:

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

Makra mogą dowolnie przekształcać kod źródłowy, prawda?

Zastrzeżenie

Oto małe zastrzeżenie, pierwszy argument zostanie oceniony kilka razy, ponieważ makro to zasadniczo rozwija się w następujący sposób:

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

Widzisz, jeśli masz tutaj zmienną lub ciąg znaków, jest OK, ale jeśli napiszesz coś takiego:

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

Wtedy twoja funkcja zostanie wywołana więcej niż raz. Może to być niepożądane. Oto jak to naprawić za pomocą once-only(dostępnego w pakiecie 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))))

Problem zniknął.

Mark Karpov
źródło
1
Byłoby miło, gdyby można dodać kilka wyjaśnień, jak to działa i co ten kod robi bardziej szczegółowo, więc ludzie następnym razem może napisać coś takiego sobie :)
clemera
Nie chcesz oceniać valuew czasie makroekspansji.
Tarsjusz
@tarsius, ja nie: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Gdyby valuezostały ocenione w czasie makroekspresji, zostałby zastąpiony rzeczywistym ciągiem znaków. Możesz umieścić wywołanie funkcji z efektami ubocznymi, zamiast tego valuenie będzie ono oceniane, dopóki nie zostanie uruchomiony rozszerzony kod, spróbuj. valueponieważ argument makra nie jest automatycznie oceniany. Prawdziwy problem polega na tym value, że zostanie to kilkakrotnie ocenione, co może być niepożądane, once-onlymoże tutaj pomóc.
Mark Karpov,
1
Zamiast mmt-once-only(który wymaga zewnętrznego dep), możesz użyć macroexp-let2.
YoungFrog,
2
Ugh. Pytanie woła o użycie iteracji set, a nie zawijanie setqmakra. Lub po prostu użyj setqz wieloma argumentami, w tym powtarzaniem argumentów.
Drew
7

Ponieważ możesz używać i manipulować symbolami w lisp, możesz po prostu przeglądać listę symboli i używać 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))
Jordon Biondo
źródło
2
Nie działa to jednak dla zmiennych powiązanych leksykalnie
npostavs
@npostavs: Prawda, ale może być tym, czego OP chce / potrzebuje. Jeśli nie używa zmiennych leksykalnych, jest to zdecydowanie najlepsza droga. Masz jednak rację, że ta „zmienna” jest niejednoznaczna, więc ogólnie pytanie może oznaczać dowolną zmienną. Rzeczywisty przypadek użycia nie jest dobrze przedstawiony, IMO.
Drew