Quando o escopo dinâmico seria útil?

9

Com o escopo dinâmico, um receptor pode acessar as variáveis ​​do chamador. Código pseudo-C:

void foo()
{
    print(x);
}

void bar()
{
    int x = 42;
    foo();
}

Como nunca programei em um idioma que suporte o escopo dinâmico, pergunto-me quais seriam alguns casos de uso do mundo real para o escopo dinâmico.

fredoverflow
fonte
talvez seja mais fácil implementar quando o intérprete é implementado em algum outro idioma específico?
Alf P. Steinbach
2
É de pouca utilidade que a maioria dos idiomas que o usaram (por exemplo, Lisp) não usam mais. Apenas para um exemplo óbvio, a maioria das implementações anteriores do Lisp usavam escopo dinâmico, mas agora todas as principais variantes (por exemplo, CL, Scheme) usam o escopo lexical.
Jerry Coffin
1
@JerryCoffin: Exceções notáveis ​​incluem o Perl e o Emacs Lisp - ambos usavam o escopo dinâmico originalmente, e agora (Perl 5, Emacs 24) têm suporte para o escopo dinâmico e lexical. É bom poder escolher.
21415 Jon Purdy
@JerryCoffin Não corresponde exatamente ao exemplo codificado, mas o javascript ainda faz amplo uso do escopo dinâmico, se eu entender a pergunta corretamente. Ainda tentando pensar em uma vantagem geral, ela fornece que não apenas compensa uma curta vinda do idioma.
Adrian
@JerryCoffin Ainda existe algum escopo dinâmico usado ativamente no Common Lisp, principalmente em torno do controle dinâmico da leitura e impressão.
Vatine 15/12

Respostas:

15

Uma aplicação muito útil do escopo dinâmico é a passagem de parâmetros contextuais sem a necessidade de adicionar novos parâmetros explicitamente a todas as funções em uma pilha de chamadas

Por exemplo, o Clojure oferece suporte ao escopo dinâmico via encadernação , que pode ser usado para reatribuir temporariamente o valor da *out*impressão. Se você reconectar *out*, todas as chamadas para impressão no escopo dinâmico da ligação serão impressas no seu novo fluxo de saída. Muito útil se, por exemplo, você desejar redirecionar toda a saída impressa para algum tipo de log de depuração.

Exemplo: no código abaixo, a função do-stuff será impressa na saída de depuração, e não na saída padrão, mas observe que eu não precisei adicionar um parâmetro de saída ao do-stuff para ativar isso ....

(defn do-stuff [] 
  (do-other-stuff)
  (print "stuff done!"))

(binding [*out* my-debug-output-writer]
  (do-stuff))

Observe que as ligações do Clojure também são localizadas por thread, portanto, você não tem problemas com o uso simultâneo desse recurso. Isso torna as ligações consideravelmente mais seguras que (ab) usando variáveis ​​globais para o mesmo objetivo.

Mikera
fonte
2

(Isenção de responsabilidade: nunca programei em um idioma de escopo dinâmico)

O escopo é muito mais fácil de implementar e potencialmente mais rápido. Com o escopo dinâmico, apenas a tabela de um símbolo é necessária (as variáveis ​​disponíveis no momento). Apenas lê nesta tabela de símbolos para tudo.

Imagine em Python a mesma função.

def bar():
    x = 42;
    foo(42)

def foo(x):
    print x

Quando ligo para barra, coloco x na tabela de símbolos. Quando chamo foo, pego a tabela de símbolos atualmente usada para bar e empurro-a para a pilha. Então chamo foo, que x passou para ele (provavelmente tendo sido colocado na nova tabela de símbolos ao chamar a função). Depois de sair da função, tenho que destruir o novo escopo e restaurar o antigo.

Com o escopo dinâmico, isso não é necessário. Eu só preciso saber as instruções para as quais preciso retornar quando a função terminar, pois nada precisa ser feito na tabela de símbolos.

