Gleichen Wert mehreren Variablen zuweisen?

12

Manchmal muss ich denselben Wert auf mehrere Variablen setzen.

In Python könnte ich tun

f_loc1 = f_loc2 = "/foo/bar"

Aber in Elisp schreibe ich

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

Ich frage mich, ob es einen Weg gibt, dies mit "/foo/bar"nur einem Mal zu erreichen .

ChillarAnand
quelle
1
Hallo Chillar, kannst du bitte die Antwort von @tarsius als die akzeptierte auswählen? Meine Antwort zeigt eine Lösung, die Ihnen das gibt, was Sie wollen, aber wie gesagt, es ist "Übung einer Art", die diese wohl künstliche Herausforderung löst, aber in der Praxis nicht sehr nützlich ist. tarsuis hat viel Mühe darauf verwendet, diese offensichtliche Überlegung in seiner Antwort zu demonstrieren. Ich denke, seine Antwort verdient es, "die Richtige" zu sein, sodass alle wieder glücklich sind.
Mark Karpov
1
Ich würde argumentieren, dass die Notwendigkeit, mehreren Variablen denselben Wert zuzuweisen, auf einen Konstruktionsfehler hindeutet, der behoben werden sollte, anstatt nach syntaktischem Zucker zu suchen, um ihn zu vertuschen :)
Mondschein
1
@lunaryorn Wenn du am Anfang source& targetauf den gleichen Pfad setzen willst und ich ihn targetspäter möglicherweise ändere , wie setze ich sie dann?
ChillarAnand
Zeigen Sie mehr Code an, damit wir erkennen können, was Sie für Ihren Anwendungsfall unter "Variable" verstehen. dh, welche Art von Variablen Sie festlegen möchten. Siehe meinen Kommentar zu @ JordonBiondos Antwort.
Drew

Antworten:

21

setq gibt den Wert zurück, so dass Sie einfach:

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

Wenn Sie sich nicht darauf verlassen möchten, verwenden Sie:

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

Persönlich würde ich letzteres vermeiden und stattdessen schreiben:

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

oder auch

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

Und der allererste Ansatz würde ich nur in ganz besonderen Situationen anwenden, in denen er tatsächlich die Absicht betont , oder wenn die Alternative darin besteht, den zweiten Ansatz zu verwenden, oder sonst progn.


Sie können hier aufhören zu lesen, wenn das oben Genannte Sie glücklich macht. Aber ich denke, dies ist eine gute Gelegenheit, Sie und andere zu warnen, Makros nicht zu missbrauchen.


Auch wenn es verlockend ist, ein Makro wie setq-every"weil das lispelt und wir es können" zu schreiben , würde ich dringend empfehlen, dies nicht zu tun. Sie gewinnen sehr wenig, indem Sie es tun:

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

Gleichzeitig erschweren Sie anderen Lesern das Lesen des Codes und stellen sicher, was passiert. setq-everyEs kann auf verschiedene Arten implementiert werden, und andere Benutzer (einschließlich einer Zukunft, für die Sie sich entschieden haben) können nicht wissen, für welche sie sich entscheiden. [Ursprünglich habe ich hier einige Beispiele angeführt, aber diese wurden von jemandem als sarkastisch und beschimpft angesehen.]

Sie können mit diesem mentalen Overhead jedes Mal umgehen, wenn Sie es sich ansehen, setq-everyoder Sie können nur mit einem einzigen zusätzlichen Charakter leben. (Zumindest für den Fall, dass Sie nur zwei Variablen setzen müssen, aber ich kann mich nicht erinnern, dass ich jemals mehr als zwei Variablen auf einmal auf den gleichen Wert setzen musste. Wenn Sie das tun müssen, ist wahrscheinlich etwas anderes falsch mit deinem Code.)

Wenn Sie dieses Makro jedoch tatsächlich mehr als ein halbes Dutzend Mal verwenden, ist die zusätzliche Indirektion möglicherweise die Mühe wert. Zum Beispiel verwende ich Makros wie --when-letaus der dash.elBibliothek. Das macht es denjenigen schwerer, die zum ersten Mal in meinem Code darauf stoßen, aber es lohnt sich, diese Verständnisschwierigkeiten zu überwinden, da der Code mehr als nur ein paar Mal verwendet wird und zumindest meiner Meinung nach besser lesbar ist .

Man könnte argumentieren, dass das Gleiche gilt setq-every, aber ich denke, dass es sehr unwahrscheinlich ist, dass dieses spezielle Makro mehr als ein paar Mal verwendet wird. Eine gute Faustregel ist, dass, wenn die Definition des Makros (einschließlich der doc-Zeichenfolge) mehr Code hinzufügt als die Verwendung des Makros entfernt, das Makro sehr wahrscheinlich überfordert ist (das Gegenteil ist jedoch nicht unbedingt der Fall).

