2.2.1 The Naming Model
É útil ter um modelo de como os nomes são associados a objetos específicos. Um designer de sistema cria um esquema de nomes, que consiste em três elementos. O primeiro elemento é um espaço de nomes, que compreende um alfabeto de símbolos juntamente com regras de sintaxe que especificam quais nomes são aceitáveis. O segundo elemento é um algoritmo de atribuição de nomes, que associa alguns (não necessariamente todos) nomes do espaço de nomes a alguns (mais uma vez, não necessariamente todos) valores num universo de valores, que é o terceiro e último elemento do esquema de atribuição de nomes. Um valor pode ser um objeto, ou pode ser outro nome do espaço de nomes original ou de um espaço de nomes diferente. Um mapeamento de nome para valor é um exemplo de um binding, e quando tal mapeamento existe, diz-se que o nome está vinculado ao valor. A Figura 2.10 ilustra.
Na maioria dos sistemas, tipicamente vários esquemas de nomenclatura distintos estão em funcionamento simultaneamente. Por exemplo, um sistema pode estar usando um esquema de nomenclatura para nomes de caixas de correio eletrônico, um segundo esquema de nomenclatura para hosts de Internet, um terceiro para arquivos e um quarto para endereços de memória virtual. Quando um intérprete de programa encontra um nome, ele deve saber qual esquema de nomenclatura a invocar. O ambiente em torno do uso do nome geralmente fornece informações suficientes para identificar o esquema de nomes. Por exemplo, em um programa de aplicação, o autor desse programa sabe que o programa deve esperar que os nomes dos arquivos sejam interpretados apenas pelo sistema de arquivos e os nomes dos hosts da Internet sejam interpretados apenas por algum serviço de rede.
O intérprete que encontra o nome executa o algoritmo de atribuição de nomes do esquema de atribuição de nomes apropriado. O algoritmo de name-mapping resolve o nome, o que significa que ele descobre e retorna o valor associado (por este motivo, o algoritmo de name-mapping também é chamado de resolvedor). O algoritmo de name-mapping é normalmente controlado por um parâmetro adicional, conhecido como contexto. Para um determinado esquema de nomenclatura, pode haver muitos contextos diferentes, e um único nome do espaço de nomes pode mapear para valores diferentes quando o resolvedor usa contextos diferentes. Por exemplo, no discurso comum quando uma pessoa se refere aos nomes “você”, “aqui”, ou “Alice”, o significado de cada um desses nomes depende do contexto em que a pessoa o pronuncia. Por outro lado, alguns esquemas de nomenclatura têm apenas um contexto. Tais esquemas de nomenclatura fornecem o que são chamados de espaços universais de nomes, e eles têm a propriedade agradável de que um nome tem sempre o mesmo significado dentro desse esquema de nomenclatura, não importa quem o usa. Por exemplo, nos Estados Unidos, os números da segurança social, que identificam as contas de pensões e impostos do governo, constituem um espaço universal de nomes. Quando há mais de um contexto, o intérprete pode dizer ao resolvedor qual ele deve usar ou o resolvedor pode usar um contexto padrão.
Podemos resumir o modelo de nomes definindo a seguinte operação conceptual sobre nomes:
valor ← resolve (nome, contexto)
Quando um intérprete encontra um nome num objecto, primeiro descobre que esquema de nomes está envolvido e, portanto, que versão de resolução deve invocar. Em seguida, identifica um contexto apropriado, resolve o nome nesse contexto, e substitui o nome pelo valor resolvido à medida que continua a interpretação. A variável contexto diz resolver qual o contexto a ser usado. Essa variável contém um nome conhecido como referência de contexto.
Em um processador, números de registro são nomes. Em um processador simples, o conjunto de nomes de registros, e os registros aos quais esses nomes estão vinculados, são ambos fixos no momento do projeto. Na maioria dos outros sistemas que usam nomes (incluindo o esquema de nomes de registros de alguns processadores de alto desempenho), é possível criar novos bindings e excluir os antigos, enumerar o espaço de nomes para obter uma lista dos bindings existentes e comparar dois nomes. Para estes fins, definimos mais quatro operações conceituais:
status ← bind (name, value, context)
status ← unbind (name, context)
list ← enumerate (context)
result ← compare (name1, name2)
A primeira operação muda de contexto adicionando uma nova encadernação; o resultado do status informa se a alteração foi ou não bem sucedida (pode falhar se o nome proposto violar as regras de sintaxe do espaço do nome). Após uma chamada para bind bem sucedida, resolve retornará o novo valor do nome.*A segunda operação, unbind, remove um binding existente do contexto, com status novamente reportando sucesso ou falha (talvez porque não havia tal binding existente). Após uma chamada bem-sucedida para unbind, o resolved não retornará mais esse valor para o nome. As operações de unbind e unbind permitem o uso de nomes para fazer conexões entre objetos e modificar essas conexões mais tarde. Um designer de um objeto pode, usando um nome para se referir a um objeto componente, escolher o objeto ao qual esse nome está vinculado nessa altura ou posteriormente, invocando bind, e eliminar um binding que não é mais apropriado invocando unbind, tudo sem modificar o objeto que usa o nome. Esta capacidade de atrasar e alterar os bindings é uma ferramenta poderosa utilizada no design de quase todos os sistemas. Algumas implementações de nomes fornecem uma operação enumerativa, que retorna uma lista de todos os nomes que podem ser resolvidos no contexto. Algumas implementações de enumerar também podem retornar uma lista de todos os valores atualmente vinculados em contexto. Finalmente, os relatórios das operações de comparação (verdadeiro ou falso), quer o nome1 seja ou não o mesmo que o nome2. O significado de “mesmo” é uma questão interessante abordada na Seção 2.2.5, e pode requerer o fornecimento de argumentos de contexto adicionais.
Diferentes esquemas de nomenclatura têm regras diferentes sobre a singularidade de mapeamentos de nome para valor. Alguns esquemas de nomenclatura têm uma regra que um nome deve mapear exatamente para um valor em um determinado contexto e um valor deve ter apenas um nome, enquanto em outros esquemas de nomenclatura um nome pode mapear para vários valores, ou um valor pode ter vários nomes, mesmo no mesmo contexto. Outro tipo de regra de unicidade é a de um espaço de nomes identificadores únicos, que fornece um conjunto de nomes que nunca serão reutilizados durante a vida útil do espaço de nomes e, uma vez vinculados, permanecerão sempre vinculados ao mesmo valor. Diz-se que tal nome tem uma ligação estável. Se um espaço de nome de identificador único também tem a regra de que um valor pode ter apenas um nome, os nomes únicos tornam-se úteis para manter o controle dos objetos durante um longo período de tempo, para comparar referências para ver se eles estão com o mesmo objeto, e para coordenar várias cópias em sistemas onde os objetos são replicados para desempenho ou confiabilidade. Por exemplo, o número da conta do cliente da maioria dos sistemas de faturamento constitui um espaço de nome identificador único. O número da conta sempre se referirá à conta do mesmo cliente enquanto essa conta existir, apesar das mudanças no endereço, número de telefone ou até mesmo no nome pessoal do cliente. Se a conta de um cliente for excluída, o número da conta desse cliente não será reutilizado algum dia para a conta de um cliente diferente. Campos nomeados dentro da conta, como o saldo devido, podem mudar de tempos em tempos, mas a ligação entre o número da conta do cliente e a conta em si é estável.
O algoritmo de mapeamento de nomes mais um único contexto não mapeia necessariamente todos os nomes do espaço de nomes para valores. Assim, um possível resultado da execução da resolução pode ser um resultado não encontrado, que a resolução pode comunicar ao chamador como um valor reservado ou como uma exceção. Por outro lado, se o esquema de nomes permite que um nome mapeie para vários valores, um possível resultado pode ser uma lista de valores. Nesse caso, a operação de unbind pode exigir um argumento adicional que especifique qual o valor a ser unbind. Finalmente, alguns esquemas de nomenclatura fornecem uma pesquisa inversa, o que significa que um chamador pode fornecer um valor como argumento para o algoritmo de name-mapping, e descobrir que nome ou nomes estão ligados a esse valor.
Figure 2.10 ilustra o modelo de nomenclatura, mostrando um espaço de nome, o universo correspondente de valores, um algoritmo de name-mapping, e um contexto que controla o algoritmo de name-mapping.
Na prática, um encontra três algoritmos de name-mapping frequentemente utilizados:
Mesa de procura
■
Procura recursiva
■
Procura múltipla
A implementação mais comum de um contexto é uma tabela de pares {nome, valor}. Quando a implementação de um contexto é uma tabela, o algoritmo de mapeamento de nomes é apenas uma pesquisa do nome nessa tabela. A tabela em si pode ser complexa, envolvendo hashing ou árvores B, mas a idéia básica ainda é a mesma. Ligar um novo nome a um valor consiste em adicionar aquele par {nome, valor} à tabela. A Figura 2.11 ilustra esta implementação comum do modelo de nomenclatura. Existe uma dessas tabelas para cada contexto, e contextos diferentes podem conter ligações diferentes para o mesmo nome.
Exemplos reais tanto do modelo de nomenclatura geral quanto da implementação de tabelas-mapping abundam:
Uma lista telefônica é um contexto de tabelas-mapping que vincula nomes de pessoas e organizações a números telefônicos. Como no exemplo da rede de comunicação de dados, números de telefone são nomes que a companhia telefônica resolve em aparências de linhas físicas, usando um algoritmo de mapeamento de nomes que envolve códigos de área, trocas e comutadores físicos. As listas telefônicas para Boston e para São Francisco são dois contextos do mesmo esquema de nomes; qualquer nome em particular pode aparecer em ambas as listas telefônicas, mas se assim for, provavelmente está vinculado a números de telefone diferentes.
Um pequeno número inteiro nomeia os registros de um processador. O valor é o próprio registro, e o mapeamento de nome para valor é feito por cabeamento.
Células de memória são nomeadas de forma similar com os números chamados endereços, e o mapeamento de nome para valor é novamente feito por cabeamento. O capítulo 5 descreve um mecanismo de renomeamento de endereços conhecido como memória virtual, que liga blocos de endereços virtuais a blocos de células de memória contíguas. Quando um sistema implementa múltiplas memórias virtuais, cada memória virtual é um contexto distinto; um dado endereço pode se referir a uma célula de memória diferente em cada memória virtual. Células de memória também podem ser compartilhadas entre memórias virtuais, neste caso a mesma célula de memória pode ter os mesmos endereços (ou diferentes) em memórias virtuais diferentes, como determinado pelos bindings.
Um sistema típico de arquivo de computador usa várias camadas de nomes e contextos: setores de disco, partições de disco, arquivos e diretórios são todos objetos nomeados. Os diretórios são exemplos de contextos de aparência de tabela. Um nome de arquivo em particular pode aparecer em vários diretórios diferentes, vinculados ao mesmo ou a arquivos diferentes. A seção 2.5 apresenta um estudo de caso de nomenclatura no sistema de arquivos unix.
Os computadores conectam-se a redes de comunicação de dados em locais conhecidos como pontos de conexão de rede. Os pontos de conexão de rede são normalmente nomeados com dois esquemas de nomeação distintos. O primeiro, usado dentro da rede, envolve um espaço de nomes que consiste em números em um campo de comprimento fixo. Esses nomes são vinculados, às vezes permanentemente e às vezes apenas brevemente, a pontos físicos de entrada e saída da rede. Um segundo esquema de nomes, usado pelos clientes da rede, mapeia um espaço de nomes universal mais fácil de usar de cadeias de caracteres para nomes do primeiro espaço de nomes. A seção 4.4 é um estudo de caso do Sistema de Nomes de Domínio, que fornece nomes de pontos de conexão de fácil utilização para a Internet.
Um programador identifica variáveis de procedimento por nomes, e cada ativação do procedimento fornece um contexto distinto no qual a maioria desses nomes são resolvidos. Alguns nomes, identificados como “static” ou “global names”, podem ser resolvidos em um contexto que é compartilhado entre ativações ou entre diferentes procedimentos. Quando um procedimento é compilado, alguns dos nomes originais de variáveis de fácil utilização podem ser substituídos por identificadores inteiros que são mais convenientes para uma máquina manipular, mas o modelo de nomenclatura ainda se mantém.
Um Uniform Resource Locator (URL) da World Wide Web é mapeado para uma página Web específica por um algoritmo relativamente complicado que divide a URL em várias partes constituintes e resolve as partes usando diferentes esquemas de nomenclatura; o resultado eventualmente identifica uma página Web em particular. A seção 3.2 é um estudo de caso deste esquema de nomenclatura.
Um sistema de cobrança de clientes normalmente mantém pelo menos dois tipos de nomes para cada conta de cliente. O número da conta nomeia a conta em um espaço de nomes identificadores único, mas também há um espaço de nomes distintos de nomes pessoais que também podem ser usados para identificar a conta. Ambos os nomes são tipicamente mapeados para registros de contas por um sistema de banco de dados, de modo que as contas podem ser recuperadas por número de conta ou por nome pessoal.
Estes exemplos também destacam uma distinção entre “naming” e binding. Alguns, mas não todos, contextos “name” coisas, no sentido de que eles mapeiam um nome para um objeto que é comumente pensado como tendo esse nome. Assim, a lista telefónica não “nomeia” nem pessoas nem linhas telefónicas. Em algum outro lugar há contextos que ligam nomes a pessoas e que ligam números de telefone a telefones físicos particulares. A lista telefónica liga os nomes das pessoas aos nomes dos telefones.