Assegnare lo stesso valore a più variabili?


12

A volte ho bisogno di impostare lo stesso valore su più variabili.

In Python, potrei farlo

f_loc1 = f_loc2 = "/foo/bar"

Ma in elisp, sto scrivendo

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

Mi chiedo se c'è un modo per raggiungere questo obiettivo usando "/foo/bar"una sola volta?


1
Ciao Chillar, puoi per favore selezionare la risposta di @tarsius come accettata? La mia risposta dimostra una soluzione che ti dà quello che vuoi, ma come ho detto è "l'esercizio di una sorta" che risolve questa sfida discutibilmente artificiale ma non molto utile nella pratica. tarsuis ha fatto molti sforzi per dimostrare questa ovvia considerazione nella sua risposta, quindi penso che la sua risposta meriti di essere "quella corretta", quindi tutti sono di nuovo felici.
Mark Karpov,

1
Direi che la necessità di assegnare lo stesso valore a più variabili indica un difetto di progettazione che dovrebbe essere corretto piuttosto che cercare zucchero sintattico per coprire :)
lunaryorn,

1
@lunaryorn se desidera impostare sourcee targetper lo stesso percorso all'inizio e potrei cambiare targetin seguito, come impostare loro allora?
ChillarAnand

Mostra più codice, così possiamo dire cosa intendi per "variabile" per il tuo caso d'uso; cioè, che tipo di variabili si sta tentando di impostare. Vedi il mio commento per la risposta di @ JordonBiondo.
Estratto il

Risposte:


21

setq restituisce il valore, quindi puoi semplicemente:

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

Se non vuoi fare affidamento su questo, allora usa:

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

Personalmente eviterei quest'ultimo e scrivo invece:

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

o anche

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

E il primo approccio che userei solo in circostanze piuttosto speciali in cui enfatizza effettivamente l'intenzione o quando l'alternativa sarebbe usare il secondo approccio o altro progn.


Puoi smettere di leggere qui se quanto sopra ti rende felice. Ma penso che sia una buona opportunità per avvisare te e gli altri di non abusare delle macro.


Infine, mentre è allettante scrivere una macro come setq-every"perché è lisp e possiamo", raccomanderei caldamente di non farlo. Guadagni molto poco facendolo:

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

Ma allo stesso tempo rendi molto più difficile per gli altri lettori leggere il codice ed essere sicuro di ciò che accade. setq-everypotrebbe essere implementato in vari modi e altri utenti (incluso un futuro tu) non possono sapere quale scegli l'autore, semplicemente guardando il codice che lo utilizza. [Inizialmente ho fatto alcuni esempi qui, ma quelli sono stati considerati sarcastici e diffidenti da qualcuno.]

Puoi affrontare quel sovraccarico mentale ogni volta che guardi setq-everyo puoi semplicemente vivere con un singolo personaggio in più. (Almeno nel caso in cui devi impostare solo due variabili, ma non ricordo che ho mai dovuto impostare più di due variabili allo stesso valore contemporaneamente. Se devi farlo, allora probabilmente c'è qualcos'altro che non va con il tuo codice.)

D'altra parte, se hai intenzione di utilizzare questa macro più di una mezza dozzina di volte, allora vale la pena indirizzarla. Ad esempio, utilizzo macro come --when-letdalla dash.ellibreria. Ciò rende più difficile per coloro che lo incontrano per la prima volta nel mio codice, ma superare la difficoltà di comprensione vale la pena perché è usato più di poche volte e, almeno secondo me, rende il codice più leggibile .

Si potrebbe obiettare che lo stesso vale per setq-every, ma penso che sia molto improbabile che questa particolare macro venga utilizzata più di qualche volta. Una buona regola empirica è che quando la definizione della macro (inclusa la doc-string) aggiunge più codice di quanto rimuova l'uso della macro, allora la macro molto probabilmente è eccessiva (il contrario non è necessariamente vero).


1
dopo aver impostato una var in setqpuoi fare riferimento al suo nuovo valore, quindi puoi usare anche questa mostruosità: (setq a 10 b a c a d a e a)che imposta abcd ed e su 10.
Jordon Biondo,