jsternberg
fonte
"Potencialmente mais rápido" apenas em um intérprete (ingênuo); compiladores podem fazer muito melhor com o escopo lexical do que com o escopo dinâmico. Além disso, "Com o escopo dinâmico, isso não é necessário ..." está errado: com o escopo dinâmico, quando o escopo de uma variável termina (por exemplo, a função retorna), você precisa atualizar a tabela de símbolos para restaurar seu valor anterior. Na verdade, acho que o código normalmente se refere diretamente a um "objeto de símbolo" que teria um campo mutável para o valor atual da variável, muito mais rápido do que fazer uma pesquisa de tabela a cada vez. Mas, ainda assim, o trabalho de atualização não desaparece.
Ryan Culpepper
Ah, eu não sabia que o escopo dinâmico ainda restauraria o valor antes que a função fosse chamada. Eu pensei que todos eles se referiam ao mesmo valor.
Jsternberg
2
sim, ainda há aninhamento. Caso contrário, seria equivalente a tornar todas as variáveis ​​globais e apenas fazer uma atribuição simples a elas.
Ryan Culpepper
2

O tratamento de exceções na maioria dos idiomas utiliza escopo dinâmico; quando ocorrer uma exceção, o controle será transferido de volta para o manipulador mais próximo na pilha de ativação (dinâmica).

Eyvind
fonte
Por favor, comente o motivo do voto negativo. Obrigado!
Eyvind
Esta é uma visão interessante. Você também diria que as returndeclarações na maioria dos idiomas usam escopo dinâmico, porque retornam o controle ao chamador na pilha?
Ruakh12
1

Não tenho 100% de certeza se essa é uma correspondência exata, mas acho que pelo menos chega perto o suficiente em um sentido geral para mostrar onde pode ser útil quebrar ou alterar as regras de escopo.

A linguagem Ruby vem da classe de modelo ERB, que por exemplo no Rails é usada para gerar arquivos html. Se você usar, fica assim:

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

Os bindingponteiros acessam variáveis ​​locais à chamada do método ERB, para que possa acessá-las e usá-las para preencher o modelo. (O código entre os EOFs é uma string, a parte entre <% =%> avaliada como código Ruby pelo ERB e declararia seu próprio escopo como uma função)

Um exemplo do Rails ainda melhor demonstra isso. Em um controlador de artigo, você encontraria algo assim:

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.xml  { render :xml => @posts }
  end
end

O arquivo index.html.erb poderia então usar a variável local @articlesassim (neste caso, a criação de um objeto ERB e a ligação são tratadas pela estrutura do Rails, para que você não a veja aqui):

<ul>
<% @articles.each do |article| %>
  <li><%= article.name</li>
<% end %>
</ul>

Portanto, pelo uso de uma variável de ligação, Ruby permite executar um e o mesmo código de modelo em contextos diferentes.

A classe ERB é apenas um exemplo de uso. O Ruby permite, em geral, obter o estado real de execução com ligações de variáveis ​​e métodos, usando a ligação Kernel #, que é muito útil em qualquer contexto em que você deseja avaliar um método em outro contexto ou deseja manter um contexto para uso posterior.

thorsten müller
fonte
1

Os casos de uso para escopo dinâmico são IHMO os mesmos para variáveis ​​globais. O escopo dinâmico evita alguns dos problemas com variáveis ​​globais, permitindo uma atualização mais controlada da variável.

Alguns casos de uso em que consigo pensar:

  • Log : faz mais sentido agrupar logs por contexto de tempo de execução que a partir de um contexto lexical. Você pode vincular dinamicamente o criador de logs em um manipulador de solicitações, para que todas as funções chamadas de lá compartilhem o mesmo "requestID" nas entradas de log.
  • Redirecionamento de saída
  • Alocadores de recursos : Para acompanhar os recursos alocados para uma ação / solicitação específica.
  • Manipulação de exceção .
  • Comportamento contextual em geral, por exemplo, o Emacs alterando o mapeamento de teclas em determinados contextos .

É claro que o escopo dinâmico não é "absolutamente necessário", mas alivia o ônus de ter que passar dados vagabundos pela cadeia de chamadas ou de implementar proxies globais inteligentes e gerenciadores de contexto.

Porém, novamente, o escopo dinâmico é útil ao lidar com tipos de elementos que muitas vezes são tratados como globais (registro em log, saída, alocação / gerenciamento de recursos, etc.).

ecerulm
fonte
-2

Praticamente não há. Espero que você, por exemplo, nunca use a mesma variável duas vezes.

void foo() {
    print_int(x);
}
void bar() {
    print_string(x);
}

Agora, como você chama foo e bar da mesma função?

Isso é efetivamente semelhante ao simples uso de uma variável global e é ruim pelos mesmos motivos.

DeadMG
fonte
2
x = 42; foo(); x = '42'; bar();?
Luc Danton