Escolar Documentos
Profissional Documentos
Cultura Documentos
Brian W Kernighan
2 de abril de 1981
Machine Translated by Google
Brian W Kernighan
Laboratórios AT&T Bell
Murray Hill, Nova Jersey 07974
ABSTRATO
2 de abril de 1981
Machine Translated by Google
Brian W Kernighan
Laboratórios AT&T Bell
Murray Hill, Nova Jersey 07974
1. Gênesis
Este artigo tem suas origens em dois eventos – uma enxurrada de artigos que comparam C e Pas cal1, 2, 3, 4 e
uma tentativa pessoal de reescrever Software Tools5 em Pascal.
Comparar C e Pascal é como comparar um Learjet com um Piper Cub - um é feito para fazer algo enquanto o
outro é para aprender - então tais comparações tendem a ser um tanto exageradas. Mas a revisão do Software Tools
parece uma comparação mais relevante.
Os programas nele contidos foram originalmente escritos em Ratfor, um dialeto “estruturado” de Fortran implementado
por um pré-processador. Como o Ratfor é realmente Fortran disfarçado, ele tem poucos dos recursos que o Pascal traz -
tipos de dados mais adequados ao processamento de caracteres, recursos de estruturação de dados para definir melhor
a organização dos dados e digitação forte para impor a verdade sobre os dados.
Acabou sendo mais difícil do que eu esperava reescrever os programas em Pascal. Este artigo é uma tentativa de
extrair da experiência algumas lições sobre a adequação do Pascal para programação (diferente do aprendizado sobre
programação). Não é uma comparação de Pascal com C ou Ratfor.
Os programas foram escritos pela primeira vez nesse dialeto de Pascal apoiado pelo interpretador de Pascal pi
fornecido pela Universidade da Califórnia em Berkeley. A linguagem está próxima do padrão nominal de Jensen e Wirth,6
com bons diagnósticos e verificação cuidadosa em tempo de execução. Desde então, os programas também foram
executados, inalterados, exceto por novas bibliotecas de primitivas, em quatro outros sistemas: um intérprete da
Universidade Livre de Amsterdã (doravante denominado VU, para Vrije Universiteit), uma versão VAX do sistema Berkeley
(um verdadeiro compilador), um compilador fornecido pela Whitesmiths, Ltd., e UCSD Pascal em um Z80. Todos, exceto
o último, desses sistemas Pascal são escritos em C.
Pascal é uma linguagem muito discutida. Uma bibliografia recente7 lista 175 itens sob o título de “discussão,
análise e debate”. Os artigos mais citados (que vale a pena ler) são uma forte crítica de Habermann8 e uma réplica
igualmente forte de Lecarme e Des jardins . por Boom e DeJong10 também é uma boa leitura. A avaliação do próprio
Wirth de Pas cal é encontrada em [11]. Não tenho desejo ou capacidade de resumir a literatura; este artigo representa
minhas observações pessoais e a maior parte necessariamente duplica pontos feitos por outros. Tentei organizar o
restante do material em torno das questões de
tipos e cosméticos
de ambiente de
fluxo de controle de
escopo
-2-
2. Tipos e Escopos
Pascal é (quase) uma linguagem fortemente tipada. Grosso modo, isso significa que cada objeto em um
programa tem um tipo bem definido que define implicitamente os valores legais e as operações no objeto. A
linguagem garante que proibirá valores e operações ilegais, por meio de uma mistura de verificação em tempo de
compilação e execução. É claro que os compiladores podem não fazer todas as verificações implícitas na definição
da linguagem. Além disso, tipagem forte não deve ser confundida com análise dimensional. Se definirmos os tipos
maçã e laranja com
modelo
maçã = inteiro; laranja =
inteiro;
então qualquer expressão aritmética arbitrária envolvendo maçãs e laranjas é perfeitamente legal.
A digitação forte aparece de várias maneiras. Por exemplo, argumentos para funções e procedimentos são
verificados quanto à correspondência de tipo adequada. Foi-se a liberdade do Fortran de passar um número de ponto
flutuante para uma sub-rotina que espera um número inteiro; isso eu considero um atributo desejável de Pascal, pois
alerta para uma construção que certamente causará um erro.
Variáveis inteiras podem ser declaradas como tendo um intervalo associado de valores legais, e o compilador
e o suporte em tempo de execução garantem que não se coloquem inteiros grandes em variáveis que contenham
apenas pequenos. Isso também parece um serviço, embora, é claro, a verificação em tempo de execução exija uma
penalidade.
Passemos a alguns problemas de tipo e escopo.
então arr10 e arr20 são matrizes de 10 e 20 inteiros, respectivamente. Suponha que queremos escrever um
procedimento sort para ordenar um array inteiro. Como arr10 e arr20 têm tipos diferentes, não é possível escrever
um único procedimento que classifique ambos.
O ponto em que isso afeta particularmente as Ferramentas de Software , e acho que os programas em geral,
é que dificulta a criação de uma biblioteca de rotinas para realizar operações comuns de propósito geral, como
classificação.
O tipo de dado específico mais frequentemente afetado é o array de char, pois em Pascal uma string é um
array de caracteres. Considere escrever uma função index(s,c) que retornará a posição na string s onde o caractere
c ocorre pela primeira vez, ou zero se não ocorrer. O problema é como lidar com o argumento string de index. As
chamadas index('hello',c) e index('goodbye',c) não podem ser ambas válidas, pois as strings têm comprimentos
diferentes. (Eu passo sobre a questão de como o final de uma string constante como 'hello' pode ser detectado,
porque não pode.)
A próxima tentativa é
temp := 'olá';
n := index(temp,c);
mas a atribuição de temp é ilegal porque 'hello' e temp têm comprimentos diferentes.
A única saída dessa regressão infinita é definir uma família de rotinas com um membro para cada tamanho de
string possível, ou fazer com que todas as strings (incluindo strings constantes como 'define') tenham o mesmo
comprimento.
Machine Translated by Google
-3-
A última abordagem é o menor de dois grandes males. Em Ferramentas, um tipo chamado string é declarado como
onde a constante MAXSTR é "grande o suficiente", e todas as strings em todos os programas são exatamente desse tamanho.
Isso está longe de ser o ideal, embora tenha possibilitado a execução dos programas. Não resolve o problema de criar
verdadeiras bibliotecas de rotinas úteis.
Existem algumas situações em que simplesmente não é aceitável usar a representação de matriz de tamanho fixo.
Por exemplo, o programa Ferramentas para classificar linhas de texto opera preenchendo a memória com quantas linhas
couberem; seu tempo de execução depende fortemente de quão cheia a memória pode ser compactada. Assim, para
classificar, outra representação é usada, um longo array de caracteres e um conjunto de índices neste array:
Mas os procedimentos e funções escritos para processar a representação de tamanho fixo não podem ser usados com a forma
de tamanho variável; um conjunto inteiramente novo de rotinas é necessário para copiar e comparar strings nesta representação.
Em Fortran ou C, as mesmas funções podem ser usadas para ambos.
e tem o array compactado do tipo [1..n] de char, onde n é o comprimento. Assim, cada literal de string de comprimento diferente
tem um tipo diferente. A única maneira de escrever uma rotina que imprimirá uma mensagem e a limpará é preencher todas
as mensagens com o mesmo tamanho máximo:
Muitos compiladores Pascal comerciais fornecem um tipo de dados string que evita explicitamente o problema; strings
são consideradas do mesmo tipo, independentemente do tamanho. Isso resolve o problema para este único tipo de dados,
mas nenhum outro. Também não resolve problemas secundários como calcular o comprimento de uma string constante; outra
função interna é a solução usual.
Entusiastas do Pascal muitas vezes afirmam que para lidar com o problema do tamanho do array, basta copiar alguma
rotina de biblioteca e preencher os parâmetros para o programa em mãos, mas a defesa parece fraca na melhor das
hipóteses:12
''Como os limites de um array fazem parte de seu tipo (ou, mais exatamente, do tipo de seus índices), é
impossível definir um procedimento ou função que se aplique a arrays com limites diferentes. Embora essa
restrição possa parecer severa, as experiências que tivemos com Pascal tendem a mostrar que ela tende a
ocorrer muito raramente. [...] No entanto, a necessidade de vincular o tamanho das matrizes paramétricas é um
defeito grave em relação ao uso de bibliotecas de programas.''
Esta falha crítica é o maior problema com o Pascal. Acredito que se pudesse ser consertado, a linguagem seria uma
ordem de grandeza mais utilizável. O padrão ISO proposto para Pascal13 fornece tal correção (''conformant array schemas''),
mas a aceitação desta parte do padrão aparentemente ainda está em dúvida.
Uma variável estática (muitas vezes chamada de variável própria em países de língua Algol) é aquela que é privada
de alguma rotina e retém seu valor de uma chamada da rotina para a próxima. De fato, para variáveis tran são estáticas
internas, exceto COMMON;† em C há uma declaração estática que pode ser aplicada a variáveis locais.
__________________
† A rigor, no Fortran 77 deve-se usar SAVE para forçar o atributo estático.
Machine Translated by Google
-4-
Pascal não tem essa classe de armazenamento. Isso significa que se uma função ou procedimento Pascal
pretende lembrar um valor de uma chamada para outra, a variável usada deve ser externa à função ou procedimento.
Portanto, deve ser visível para outros procedimentos e seu nome deve ser exclusivo no escopo maior. Um exemplo
simples do problema é um gerador de números aleatórios: o valor usado para calcular a saída atual deve ser salvo
para calcular a próxima, portanto, deve ser armazenado em uma variável cujo tempo de vida inclua todas as chamadas
do gerador de números aleatórios. Na prática, este é tipicamente o bloco mais externo do programa. Assim, a
declaração de tal variável está muito distante do local onde ela é realmente usada.
Um exemplo vem do formatador de texto descrito no Capítulo 7 de Ferramentas. A variável dir controla a
direção a partir da qual os espaços em branco em excesso são inseridos durante a justificação da linha, para obter a
esquerda e a direita alternadamente. Em Pascal, o código fica assim:
foi
...
...
fim;
A declaração, inicialização e uso da variável dir estão espalhados por todo o programa, literalmente centenas de
linhas de distância. Em C ou Fortran, dir pode ser privado para a única rotina que precisa saber sobre isso:
...
a Principal()
{
...
}
...
justificar() {
dir = 1 - dir;
...
}
É claro que existem muitos outros exemplos do mesmo problema em escala maior; funções
para E/S em buffer, gerenciamento de armazenamento e tabelas de símbolos vêm à mente.
Há pelo menos dois problemas relacionados. Pascal não fornece nenhuma maneira de inicializar variáveis
estaticamente (ou seja, em tempo de compilação); não há nada análogo à instrução DATA do Fortran ou inicializadores
como
Machine Translated by Google
-5-
int dir = 0;
em C. Isso significa que um programa Pascal deve conter instruções de atribuição explícitas para inicializar
variáveis (como o
você := 0;
acima de). Esse código torna o texto-fonte do programa maior e o próprio programa maior em tempo de execução.
Além disso, a falta de inicializadores agrava o problema de escopo muito grande causado pela falta de uma
classe de armazenamento estático. O tempo para inicializar as coisas é no início, então ou a rotina principal
começa com muito código de inicialização, ou chama uma ou mais rotinas para fazer as inicializações. Em ambos
os casos, as variáveis a serem inicializadas devem estar visíveis, o que significa estar em vigor no nível mais alto
da hierarquia. O resultado é que qualquer variável a ser inicializada tem escopo global.
A terceira dificuldade é que não há como duas rotinas compartilharem uma variável a menos que ela seja
declarada em ou acima de seu ancestral menos comum. A classe de armazenamento estático externo do Fortran
COMMON e do C fornecem uma maneira de duas rotinas cooperarem de forma privada, sem compartilhar
informações com seus ancestrais.
O novo padrão não oferece variáveis estáticas, inicialização ou comunicação não hierárquica.
Até certo ponto, isso pode ser mitigado por um mecanismo como o recurso #include de C e Ratfor: arquivos
de origem podem ser incluídos onde necessário sem sobrecarregar o programa. #include não faz parte do Pascal
padrão, embora os compiladores UCB, VU e Whitesmiths o forneçam.
Existe também uma declaração de encaminhamento em Pascal que permite separar a declaração da
função ou cabeçalho do procedimento do corpo; destina-se a definir procedimentos mutuamente recursivos.
Quando o corpo é declarado posteriormente, o cabeçalho dessa declaração pode conter apenas o nome da
função e não deve repetir as informações da primeira instância.
Um problema relacionado é que Pascal tem uma ordem estrita na qual está disposto a aceitar declarações
ções. Cada procedimento ou função consiste em
Isso significa que todas as declarações de um tipo (tipos, por exemplo) devem ser agrupadas para conveniência
do compilador, mesmo quando o programador gostaria de manter juntas coisas logicamente relacionadas para
entender melhor o programa. Como um programa deve ser apresentado ao compilador de uma só vez, raramente
é possível manter a declaração, a inicialização e o uso de tipos e variáveis juntos. Mesmo alguns dos mais
dedicados defensores de Pascal concordam:14 ''A incapacidade de fazer tais agrupamentos na estruturação de
grandes programas é uma das limitações mais frustrantes de Pascal.''
Machine Translated by Google
-6-
Pascal ''oficial'' não fornece compilação separada e, portanto, cada implementação decide por conta própria o que fazer.
Alguns (o intérprete de Berkeley, por exemplo) o desaprovam inteiramente; isso está mais próximo do espírito da linguagem e
corresponde exatamente à letra. Muitos outros fornecem uma declaração que especifica que o corpo de uma função é definido
externamente. De qualquer forma, todos esses mecanismos não são padronizados e, portanto, são feitos de maneira diferente
por sistemas diferentes.
Teoricamente, não há necessidade de compilação separada - se o compilador for muito rápido (e se a fonte para todas as
rotinas estiver sempre disponível e se o compilador tiver um recurso de inclusão de arquivos para que várias cópias da fonte não
sejam necessárias), recompilar tudo é equivalente. Na prática, é claro, os compiladores nunca são rápidos o suficiente e a fonte
geralmente fica oculta e a inclusão de arquivos não faz parte da linguagem, portanto, as alterações são demoradas.
Alguns sistemas permitem a compilação separada, mas não validam a consistência dos tipos ao longo do limite. Isso cria
um buraco gigante na tipagem forte. (A maioria das outras linguagens também não faz verificação cruzada de compilação, então
Pascal não é inferior a esse respeito.) Eu vi pelo menos um artigo (infelizmente não publicado) que na página n castiga C por
falhar em verificar tipos em limites separados de compilação enquanto sugere na página n+1 que a maneira de lidar com o Pascal
é compilar os procedimentos separadamente para evitar a verificação de tipos.
pontos a seguir são pequenas irritações, mas eu tenho que colocá-los em algum lugar.
Não é legal nomear um tipo não básico como o parâmetro formal literal de um procedimento; o seguinte não é permitido:
Em vez disso, deve-se inventar um nome de tipo, fazer uma declaração de tipo e declarar o parâmetro formal como uma instância
desse tipo:
Naturalmente, a declaração de tipo é fisicamente separada do procedimento que a utiliza. A disciplina de inventar nomes de
tipos é útil para tipos que são usados com frequência, mas é uma distração para coisas usadas apenas uma vez.
É bom ter a declaração var para parâmetros formais de funções e procedimentos; o procedimento indica claramente que
pretende modificar o argumento. Mas o programa chamador não tem como declarar que uma variável deve ser modificada — a
informação está apenas em um lugar, enquanto dois lugares seriam melhores. (Meio pão é melhor do que nenhum – Fortran não
diz nada ao usuário sobre quem fará o que com as variáveis.)
Também é um pequeno incômodo que os arrays sejam passados por valor por padrão - o efeito líquido é que cada
parâmetro do array é declarado var pelo programador mais ou menos sem pensar. Se a declaração var for omitida
inadvertidamente, o bug resultante será sutil.
A construção de conjunto de Pascal parece uma boa ideia, fornecendo conveniência de notação e algumas
verificação de tipo livre. Por exemplo, um conjunto de testes como
pode ser escrito de forma mais clara e talvez mais eficiente como
-7-
Mas, na prática, os tipos de conjunto não são úteis para muito mais do que isso, porque o tamanho de um conjunto é
fortemente dependente da implementação (provavelmente porque era assim na implementação original do CDC: 59 bits).
Por exemplo, é natural tentar escrever a função isalphanum(c) (''c é alfanumérico?'') como
Mas em muitas implementações do Pascal (incluindo o original) este código falha porque os conjuntos são muito pequenos.
Consequentemente, os conjuntos são geralmente deixados sem uso se alguém pretende escrever programas portáteis.
(Esta rotina específica também executa uma ordem de magnitude mais lenta com conjuntos do que com um teste de
intervalo ou referência de matriz.)
3. Fluxo de Controle
As deficiências de controle de fluxo de Pascal são pequenas, mas numerosas - a morte de mil
cortes, em vez de um único golpe em um ponto vital.
Não há ordem garantida de avaliação dos operadores lógicos e e ou — nada como && e || em C. Essa falha, que é
compartilhada com a maioria das outras linguagens, prejudica mais frequentemente o controle de loop:
é extremamente imprudente o uso de Pascal, pois não há como garantir que i seja testado antes que x[i] seja.
A propósito, os parênteses neste código são obrigatórios — a linguagem tem apenas quatro níveis de precedência
de operador, com relacionais na parte inferior.
Não há instrução break para sair de loops. Isso é consistente com a filosofia de uma entrada e uma saída defendida
pelos proponentes da programação estruturada, mas leva a circunlocuções desagradáveis ou código duplicado,
particularmente quando combinado com a incapacidade de controlar a ordem na qual as expressões lógicas são avaliadas.
Considere esta situação comum, expressa em C ou Ratfor:
resto do laço
}
feito := falso;
while (não feito) e (getnext(...)) fazem
se algo então feito := true
mais comece
resto do laço
fim
Machine Translated by Google
-8-
Mas isso não funciona, porque não há como forçar o ''not done'' a ser avaliado antes da próxima chamada de
getnext. Isso leva, após vários falsos começos, a
feito := falso;
enquanto não estiver pronto comece
feito := getnext(...); se algo então
feito := true
É claro que os reincidentes podem usar um goto e um rótulo (somente numérico e deve ser declarado) para sair
de um loop. Caso contrário, as saídas antecipadas são uma dor, quase sempre exigindo a invenção de uma
variável booleana e uma certa dose de astúcia. Compare encontrar o último não em branco em uma matriz no Ratfor:
para (i = max; i > 0; i = i - 1) se (arr(i) != ' ') quebrar
com Pascal:
feito := falso;
e := max;
while (i > 0) e (não concluído) faça if arr[i] = ' then
'
i := i - 1
senão
feito := verdadeiro;
O índice de um loop for é indefinido fora do loop, portanto não é possível descobrir se foi até o final ou não.
O incremento de um loop for só pode ser +1 ou -1, uma restrição menor.
Não há declaração de retorno, novamente por motivos de entrada e saída. Um valor de função é retornado
definindo o valor de uma pseudo-variável (como em Fortran) e, em seguida, caindo no final da função. Isso às
vezes leva a contorções para garantir que todos os caminhos cheguem ao final da função com o valor adequado.
Também não há uma maneira padrão de encerrar a execução, exceto atingindo o final do bloco mais externo,
embora muitas implementações forneçam uma interrupção que causa o término imediato.
A instrução case é melhor projetada do que em C, exceto que não há cláusula padrão e o comportamento
é indefinido se a expressão de entrada não corresponder a nenhum dos casos. Essa omissão crucial torna a
construção do caso quase inútil. Em mais de 6000 linhas de Pascal em Ferramentas de Software em Pascal, usei-
o apenas quatro vezes, embora se houvesse um padrão, um caso teria servido em pelo menos uma dúzia de
lugares.
O novo padrão não oferece alívio em nenhum desses pontos.
4. O Meio Ambiente
-9-
...
a leitura antecipada faz com que o programa trave, aguardando entrada antes de imprimir o prompt que a solicita.
É possível escapar da maioria dos efeitos maléficos deste projeto de E/S por implementações muito cuidadosas
ção, mas nem todos os sistemas Pascal o fazem e, em qualquer caso, é relativamente caro.
O design de E/S reflete o sistema operacional original no qual o Pascal foi projetado; até Wirth reconhece esse
viés, embora não seus defeitos.15 Supõe-se que os arquivos de texto consistem em registros, ou seja, linhas de texto.
Quando o último caractere de uma linha é lido, a função interna eoln se torna verdadeira; nesse ponto, deve-se chamar
readln para iniciar a leitura de uma nova linha e redefinir eoln.
Da mesma forma, quando o último caractere do arquivo é lido, o eof embutido se torna verdadeiro. Em ambos os casos,
eoln e eof devem ser testados antes de cada leitura e não depois.
Diante disso, esforços consideráveis devem ser tomados para simular uma entrada sensata. Esta implementação
de getc funciona para sistemas de E/S Berkeley e VU, mas pode não funcionar necessariamente para qualquer outra
coisa:
ch : caractere;
começar
se e de então
c := ENDFILE
else if eoln then begin readln;
c := NOVA LINHA
fim
senão comece
read(ch);
c := ord(ch)
fim;
getc := c
fim;
O tipo caractere não é o mesmo que char, pois ENDFILE e talvez NEWLINE não são valores válidos para uma variável
char.
Não há nenhuma noção de acesso a um sistema de arquivos, exceto para arquivos predefinidos nomeados por
(na verdade) número de unidade lógica na instrução do programa que inicia cada programa. Isso aparentemente reflete o
sistema de lote do CDC no qual o Pascal foi originalmente desenvolvido. Uma variável de arquivo
foi fv: arquivo do tipo
é um tipo muito especial de objeto — ele não pode ser atribuído, nem usado, exceto por chamadas para procedimentos
internos como eof, eoln, read, write, reset e rewrite. (reset rebobina um arquivo e o deixa pronto para releitura; reescrever
deixa um arquivo pronto para escrita.)
A maioria das implementações do Pascal fornece uma escotilha de escape para permitir o acesso a arquivos por
nome do ambiente externo, mas não de maneira conveniente e não padronizada. Por exemplo, muitos sistemas permitem
que um argumento de nome de arquivo em chamadas seja redefinido e reescrito:
Mas redefinir e reescrever são procedimentos, não funções — não há retorno de status e nenhuma maneira de recuperar
o controle se, por algum motivo, a tentativa de acesso falhar. (UCSD fornece um sinalizador em tempo de compilação
que desativa a interrupção normal.) E como os fvs não podem aparecer em expressões como
Machine Translated by Google
- 10 -
também não há escapatória nessa direção. Essa camisa de força torna essencialmente impossível escrever programas que
se recuperem de nomes de arquivos com erros ortográficos, etc. Eu nunca resolvi isso adequadamente na revisão de
Ferramentas .
Não há noção de acesso a argumentos de linha de comando, provavelmente refletindo as origens do processamento
em lote do Pascal. As rotinas locais podem permitir isso adicionando procedimentos não padronizados ao ambiente.
Como não é possível escrever um alocador de armazenamento de uso geral em Pascal (não há como falar sobre os
tipos que tal função retornaria), a linguagem possui um procedimento embutido chamado new que aloca espaço de um heap .
Apenas tipos definidos podem ser alocados, portanto, não é possível alocar, por exemplo, arrays de tamanho arbitrário para
armazenar cadeias de caracteres. Os ponteiros retornados por new podem ser passados, mas não manipulados: não há
aritmética de ponteiros.
Não há como recuperar o controle se o armazenamento acabar.
5. Questões Cosméticas
A maioria desses problemas é irritante para um programador experiente, e alguns são provavelmente
incômodo mesmo para iniciantes. Todos podem ser vividos.
Pascal, em comum com a maioria das outras linguagens inspiradas em Algol, usa o ponto e vírgula como um
separador de instrução em vez de um terminador (como é em PL/I e C). Como resultado, deve-se ter uma noção
razoavelmente sofisticada do que é uma declaração para colocar ponto e vírgula corretamente. Talvez mais importante, se
alguém leva a sério usá-los nos lugares apropriados, é necessária uma quantidade razoável de edição incômoda. Considere
o primeiro corte em um programa:
se um então
b;
c;
Mas se algo deve ser inserido antes de b, não precisa mais de ponto e vírgula, porque agora precede um fim:
se então começar
b0;
b
fim;
c;
se então começar
b0;
b
fim
senão
d;
c;
E assim por diante, com ponto e vírgula ondulando para cima e para baixo no programa à medida que ele evolui.
Um resultado experimental geralmente aceito na psicologia do programador é que o ponto e vírgula como separador é
cerca de dez vezes mais propenso a erros do que o ponto e vírgula como terminador . quase sempre se pode fechar os olhos
e se safar com um ponto e vírgula como terminador. As exceções estão em lugares como declarações, onde o problema do
separador vs. terminador não parece tão sério de qualquer maneira, e antes de mais nada, o que é fácil de lembrar.
- 11 -
Um nome de função por si só é uma chamada dessa função; não há como distinguir tal chamada de função de uma variável
simples, exceto conhecendo os nomes das funções. Pascal usa o truque Fortran de fazer o nome da função agir como uma
variável dentro da função, exceto que onde em Fortran o nome da função é realmente uma variável, e pode aparecer em
expressões, em Pascal, sua aparência em uma expressão é uma invocação recursiva: se f é uma função de argumento zero, f:=f+1
é uma chamada recursiva de f.
fim
porque eu não poderia escrever uma função xor sensata. Os tipos de conjuntos ajudam um pouco aqui (por assim dizer), mas não
o suficiente; pessoas que afirmam que Pascal é uma linguagem de programação de sistema geralmente ignoram esse ponto. Por
exemplo, [18, pág. 685]
''Pascal é atualmente [1977] a melhor linguagem de domínio público para fins de programação de sistemas e
implementação de software.''
parece um pouco ingênuo.
Não há string nula, talvez porque Pascal usa a notação de aspas duplas para indicar uma aspa incorporada em uma string:
Não há como colocar símbolos não gráficos em strings. De fato, caracteres não gráficos são não-pessoas em um sentido mais
forte, pois não são mencionados em nenhuma parte da linguagem padrão.
Conceitos como novas linhas, tabulações e assim por diante são tratados em cada sistema de maneira ad hoc , geralmente
sabendo algo sobre o conjunto de caracteres (por exemplo, nova linha ASCII tem valor decimal 10).
Não há processador de macro. O mecanismo const para definir constantes de manifesto cuida de cerca de 95% dos usos
de instruções #define simples em C, mas as mais envolvidas são inúteis. Certamente é possível colocar um pré-processador de
macro em um compilador Pascal. Isso me permitiu simular um procedimento de erro sensato como
(halt, por sua vez, pode ser definido como uma ramificação para o final do bloco mais externo.) Em seguida, chamadas como
funciona desde que o writeln (como parte do ambiente Pascal padrão) pode lidar com strings de qualquer tamanho.
É lamentável que não haja como disponibilizar essa comodidade para rotinas em geral.
A linguagem proíbe expressões em declarações, então não é possível escrever coisas como
- 12 -
6. Perspectiva
O esforço para reescrever os programas em Ferramentas de Software começou em março de 1980 e, aos
trancos e barrancos, durou até janeiro de 1981. O produto final19 foi publicado em junho de 1981. Durante esse tempo
fui me adaptando gradualmente à maioria dos problemas superficiais com Pascal (cosméticos, as inadequações do
controle de fluxo), e desenvolveu soluções imperfeitas para as significativas (tamanhos de array, ambiente de tempo de
execução).
Os programas do livro devem ser programas completos e bem projetados que realizam tarefas não triviais. Mas
eles não precisam ser eficientes, nem suas interações com o sistema operacional são muito complicadas, então
consegui me virar com algumas soluções bem desajeitadas, que simplesmente não funcionariam para programas reais.
Não há nenhuma maneira significativa pela qual eu achei o Pascal superior ao C, mas há vários lugares onde é
uma clara melhoria em relação ao Ratfor. A mais óbvia de longe é a recursão: vários programas são muito mais limpos
quando escritos recursivamente, notadamente a pesquisa de padrões, a classificação rápida e a avaliação de expressões.
Os tipos de dados de enumeração são uma boa ideia. Ao mesmo tempo, delimitam o leque de valores jurídicos
e os documentam. Os registros ajudam a agrupar variáveis relacionadas. Eu encontrei relativamente pouco uso para
ponteiros.
Variáveis booleanas são melhores que números inteiros para condições booleanas; os programas Ratfor
originais continham algumas construções não naturais porque as variáveis lógicas do Fortran são mal projetadas.
Ocasionalmente, a verificação de tipos de Pascal alertava sobre um lapso de mão ao escrever um programa; a
verificação de valores em tempo de execução também indicava erros de tempos em tempos, particularmente violações
de intervalo de índice.
Voltando ao lado negativo, recompilar um grande programa do zero para alterar uma única linha de fonte é
extremamente cansativo; compilação separada, com ou sem verificação de tipo, é obrigatória para programas grandes.
Tirei pouco benefício do fato de que os caracteres fazem parte do Pascal e não do For tran, porque o tratamento
Pascal de strings e não gráficos é muito inadequado. Em ambas as linguagens, é terrivelmente desajeitado inicializar
strings literais para tabelas de palavras-chave, mensagens de erro e similares.
Os programas finalizados têm, em geral, aproximadamente o mesmo número de linhas de origem que seus
equivalentes Ratfor. A princípio isso me surpreendeu, pois meu preconceito era que Pascal é uma linguagem mais
prolixa e menos expressiva. A verdadeira razão parece ser que Pascal permite expressões arbitrárias em lugares como
limites de loop e subscritos onde Fortran (ou seja, Fortran 66 portátil) não permite, então algumas atribuições inúteis
podem ser eliminadas; além disso, os programas Ratfor declaram funções enquanto os programas Pascal não.
2. A falta de variáveis estáticas, inicialização e uma forma de comunicação não hierárquica combinam-se para destruir
a “localidade” de um programa — as variáveis requerem muito mais escopo do que deveriam.
3. A natureza de uma só passagem da linguagem força os procedimentos e funções a serem apresentados em uma
ordem não natural; a separação forçada de várias declarações dispersa os componentes do programa que
logicamente pertencem um ao outro.
4. A falta de compilação separada impede o desenvolvimento de grandes programas e torna
o uso de bibliotecas impossível.
5. A ordem de avaliação da expressão lógica não pode ser controlada, o que leva a
Machine Translated by Google
- 13 -
7. A E/S padrão está com defeito. Não há nenhuma provisão sensata para lidar com arquivos ou argumentos de
programa como parte da linguagem padrão, e nenhum mecanismo de extensão.
8. A linguagem carece da maioria das ferramentas necessárias para montar grandes programas, principalmente
inclusão de arquivo.
9. Não há escapatória.
Este último ponto é talvez o mais importante. A linguagem é inadequada, mas circunscrita, pois não há como
escapar de suas limitações. Não há casts para desabilitar a verificação de tipo quando necessário. Não há como
substituir o ambiente de tempo de execução defeituoso por um
sensata, a menos que se controle o compilador que define os "procedimentos padrão". A linguagem é fechada.
Pessoas que usam Pascal para programação séria caem em uma armadilha fatal. Porque a linguagem
é tão impotente, deve ser estendida. Mas cada grupo estende Pascal em sua própria direção, para fazer
parece com qualquer idioma que eles realmente querem. Extensões para compilação separada, Fortran como COMMON,
tipos de dados de string, variáveis estáticas internas, inicialização, números octais, operadores de bits, etc., todos
aumentam a utilidade da linguagem para um grupo, mas destroem sua portabilidade para outros.
Eu sinto que é um erro usar Pascal para algo muito além de seu alvo original. Em seu
forma pura, Pascal é uma linguagem de brinquedo, adequada para ensino, mas não para programação real.
Agradecimentos
Sou grato a Al Aho, Al Feuer, Narain Gehani, Bob Martin, Doug McIlroy, Rob Pike,
Dennis Ritchie, Chris Van Wyk e Charles Wetherell pelas críticas úteis às versões anteriores do
este papel.
- 14 -
16. JD Gannon e JJ Horning, ''Design de linguagem para confiabilidade de programação'', IEEE Trans. Programas
Engineering SE-1(2), pp. 179-191 (junho de 1975).
17. JD Ichbiah, et al, ''Racional para o design da linguagem de programação Ada'', SIGPLAN Notices
14 (6) (junho de 1979).
18. J. Welsh, WJ Sneeringer e CAR Hoare, ibid.
19. BW Kernighan e PJ Plauger, Ferramentas de Software em Pascal, Addison-Wesley (1981).