1
@JordonBiondo, Sì, ma penso che l'obiettivo di OP sia quello di ridurre la ripetizione nel suo codice :-)
Mark Karpov,

3
Vorrei darti un +10 per l'avvertimento contro l'abuso di macro!
lunaryorn,

Ho appena creato una ricerca sulle migliori pratiche macro. emacs.stackexchange.com/questions/21015 . Spero che @tarsius e altri diano una grande risposta.
Yasushi Shoji

13

Una macro per fare quello che vuoi

Come esercizio di una specie:

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

Ora provalo:

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

Come funziona

Dato che le persone sono curiose di sapere come funziona (secondo i commenti), ecco una spiegazione. Per imparare davvero a scrivere macro scegli un buon libro Common Lisp (sì, Common Lisp, sarai in grado di fare le stesse cose in Emacs Lisp, ma Common Lisp è un po 'più potente e ha libri migliori, IMHO).

Le macro operano su codice non elaborato. Le macro non valutano i loro argomenti (a differenza delle funzioni). Quindi abbiamo qui non valutato valuee la raccolta di vars, che per la nostra macro sono solo simboli.

prognraggruppa diverse setqforme in una. Questa cosa:

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

Genera solo un elenco di setqmoduli, usando l'esempio di OP sarà:

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

Vedete, il modulo è all'interno del modulo backquote ed è preceduto da una virgola ,. All'interno del modulo retroquistato, tutto viene quotato come al solito, ma la , valutazione "attiva" temporaneamente, quindi l'intero mapcarviene valutato al momento dell'espansione macro.

@Rimuove infine la parentesi esterna dall'elenco con setqs, quindi otteniamo:

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

Le macro possono trasformare arbitrariamente il tuo codice sorgente, non è fantastico?

Un avvertimento

Ecco un piccolo avvertimento, il primo argomento verrà valutato più volte, poiché questa macro si espande essenzialmente a quanto segue:

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

Vedi, se hai una variabile o una stringa qui va bene, ma se scrivi qualcosa del genere:

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

Quindi la tua funzione verrà chiamata più di una volta. Questo potrebbe essere indesiderabile. Ecco come risolverlo con l'aiuto di once-only(disponibile nel pacchetto 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))))

E il problema è sparito.


1
Sarebbe bello se potessi aggiungere alcune spiegazioni su come funziona e cosa sta facendo questo codice in modo più dettagliato, quindi la prossima volta le persone possono scrivere qualcosa del genere da sole :)
Clemera,

Non vuoi valutare valueal momento dell'espansione macro.
tarsius,

@tarsius, non lo faccio: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Se valuefossero valutati al momento della macroespansione, verrebbero sostituiti con una stringa effettiva. Puoi mettere la funzione chiamata con effetti collaterali, al posto di value, non verrà valutata fino a quando non viene eseguito il codice espanso, provalo. valuecome argomento della macro non viene valutato automaticamente. Il vero problema è che valuesaranno valutati più volte, il che può essere indesiderabile, once-onlypuò aiutare qui.
Mark Karpov,

1
Invece di mmt-once-only(che richiede un dep esterno), è possibile utilizzare macroexp-let2.
YoungFrog,

2
Ugh. La domanda grida di usare l'iterazione set, non di racchiuderla setqin una macro. O semplicemente usare setqcon più arg, inclusa la ripetizione di arg.
Ha

7

Dal momento che è possibile utilizzare e manipolare i simboli in lisp, è possibile semplicemente scorrere su un elenco di simboli e utilizzare 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
Tuttavia, ciò non funziona per le variabili associate al lessico
npostavs,

@npostavs: True, ma potrebbe essere quello che l'OP vuole / ha bisogno. Se non sta usando variabili lessicali, allora è sicuramente la strada da percorrere. Hai ragione, però, che "variabile" è ambigua, quindi in generale la domanda potrebbe significare qualsiasi tipo di variabile. Il caso d'uso effettivo non è ben presentato, IMO.
Estratto il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.