Suponha que eu tenha um arquivo chamado elisp-defvar-test.el
contendo:
;;; elisp-defvar-test.el --- -*- lexical-binding: t -*-
(defvar my-dynamic-var)
(defun f1 (x)
"Should return X."
(let ((my-dynamic-var x))
(f2)))
(defun f2 ()
"Returns the current value of `my-dynamic-var'."
my-dynamic-var)
(provide 'elisp-dynamic-test)
;;; elisp-defvar-test.el ends here
Carrego esse arquivo e, em seguida, vou para o buffer temporário e execute:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
retorna 5 conforme o esperado, indicando que o corpo de f1
está tratando my-dynamic-var
como uma variável com escopo dinâmico, conforme o esperado. No entanto, a última forma fornece um erro de variável nula para my-dynamic-var
, indicando que está usando o escopo lexical para essa variável. Isso parece estar em desacordo com a documentação de defvar
, que diz:
O
defvar
formulário também declara a variável como "especial", para que ela seja sempre vinculada dinamicamente, mesmo quelexical-binding
seja t.
Se eu alterar o defvar
formulário no arquivo de teste para fornecer um valor inicial, a variável será sempre tratada como dinâmica, como diz a documentação. Alguém pode explicar por que o escopo de uma variável é determinado pelo fato de ter ou não defvar
sido fornecido um valor inicial ao declarar essa variável?
Aqui está o erro de rastreamento, caso isso importe:
Debugger entered--Lisp error: (void-variable my-dynamic-var)
f2()
(let ((my-dynamic-var 5)) (f2))
(progn (let ((my-dynamic-var 5)) (f2)))
eval((progn (let ((my-dynamic-var 5)) (f2))) t)
elisp--eval-last-sexp(t)
eval-last-sexp(t)
eval-print-last-sexp(nil)
funcall-interactively(eval-print-last-sexp nil)
call-interactively(eval-print-last-sexp nil nil)
command-execute(eval-print-last-sexp)
fonte
Respostas:
O motivo pelo qual os dois são tratados de maneira diferente é principalmente "porque é disso que precisamos". Mais especificamente, a forma de argumento único
defvar
apareceu há muito tempo, mas mais tarde que a outra e era basicamente um "truque" para silenciar os avisos do compilador: no momento da execução, ela não teve nenhum efeito, portanto, como "acidente", significou que o comportamento de silenciamento se(defvar FOO)
aplicava apenas ao arquivo atual (uma vez que o compilador não tinha como saber que esse defvar havia sido executado em outro arquivo).Quando
lexical-binding
foi introduzido no Emacs-24, decidimos reutilizar este(defvar FOO)
formulário, mas isso implica que agora ele tem um efeito.Em parte para preservar o comportamento anterior "afeta apenas o arquivo atual", mas o mais importante é permitir que uma biblioteca use
toto
como um var de escopo dinâmico sem impedir que outras bibliotecas usemtoto
como um var de escopo lexicamente (geralmente a convenção de nomenclatura de prefixo de pacote evita esses conflitos, mas infelizmente não é usado em todos os lugares), o novo comportamento de(defvar FOO)
foi definido para aplicar-se apenas ao arquivo atual e foi refinado, portanto, somente se aplica ao escopo atual (por exemplo, se ele aparecer em uma função, afeta apenas os usos de que var dentro dessa função).Fundamentalmente,
(defvar FOO VAL)
e(defvar FOO)
são apenas duas coisas "completamente diferentes". Por acaso, eles usam a mesma palavra-chave por razões históricas.fonte
(defvar FOO)
torna o novo modo muito mais compatível com o código antigo. Além disso, o IIRC que tem um problema com a solução do CommonLisp é que é muito caro para um intérprete puro como o do Elisp (por exemplo, toda vez que você avalia um,let
é necessário olhar dentro de seu corpo, caso haja algumdeclare
que afete alguns dos outros).Com base na experimentação, acredito que o problema é que,
(defvar VAR)
sem nenhum valor init, apenas afeta as bibliotecas em que aparece.Quando adicionei
(defvar my-dynamic-var)
ao*scratch*
buffer, o erro não ocorreu mais.Originalmente, pensei que isso se devia à avaliação desse formulário, mas notei, em primeiro lugar, que basta visitar o arquivo com esse formulário presente; e, além disso, o simples fato de adicionar (ou remover) esse formulário no buffer, sem avaliar, foi suficiente para alterar o que aconteceu ao avaliar
(let ((my-dynamic-var 5)) (f2))
dentro desse mesmo buffer comeval-last-sexp
.(Não tenho uma compreensão real do que está acontecendo aqui. Acho o comportamento surpreendente, mas não estou familiarizado com os detalhes de como essa funcionalidade é implementada.)
Acrescentarei que essa forma de
defvar
(sem valor init) impede o compilador de bytes de reclamar sobre o uso de uma variável dinâmica definida externamente no arquivo elisp que está sendo compilado, mas por si só não faz com que essa variável sejaboundp
; portanto, não está definindo estritamente a variável. (Observe que, se a variável fosseboundp
, esse problema não ocorreria.)Na prática Suponho que isto vai funcionar ok, desde que você não incluem
(defvar my-dynamic-var)
em qualquer biblioteca de ligação lexical que usa suamy-dynamic-var
variável (que presumivelmente teria uma definição real em outro lugar).Editar:
Graças ao ponteiro de @npostavs nos comentários:
Ambos
eval-last-sexp
eeval-defun
useeval-sexp-add-defvars
para:Especificamente ele localiza todos
defvar
,defconst
edefcustom
casos. (Mesmo quando comentado, eu noto.)Como ele está pesquisando o buffer no momento da chamada, explica como esses formulários podem ter efeito no buffer, mesmo sem serem avaliados, e confirma que o formulário deve aparecer no mesmo arquivo elisp (e também antes do código que está sendo avaliado) .
fonte
eval-sexp-add-defvars
verifica se há defvars no texto do buffer.Não consigo reproduzir isso, avaliar o último trecho funciona bem aqui e retorna 5 conforme o esperado. Tem certeza de que não está avaliando
my-dynamic-var
por conta própria? Isso gerará um erro porque a variável é nula, não foi definida como um valor e só terá um se você a vincular dinamicamente a um.fonte
lexical-binding
nulo antes de avaliar os formulários? Recebo o comportamento que você descreve comlexical-binding
zero, mas quando o configuro como zero, recebo o erro de variável nula.lexical-binding
está definido e avaliado os formulários sequencialmente.my-dynamic-var
um valor dinâmico de nível superior em sua sessão atual? Eu acho que isso poderia marcá-lo permanentemente especial.