Atribuindo o mesmo valor a várias variáveis?

12

Às vezes, preciso definir o mesmo valor para várias variáveis.

Em Python, eu poderia fazer

f_loc1 = f_loc2 = "/foo/bar"

Mas no Elisp, estou escrevendo

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

Gostaria de saber se existe uma maneira de conseguir isso usando "/foo/bar"apenas uma vez?

ChillarAnand
fonte
1
Olá, Chillar, você pode selecionar a resposta @tarsius 'como a aceita? Minha resposta demonstra uma solução que lhe dá o que você deseja, mas, como eu disse, é um "tipo de exercício" que resolve esse desafio indiscutivelmente artificial, mas não muito útil na prática. tarsuis se esforçou muito para demonstrar essa consideração óbvia em sua resposta, então acho que sua resposta merece ser "a correta", para que todos fiquem felizes novamente.
22815 Mark-Karpov #
1
Eu diria que a necessidade de atribuir o mesmo valor a múltiplas variáveis indica uma falha de projeto que deve ser fixo ao invés de olhar para o açúcar sintático para encobrir :)
lunaryorn
1
@ lunaryorn se quiser definir sourcee targetpara o mesmo caminho no início e eu posso mudar targetmais tarde, como defini-los, então?
precisa saber é o seguinte
Mostre mais código, para que possamos dizer o que você quer dizer com "variável" para seu caso de uso; ou seja, que tipo de variáveis ​​você está tentando definir. Veja meu comentário para a resposta de @ JordonBiondo.
Drew

Respostas:

21

setq retorna o valor, então você pode:

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

Se você não quiser confiar nisso, use:

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

Pessoalmente, eu evitaria o último e escreveria:

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

ou mesmo

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

E a primeira abordagem que eu usaria apenas em circunstâncias bastante especiais, onde ela realmente enfatiza a intenção, ou quando a alternativa seria usar a segunda abordagem ou então progn.


Você pode parar de ler aqui se o texto acima o fizer feliz. Mas acho que é uma boa oportunidade para avisar você e outras pessoas a não abusarem de macros.


Por fim, embora seja tentador escrever uma macro como setq-every"porque isso é lisp e nós podemos", recomendo fortemente que não o faça. Você ganha muito pouco fazendo isso:

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

Mas, ao mesmo tempo, você torna muito mais difícil para outros leitores ler o código e ter certeza do que acontece. setq-everypode ser implementado de várias maneiras e outros usuários (incluindo um futuro você) não podem saber qual deles o autor escolhe, apenas observando o código que o utiliza. [Eu originalmente fiz alguns exemplos aqui, mas esses foram considerados sarcásticos e desmedidos por alguém.]

Você pode lidar com essa sobrecarga mental toda vez que olhar setq-everyou apenas viver com um único personagem extra. (Pelo menos no caso em que você precisa definir apenas duas variáveis, mas não me lembro, tive que definir mais de duas variáveis ​​para o mesmo valor de uma só vez. Se você precisar fazer isso, provavelmente há algo errado. com o seu código.)

Por outro lado, se você realmente usar essa macro mais do que meia dúzia de vezes, o indireto adicional poderá valer a pena. Por exemplo, eu uso macros como --when-letna dash.elbiblioteca. Isso torna mais difícil para quem o encontra primeiro no meu código, mas superar essa dificuldade no entendimento vale a pena porque é usado mais do que apenas algumas vezes e, pelo menos na minha opinião, torna o código mais legível .

Alguém poderia argumentar que o mesmo se aplica setq-every, mas acho que é muito improvável que essa macro em particular seja usada mais do que algumas vezes. Uma boa regra geral é que, quando a definição da macro (incluindo a sequência de documentos) adiciona mais código do que o uso da macro remove, a macro provavelmente é um exagero (embora o contrário não seja necessariamente verdadeiro).

