Você está na página 1de 16

Machine Translated by Google

Laboratórios AT&T Bell


Murray Hill, Nova Jersey 07974

Relatório Técnico de Ciência da Computação nº 100

Por que Pascal não é minha linguagem de programação favorita

Brian W Kernighan

2 de abril de 1981
Machine Translated by Google

Por que Pascal não é minha linguagem de programação favorita

Brian W Kernighan
Laboratórios AT&T Bell
Murray Hill, Nova Jersey 07974

ABSTRATO

A linguagem de programação Pascal tornou-se a linguagem dominante de instrução


no ensino de ciência da computação. Também influenciou fortemente as línguas
desenvolvidas posteriormente, em particular a Ada.
Pascal foi originalmente concebido principalmente como uma linguagem de ensino,
mas tem sido cada vez mais recomendado como uma linguagem para programação séria
também, por exemplo, para tarefas de programação de sistemas e até sistemas operacionais.
Pascal, pelo menos em sua forma padrão, simplesmente não é adequado para
programação séria. Este artigo discute minha descoberta pessoal de algumas das razões.

2 de abril de 1981
Machine Translated by Google

Por que Pascal não é minha linguagem de programação favorita

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

e dentro de cada área mais ou menos em ordem decrescente de importância.


Para expor minhas conclusões desde o início: Pascal pode ser uma linguagem admirável para ensinar iniciantes a
programar; Não tenho experiência direta com isso. Foi uma conquista considerável para 1968. Certamente influenciou o
design de linguagens recentes, das quais Ada provavelmente será a mais importante. Mas em sua forma padrão (tanto
atual quanto proposta), Pascal não é adequado para escrever programas reais. É adequado apenas para programas
pequenos e autocontidos que têm apenas interações triviais com seu ambiente e que não usam nenhum software
Machine Translated by Google

-2-

escrito por qualquer outra pessoa.

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.

2.1. O tamanho de um array é parte de seu tipo


Se alguém declara

foi arr10 : array [1..10] de inteiro; arr20 : array [1..20]


de inteiro;

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 é

var temp : array [1..10] de char;

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

tipo string = array [1..MAXSTR] de char;

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:

modelo charbuf = array [1..MAXBUF] de char; charindex = array


[1..MAXINDEX] de 0..MAXBUF;

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.

Como sugerido acima, uma string constante é escrita como

'isso é uma corda'

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:

