Escopo (computação)
Em Ciência da Computação escopo é um contexto delimitante aos quais valores e expressões estão associados. Linguagens de programação têm diversos tipos de escopos. O tipo de escopo vai determinar quais tipos de entidades este pode conter e como estas são afetadas, em outras palavras, a sua semântica. Normalmente, o escopo é utilizado para definir o grau de ocultação da informação, isto é, a visibilidade e acessibilidade às variáveis em diferentes partes do programa. Escopos podem:
- Conter declarações ou definições de identificadores;
- Conter instruções e/ou expressões, que definem um algoritmo executável ou parte dele;
- aninhar ou ser aninhados.
Um espaço de nomes é um escopo que usa a natureza envolvente do escopo para agrupar identificadores relacionados logicamente sob um identificador único. Assim, os escopos podem afetar a resolução de nomes pelo seu conteúdo.
Variáveis são associadas com os escopos. Diferentes tipos de escopo afetam como variáveis locais são vinculadas. Isto tem conseqüências diferentes dependendo se a linguagem tem escopo estático (léxico) ou escopo dinâmico.
História
[editar | editar código-fonte]O escopo léxico foi utilizado pela ALGOL e foi seguida pela maioria das outras linguagens de programação, desde então. O escopo estático (ou léxico) também foi introduzido em LISP 1.5 (Através do dispositivo de Funarg (Functional argument) desenvolvido por Steve Russell, trabalhando com John McCarthy). O interpretador Lisp original (1960) e os primeiros Lisps usavam escopo dinâmico, mas os descendentes das linguagens de escopo dinâmico muitas vezes adotaram o escopo estático; A linguagem Common Lisp tem tanto escopo estático quanto dinâmico, enquanto Scheme usa exclusivamente escopo estático. Perl é uma outra linguagem com escopo dinâmico, que acrescentou escopo estático mais tarde. Linguagens como o Pascal e C sempre tiveram escopo léxico, uma vez que ambas são influenciadas pelas ideias surgidas com a ALGOL 60[1] (embora C não inclua funcões aninhadas lexicamente).
Exemplo
[editar | editar código-fonte]O exemplo a seguir mostra vários escopos declarados na linguagem C#:
namespace N
{ // escopo de namespace, meramente identificadores de grupos
class C
{ // escopo de classe, define/declara variáveis membro e funções
void f (bool b)
{ // escopo do bloco mais externo (função), inclui instruções executáveis
if (b)
{ // escopo do bloco mais interno por declarações executadas condicionalmente
// (Note que, ambos escopos de bloco estão sem nome.)
…
}
}
}
}
Escopo léxico versus escopo dinâmico
[editar | editar código-fonte]Uma das razões fundamentais para o uso de escopos é manter as variáveis em diferentes partes do programa distintas umas das outras. Dado que há somente um pequeno número de nomes curtos de variáveis e que os programadores compartilham hábitos de nomeação de variáveis (por exemplo i
para um índice de vetor), em qualquer programa de tamanho moderado o mesmo nome de variável será utilizado em vários escopos diferentes. A questão de como combinar as várias ocorrências de variáveis aos nomes adequados é geralmente respondida de duas maneiras: escopo léxico e escopo dinâmico.
Escopo léxico
[editar | editar código-fonte]O escopo léxico ou estático foi introduzido pela linguagem ALGOL 60. O escopo é assim denominado, porque pode ser determinado estaticamente, ou seja, antes da execução.[1] O escopo léxico define o escopo em termos da estrutura léxica do programa.[2] Com escopo léxico, um nome sempre se refere ao seu ambiente léxico (mais ou menos) local. Esta é uma propriedade do texto do programa e é feita independente da pilha de chamadas em tempo de execução pela implementação da linguagem. Ou seja, O escopo léxico de uma declaração é a parte do texto do programa, onde a utilização do identificador é uma referência a essa declaração particular do identificador.[3] Pelo fato de esta correspondência só exigir a análise do texto do programa estático, este tipo de delimitação de escopo é também chamado de escopo estático.
O escopo estático é padrão em todas as linguagens baseadas na ALGOL, tais como Pascal, ADA e C, bem como em linguagens funcionais modernas, tais como ML e Haskell, pois permite que o programador possa elaborar raciocínios sobre valores, parâmetros e referências a objetos (ou seja, variáveis, constantes, funções etc), como substituições de nome simples. Isso torna muito mais fácil fazer o código modular e se raciocinar sobre ele, já que a estrutura local de nomeação pode ser entendida isoladamente. Devido ao entendimento de que o escopo estático torna a programação mais confiável, há uma tendência a se rejeitar o escopo dinâmico..[4] Em contraste, o escopo dinâmico força o programador a antecipar todos os possíveis contextos dinâmicos nos quais o código do módulo pode ser invocado.
Por exemplo, considere o seguinte fragmento de programa em Pascal:
program A;
var I:integer;
K:char;
procedure B;
var K:real;
L:integer;
procedure C;
var M:real;
begin
(*escopo A+B+C*)
end;
procedure D;
var K:integer;
begin
(*escopo A+B+D*)
end;
begin
(*escopo A+B*)
end;
begin
(*escopo A*)
end.
A variável I é visível em todos os pontos, porque nunca é encoberta por outra variável de mesmo nome. A variável char K é visível apenas no programa principal, porque ela está encoberta pela variável K real visível no procedimento B, C e D apenas. A variável L também é visível apenas nos procedimentos B, C e D, mas não esconde qualquer outra variável. A variável M é visível apenas no processo C e, portanto, não é acessível quer a partir do procedimento B, do procedimento D, ou do programa principal. Além disso, os procedimentos C e D só são visíveis nos procedimentos B, C e D (C e D são procedimentos com o mesmo pai estático e portanto, enxergam uns aos outros), e, portanto, não podem ser chamados a partir do programa principal.
Além disso, poderia haver ainda outro procedimento C declarado no programa, fora do processo B. O lugar exato no programa em que C é chamado então determina qual procedimento C é chamado, e isto é precisamente análogo ao escopo de variáveis.
Uma das técnicas mais comuns de implementação do escopo estático é a cadeia estática.[5] Esta cadeia mantém ponteiros em cada procedimento apontados para o pai estático. Quando uma referência a uma variável é encontrada em um procedimento, primeiro se procura no registro de ativação deste procedimento. Caso não se encontre a variável, se busca no próximo pai estático e assim por diante. No exemplo da figura ao lado (relativo ao código de exemplo em Pascal acima), após a chamada dos procedimentos A, B, D e C respectivamente, caso haja uma referência no procedimento C sobre a variável K, a variável usada é a que está declarada no procedimento B (K real).
Implementações corretas do escopo estático em linguagens com funções aninhadas de primeira classe (ou seja, funções que podem ser passadas como argumentos) podem ser sutis, pois se exige que cada valor da função leve com ele um registro dos valores das variáveis das quais ele depende (o par da função e deste ambiente é chamado de clausura). Dependendo da implementação e da arquitetura do computador, a pesquisa de variável pode ficar ligeiramente ineficiente quando funções aninhadas léxicamente em muitos níveis são usadas. No entanto, para cada função aninhada que não se refere aos valores da variável dentro de seu encapsulamento, mas apenas aos parâmetros diretos e variáveis imediatamente locais, a posição relativa de cada valor pode ser conhecida em tempo de compilação. Nenhuma sobrecarga é, portanto efetuada com esse tipo de função aninhada. Naturalmente, o mesmo se aplica aos programas específicos, onde funções aninhadas não são utilizados e, claro, para programas escritos em uma linguagem onde as funções aninhadas não estão disponíveis (como a linguagem C).
Escopo dinâmico
[editar | editar código-fonte]Com escopo dinâmico, cada identificador tem uma pilha global de vinculações. Introduzindo uma variável local com o nome de x empilha uma vinculação na pilha global x (que pode estar vazia), que estará desempilhada quando o fluxo de controle deixar o escopo. Avaliar x em qualquer contexto sempre produz a vinculação mais ao topo. Em outras palavras, um identificador global refere-se ao identificador associado com o ambiente mais recente. Note-se que isso não pode ser feito em tempo de compilação, porque a pilha de vinculação só existe em tempo de execução, razão pela qual este tipo de delimitação é chamado de escopo dinâmico.
Geralmente, alguns blocos são definidos para criar vinculações cujo tempo de vida útil é o tempo de execução do bloco; isso adiciona algumas funcionalidades do escopo estático para o processo de escopo dinâmico. No entanto, como uma seção de código pode ser chamada de diferentes locais e situações, pode ser difícil determinar desde o início quais vinculações serão aplicadas quando uma variável for utilizada (ou se a sua existência naquele contexto). Isto pode ser benéfico. a aplicação do princípio do menor conhecimento sugere que o código evita, dependendo de razões para (ou circunstâncias) o valor de uma variável, simplesmente usando o valor de acordo com a sua definição. Esta interpretação restritiva de dados compartilhados pode fornecer um sistema muito flexível para adaptar o comportamento de uma função para o estado atual (ou política) do sistema. No entanto, este benefício depende da documentação cuidadosa de todas as variáveis usadas dessa maneira, bem como na prevenção cuidadosa das suposições sobre o comportamento de uma variável, e não prevê qualquer mecanismo para detectar interferências entre as diferentes partes de um programa. Como tal, o escopo dinâmico pode ser perigoso e poucas linguagens modernas o usam. Algumas linguagens, como o Perl e Common Lisp, permitem que o programador escolha o escopo estático ou dinâmico, quando da definição ou redefinição de uma variável. John McCarthy projetou a linguagem Lisp com escopo dinâmico objetivando compartilhamento de código com variáveis livres.[6] APL,[6] Logo e Emacs Lisp são outros exemplos de linguagens que usam escopo dinâmico.
O escopo dinâmico é bastante fácil de implementar. Para encontrar o valor de um identificador, o programa poderia atravessar a pilha de execução, através da cadeia dinâmica, verificando cada registro de ativação buscando um valor para o identificador. Na prática, isto torna-se mais eficiente através da utilização de uma lista de associação, que é uma pilha de pares nome/valor. Pares são empilhados sempre que as declarações são feitas, e desempilhados no momento em que as variáveis deixam o escopo.[7] Uma estratégia alternativa, que é consideravelmente mais rápida, é fazer uso de uma tabela central de referência, que associa cada nome com seu significado atual. Isso evita uma busca linear durante a execução para encontrar um nome específico, embora a manutenção desta tabela seja mais complexa.[7] Note-se que ambas as estratégias assumem um ordenamento de vinculações em estrutura de pilha (LIFO) para qualquer variável; na prática, todas as vinculações são ordenadas desta forma.
Exemplos
[editar | editar código-fonte]Este exemplo compara as conseqüências do uso do escopo estático e do escopo dinâmico. Observe o seguinte código em linguagem C-like:
int x = 0;
int f() { return x; }
int g() { int x = 1; return f(); }
Com o escopo estático, a chamada de g irá retornar 0, uma vez que foi determinado no momento da compilação que a expressão x em qualquer chamada de f irá produzir uma vinculação global x, que não é afetada pela introdução de uma variável local de mesmo nome em g.
Com o escopo dinâmico, a pilha de vinculação para o identificador x conterá dois itens quando f é chamada de g: a vinculação global a 0, e a vinculação a 1 introduzida em g (que ainda está presente na pilha uma vez que o fluxo de controle não deixou g ainda). Uma vez que a avaliação da expressão do identificador, por definição, sempre produz a vinculação superior, o resultado neste caso é 1.
Na linguagem Perl, as variáveis podem ser definidas tanto com o escopo estático quanto o dinâmico. A palavra-chave "my
" de Perl, define uma variável local de escopo estático, enquanto a palavra "local
" define uma variável local de escopo dinâmico.[8] Isso permite maior esclarecimento com exemplos práticos de cada modelo de escopo.
$x = 0;
sub f { return $x; }
sub g { my $x = 1; return f(); }
print g()."\n";
O exemplo acima usa "my
" para usar escopo estático na variável local g $x. Como acima, a chamada de g retorna 0 porque f não pode enxergar a variável $x de g, logo ela procura pela variável global $x.
$x = 0;
sub f { return $x; }
sub g { local $x = 1; return f(); }
print g()."\n";
Nesta alternativa, "local
" é usada para fazer $x de g ter escopo dinâmico. Agora, a chamada de g resulta em 1 porque f enxerga a variável local de g descendo pela pilha de execução.
Em outras palavras, a variável $x com escopo dinâmico é resolvida no ambiente de execução, em vez de no ambiente de definição.
Escopo fechado versus escopo aberto
[editar | editar código-fonte]Módulos cujos nomes devam ser explicitamente importados são ditos escopos fechados ao passo que escopos que não requerem declarações expícitas de importação são ditos escopos abertos.[9] Módulos são fechados nas linguagens Modula-2, Modula-3 e Euclid e abertos em ADA.[9]
Ver também
[editar | editar código-fonte]Referências
- ↑ a b Sebesta, Robert W. (2006). Concepts of Programming Languages (em inglês) 7ª ed. Boston: Addison Wesley. pp. 228–239. ISBN 0-321-33025-0
- ↑ GUEZZI, Carlo; JAZAYERI, Mehdi (1998). Programming Languages Concepts (em inglês) 3ª ed. New York: John Wiley & Sons. 52 páginas. ISBN 0-471-10426-4
- ↑ PRATT, Terrence W.; ZELKOWITZ, Marvin V (2001). Programming Languages. Design and Implementation (em inglês) 4ª ed. Upper Saddle River, New Jersey: Prentice hall. 364 páginas. ISBN 0-13-027678-2
- ↑ MACLENNAN, Bruce J (1999). Principles of Programming Languages. Design, Evaluation and Implementation (em inglês) 3ª ed. Oxford: Oxford University Press. pp. 109–111. ISBN 0-19-511306-3
- ↑ PRATT, Terrence W.; ZELKOWITZ, Marvin V (2001). Programming Languages. Design and Implementation (em inglês) 4ª ed. Upper Saddle River, New Jersey: Prentice hall. pp. 339–340. ISBN 0-13-027678-2
- ↑ a b APPLEBY, Doris; VANDEKOPPLE, Julius J (1997). Programming Languages. Paradigm and Practice (em inglês) 2ª ed. New York: McGraw-Hill. 43 páginas. ISBN 0-07-005315-4
- ↑ a b SCOTT, Michael L (2000). Programming Language Pragmatics (em inglês). San Francisco: Morgam Kaufmann/Academic Press. pp. 132–137. ISBN 1-55860-442-1
- ↑ Perl FAQ 4.3 Qual é a diferença entre escopo dinâmico e estático (lexical)?
- ↑ a b SCOTT, Michael L (2000). Programming Language Pragmatics (em inglês). San Francisco: Morgam Kaufmann/Academic Press. 125 páginas. ISBN 1-55860-442-1