tarso
fonte
1
depois de definir uma var em setqque você pode fazer referência a ele é novo valor, então você poderia usar essa monstruosidade muito: (setq a 10 b a c a d a e a)que conjuntos abcd e e a 10.
Jordon Biondo
1
@JordonBiondo, Sim, mas acho que visam de OP é reduzir a repetição em seu código :-)
Mark Karpov
3
Gostaria de lhe dar um +10 pelo aviso contra abuso de macro!
lunaryorn
Acabei de criar uma busca sobre as melhores práticas de macro. emacs.stackexchange.com/questions/21015 . Hope @tarsius e outros dão ótimas respostas.
Yasushi Shoji
13

Uma macro para fazer o que você deseja

Como um exercício de uma espécie:

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

Agora tente:

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

Como funciona

Como as pessoas têm curiosidade em saber como isso funciona (de acordo com os comentários), aqui está uma explicação. Para realmente aprender a escrever macros, escolha um bom livro Common Lisp (sim, Common Lisp, você poderá fazer o mesmo no Emacs Lisp, mas o Common Lisp é um pouco mais poderoso e possui melhores livros, IMHO).

Macros operam em código bruto. Macros não avaliam seus argumentos (ao contrário de funções). Portanto, temos aqui uma avaliação valuee uma coleção de vars, que para nossa macro são apenas símbolos.

prognagrupa várias setqformas em uma. Essa coisa:

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

Apenas gera uma lista de setqformulários, usando o exemplo do OP, será:

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

Veja, o formulário está dentro do formulário de aspas anteriores e é prefixado com uma vírgula ,. Dentro do formulário com aspas retroativas, tudo é cotado como de costume, mas , a avaliação mapcaré ativada temporariamente, e toda a avaliação é avaliada no momento da macroexpansão.

Finalmente @remove os parênteses externos da lista com setqs, para obtermos:

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

As macros podem transformar arbitrariamente seu código-fonte, não é ótimo?

Uma ressalva

Aqui está uma pequena ressalva: o primeiro argumento será avaliado várias vezes, porque essa macro se expande essencialmente para o seguinte:

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

Veja bem, se você tiver uma variável ou string aqui, tudo bem, mas se você escrever algo como isto:

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

Então sua função será chamada mais de uma vez. Isso pode ser indesejável. Aqui está como corrigi-lo com a ajuda de once-only(disponível no pacote 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 o problema se foi.

Mark Karpov
fonte
1
Seria bom se você pode adicionar algumas explicações como isto funciona eo que este código está fazendo em mais detalhes, tão próxima vez que as pessoas podem escrever algo como isto-se :)
clemera
Você não deseja avaliar valueno momento da expansão macro.
tarsius
@ tarso, eu não: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Se valuefosse avaliado no momento da macroexpansão, seria substituído por uma string real. Você pode colocar uma chamada de função com efeitos colaterais, no lugar de value, ela não será avaliada até que o código expandido seja executado, tente. valuecomo argumento de macro não é avaliado automaticamente. O problema real é que valueserá avaliado várias vezes, o que pode ser indesejável, once-onlypode ajudar aqui.
22815 Mark Karpov #
1
Em vez de mmt-once-only(que requer um dep externo), você pode usar macroexp-let2.
YoungFrog
2
Ugh. A questão clama por usar iteração com set, para não quebrar setqem uma macro. Ou apenas use setqcom vários argumentos, incluindo a repetição de argumentos.
Desenhou
7

Como você pode usar e manipular símbolos no lisp, você pode simplesmente fazer um loop sobre uma lista de símbolos e usá-los 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
fonte
2
Isso não funciona para variáveis lexically vinculados embora
npostavs
@npostavs: Verdade, mas pode ser o que o OP deseja / precisa. Se ele não está usando variáveis ​​lexicais, é definitivamente o caminho a percorrer. Você está certo, porém, de que "variável" é ambígua, portanto, em geral, a questão pode significar qualquer tipo de variável. O caso de uso real não está bem apresentado, IMO.
Tirou