Tarsius
quelle
1
Nachdem Sie eine Variable eingegeben setqhaben, können Sie auf ihren neuen Wert verweisen, sodass Sie auch diese Monstrosität verwenden können: (setq a 10 b a c a d a e a)abcd und e werden auf 10 gesetzt.
Jordon Biondo
1
@JordonBiondo, ja, aber ich denke Ziel von OP ist es, die Wiederholung in seinem Code zu reduzieren :-)
Mark Karpov
3
Ich möchte Ihnen eine +10 für die Warnung vor Makromissbrauch geben!
Lunaryorn
Ich habe gerade eine Quest über bewährte Vorgehensweisen für Makros erstellt. emacs.stackexchange.com/questions/21015 . Hoffe @tarsius und andere geben tolle Antworten.
Yasushi Shoji
13

Ein Makro, um zu tun, was Sie wollen

Als eine Art Übung:

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

Probieren Sie es jetzt aus:

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

Wie funktioniert es

Da die Leute neugierig sind, wie es funktioniert (laut Kommentaren), folgt hier eine Erklärung. Um wirklich zu lernen, wie man Makros schreibt, wählen Sie ein gutes Common Lisp-Buch (ja, Common Lisp, Sie werden in Emacs Lisp dasselbe tun können, aber Common Lisp ist ein bisschen leistungsfähiger und hat bessere Bücher, IMHO).

Makros arbeiten mit Rohcode. Makros werten ihre Argumente nicht aus (im Gegensatz zu Funktionen). Wir haben also hier unbewertet valueund eine Sammlung von vars, die für unser Makro nur Symbole sind.

progngruppiert mehrere setqFormen zu einer. Dieses Ding:

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

Erzeugt einfach eine Liste von setqFormularen, am Beispiel von OP:

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

Sie sehen, das Formular befindet sich innerhalb des Backquote-Formulars und wird mit einem Komma vorangestellt ,. Innerhalb des Backquoted-Formulars wird alles wie gewohnt angegeben, die , Auswertung wird jedoch vorübergehend "eingeschaltet", sodass mapcardas Ganze zur Makroexpansionszeit ausgewertet wird.

@Entfernt schließlich die äußere Klammer von der Liste mit setqs, so erhalten wir:

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

Makros können Ihren Quellcode beliebig verändern, ist das nicht großartig?

Eine Einschränkung

Hier ist eine kleine Einschränkung, das erste Argument wird mehrmals ausgewertet, da dieses Makro im Wesentlichen wie folgt erweitert wird:

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

Sie sehen, wenn Sie eine Variable oder einen String hier haben, ist es in Ordnung, aber wenn Sie so etwas schreiben:

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

Dann wird Ihre Funktion mehrmals aufgerufen. Dies kann unerwünscht sein. So beheben Sie das Problem mithilfe von once-only(im MMT-Paket verfügbar ):

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

Und das Problem ist weg.

Mark Karpov
quelle
1
Wäre nett, wenn Sie ein paar Erklärungen hinzufügen könnten, wie das funktioniert und was dieser Code ausführlicher macht, damit die Leute das nächste Mal so etwas selbst schreiben können :)
clemera 06.09.15
Sie möchten nicht zur valueMakro-Expansionszeit auswerten .
Tarsius
@tarsius, ich weiß nicht: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Wenn sie valuezur Makroexpansionszeit ausgewertet werden, wird sie durch die tatsächliche Zeichenfolge ersetzt. Sie können einen Funktionsaufruf mit Nebenwirkungen verwenden, valuestattdessen wird er erst ausgewertet, wenn erweiterter Code ausgeführt wird. Probieren Sie es aus. valueals Argument von Makro wird nicht automatisch ausgewertet. Das eigentliche Problem ist, dass valuemehrmals ausgewertet wird, was möglicherweise unerwünscht ist, once-onlykann hier Abhilfe schaffen.
Mark Karpov
1
Anstelle von mmt-once-only(was eine externe Dep erfordert) könnten Sie verwenden macroexp-let2.
YoungFrog
2
Pfui. Die Frage schreit nach Iteration set, nicht nach setqMakros. Oder einfach setqmit mehreren Argumenten verwenden, einschließlich der Wiederholung von Argumenten.
Drew
7

Da Sie Symbole in lisp verwenden und bearbeiten können, können Sie einfach eine Liste von Symbolen durchlaufen und verwenden 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
quelle
2
Dies funktioniert jedoch nicht für lexikalisch gebundene Variablen
npostavs 06.09.15
@npostavs: Stimmt, aber es könnte sein, was das OP will / braucht. Wenn er keine lexikalischen Variablen verwendet, ist dies definitiv der richtige Weg. Sie haben jedoch Recht, dass "Variable" mehrdeutig ist, sodass die Frage im Allgemeinen jede Art von Variable bedeuten kann. Der tatsächliche Anwendungsfall ist nicht gut dargestellt, IMO.
Drew