error('mensagem curta'); error('esta é uma mensagem um pouco


mais longa');

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.

2.2. Não há variáveis estáticas e nenhuma inicialização

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:

formatador de programa (...);

foi

você: 0..1; { direção para adicionar espaços extras }


.
.
.
justificação do procedimento (...);
começar
dir := 1 - dir; {direção oposta da última vez}
...
fim;

...

begin { rotina principal do formatador } dir := 0;

...
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() {

estático int dir = 0;

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.

2.3. Componentes de programa relacionados devem ser


mantidos separados Como o Pascal original foi implementado com um compilador de uma passagem, a
linguagem acredita fortemente na declaração antes do uso. Em particular, procedimentos e funções devem ser
declarados (corpo e tudo) antes de serem usados. O resultado é que um programa Pascal típico lê de baixo para
cima — todos os procedimentos e funções são exibidos antes de qualquer código que os chama, em todos os
níveis. Isso é essencialmente oposto à ordem em que as funções são projetadas e usadas.

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

label declarações de rótulos, se houver


declarações de constantes const , se houver
declarações de
declarações
variáveis dedetipo
tipo,
, se
sehouver
houver
foi declarações de procedimentos e
funções , se houver algum começo

corpo de função ou procedimento


fim

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-

Um recurso de inclusão de arquivos ajuda apenas um pouco aqui.

A nova norma não flexibiliza os requisitos sobre a ordem das declarações.

2.4. Não há compilação separada A linguagem

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.

O novo padrão não oferece compilação separada.

2.5. Alguns problemas diversos de tipo e escopo A maioria dos

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:

procedimento add10(var a : array[1..10] de inteiro);

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:

modelo a10 = array [1..10] de inteiro;


...
procedimento add10 (var a : a10);

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

se (c = em branco) ou (c = tab) ou (c = nova linha) então ...

pode ser escrito de forma mais clara e talvez mais eficiente como

se c em [em branco, tabulação, nova linha] então ...


Machine Translated by Google

-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

{ isalphanum(c) -- true se c for letra ou dígito } function isalphanum (c : char) :


boolean; começar

isalphanum := c in ['a'..'z', 'A'..'Z', '0'..'9']


fim;

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.)

2.6. Não há escapatória


Não há como sobrescrever o mecanismo de tipo quando necessário, nada análogo ao mecanismo ''cast'' em C.
Isso significa que não é possível escrever programas como alocadores de armazenamento ou sistemas de E/S em Pascal ,
porque não há como falar sobre o tipo de objeto que eles retornam e não há como forçar esses objetos em um tipo
arbitrário para outro uso. (Estritamente falando, há um grande buraco na verificação de tipo próximo aos registros de
variantes, através do qual algumas incompatibilidades de tipo ilegais podem ser obtidas.)

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:

enquanto (i <= XMAX) e (x[i] > 0) fazem ...

é 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:

while (getnext(...)) { if (algo) break

resto do laço
}

Sem instrução break, a primeira tentativa em Pascal é

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

senão, se não for feito, comece o resto do


loop
fim
fim

É 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

O ambiente de tempo de execução do Pascal é relativamente escasso e não há mecanismo de extensão


exceto talvez bibliotecas de nível de fonte na linguagem "oficial".
A E/S integrada do Pascal tem uma reputação merecidamente ruim. Ela acredita fortemente em entradas
e saídas orientadas para registros. Ele também tem uma convenção de antecipação que é difícil de implementar
adequadamente em um ambiente interativo. Basicamente, o problema é que o sistema de E/S acredita que deve
ler um registro antes do registro que está sendo processado. Em um sistema interativo, isso significa que quando
um programa é iniciado, sua primeira operação é tentar ler o terminal para a primeira linha de entrada, antes que
qualquer programa em si seja executado. Mas no programa
Machine Translated by Google

-9-

escreva('Digite seu nome: '); leia(nome);

...

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:

{ getc -- lê o caractere da entrada padrão } function getc (var c : character) :


character;
foi

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:

reset(fv, nome do arquivo);

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 -

reset(fv, nome do arquivo); se fv


= falha então...

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.

O novo padrão não oferece mudanças em nenhuma dessas áreas.

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;

Agora, se adicionarmos um else, devemos remover o ponto e vírgula no final:

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.

Os programadores C e Ratfor acham o início e o fim volumosos em comparação com { e }.


Machine Translated by Google

- 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.

Há uma escassez de operadores (provavelmente relacionada à escassez de níveis de precedência).


Em particular, não há operadores de manipulação de bits (AND, OR, XOR, etc.). Eu simplesmente desisti de
tentar escrever o seguinte programa de criptografia trivial em Pascal:
e := 1; while
getc(c) <> ENDFILE comece putc(xor(c, key[i])); i := mod keylen +
1

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:

'Isto é um '' personagem'

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

#define erro(s) begin writeln(s); pare com isso

(halt, por sua vez, pode ser definido como uma ramificação para o final do bloco mais externo.) Em seguida, chamadas como

error('pequena string'); error('string muito


maior');

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

const TAMANHO = 10;


modelo arr = array [1..SIZE+1] de inteiro;

ou até mais simples como

const TAMANHO = 10;


TAMANHO1 = TAMANHO + 1;
Machine Translated by Google

- 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.

Para encerrar, deixe-me resumir os principais pontos do caso contra Pascal.


1. Como o tamanho de um array faz parte de seu tipo, não é possível escrever rotinas de uso geral, ou seja, lidar
com arrays de tamanhos diferentes. Em particular, o manuseio de strings é muito difícil.

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 -

código e variáveis estranhas.


6. A instrução case é emasculada porque não há cláusula padrão.

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.

1. Feuer, AR e NH Gehani, ''Uma comparação das linguagens de programação C e Pascal - Parte


I: Language Concepts,'' memorando interno do Bell Labs (setembro de 1979).
2. NH Gehani e AR Feuer, ''Uma comparação das linguagens de programação C e Pascal - Parte
II: Propriedades do programa e domínios de programação,'' memorando interno do Bell Labs (fevereiro
1980).
3. P. Mateti, ''Pascal versus C: Uma Comparação Subjetiva'', Design de Linguagem e Metodologia de Programação
Simpósio, Springer-Verlag, Sydney, Austrália (setembro de 1979).
4. A. Springer, ''Uma Comparação da Linguagem C e Pascal'', Relatório Técnico da IBM G320-2128, Cam bridge
Scientific Center (agosto de 1979).
5. BW Kernighan e PJ Plauger, Software Tools, Addison-Wesley, Reading, Mass. (1976).
6. K. Jensen, Pascal User Manual and Report, Springer-Verlag (1978). (2ª edição.)
7. David V. Moffat, ''A Categorized Pascal Bibliography'', SIGPLAN Notices 15(10), pp. 63-75 (outubro
1980).
8. AN Habermann, ''Comentários Críticos sobre a Linguagem de Programação Pascal'' Acta Informatica 3,
pp. 47-57 (1973).
9. O. Lecarme e P. Desjardins, "Mais comentários sobre a linguagem de programação Pascal", Acta Informática 4,
pp. 231-243 (1975).
10. HJ Boom e E. DeJong, ''Uma Comparação Crítica de Várias Implementações de Linguagem de Programação
ções,'' Software Practice and Experience 10(6), pp. 435-473 (junho de 1980).
11. N. Wirth, "An Assessment of the Programming Language Pascal", IEEE Transactions on Software Engineering
SE-1(2), pp. 192-198 (junho de 1975).
12. O. Lecarme e P. Desjardins, ibid, p. 239.
13. AM Addyman, ''A Draft Proposal for Pascal'', SIGPLAN Notices 15(4), pp. 1-66 (abril de 1980).
14. J. Welsh, WJ Sneeringer e CAR Hoare, "Ambiguities and Insecurities in Pascal", Software Practice and Experience
7, pp. 685-696 (1977).
15. N. Wirth, ibid., p. 196.
Machine Translated by Google

- 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).

Você também pode gostar