Eiffel (linguagem de programação)
Eiffel é uma linguagem de programação orientada a objetos projetada por Bertrand Meyer (um proponente da orientação a objetos e autor de Object-Oriented Software Construction) e Eiffel Software. Meyer concebeu a linguagem em 1985 com o objetivo de aumentar a confiabilidade do desenvolvimento de software comercial; a primeira versão foi disponibilizada em 1986. Em 2005, Eiffel tornou-se uma linguagem padronizada pela ISO.
O design da linguagem está intimamente ligado ao método de programação Eiffel. Ambos são baseados em um conjunto de princípios, incluindo design por contrato, separação comando-consulta, princípio de acesso uniforme, princípio de escolha única, princípio aberto-fechado e separação opção-operando.
Muitos conceitos introduzidos inicialmente por Eiffel mais tarde chegaram a Java, C# e outras linguagens. Novas ideias de design de linguagem, particularmente através do processo de padronização Ecma/ISO, continuam a ser incorporadas à linguagem Eiffel.
Características
As principais características da linguagem Eiffel incluem:
- Uma estrutura de programa orientada a objetos na qual uma classe serve como unidade básica de decomposição.
- Design por contrato firmemente integrado com outros construtos de linguagem.
- Gestão de memória automática, normalmente implementada pela coleta de lixo.
- Herança, incluindo herança múltipla, renomeação, redefinição, "selecionar", herança não conformadora e outros mecanismos destinados a tornar a herança segura.
- Programação genérica restrita e sem restrições
- Um sistema de tipo uniforme que lida com semântica de valor e referência em que todos os tipos, incluindo tipos básicos como INTEGER, são baseados em classes.
- Digitação estática
- Segurança de Void, ou proteção estática contra chamadas em referências nulas, através do mecanismo de tipos anexados.
- Agentes, ou objetos que envolvem computação, intimamente conectado com fechamentos e cálculo lambda.
- Uma vez. rotinas, ou rotinas avaliadas apenas uma vez, para compartilhamento de objetos e inicialização descentralizada.
- Sintaxe baseada em palavras-chave na tradição ALGOL/Pascal, mas sem separador, na medida em que os ponto e vírgulas são opcionais, com sintaxe do operador disponível para rotinas.
- Insensibilidade
- Simples Concurrent Object-Oriented Programming (SCOOP) facilita a criação de múltiplos veículos de execução simultaneamente ativos em um nível de abstração acima dos detalhes específicos desses veículos (por exemplo, múltiplos fios sem gerenciamento específico de mutex).
Metas de design
Eiffel enfatiza declarações declarativas sobre o código processual e tenta eliminar a necessidade de instruções de contabilidade.
Eiffel evita truques de codificação ou técnicas de codificação destinadas a dicas de otimização para o compilador. O objetivo não é apenas tornar o código mais legível, mas também permitir que os programadores se concentrem nos aspectos importantes de um programa sem se atolar em detalhes de implementação. A simplicidade de Eiffel visa promover respostas simples, extensíveis, reutilizáveis e confiáveis para problemas de computação. Compiladores para programas de computador escritos em Eiffel fornecem extensas técnicas de otimização, como in-lining automático, que aliviam o programador de parte da carga de otimização.
Fundo
Eiffel foi originalmente desenvolvido pela Eiffel Software, empresa fundada por Bertrand Meyer. Construção de Software Orientada a Objetos contém um tratamento detalhado dos conceitos e da teoria da tecnologia de objetos que levou ao design de Eiffel.
O objetivo do projeto por trás da linguagem Eiffel, bibliotecas e métodos de programação é permitir que os programadores criem módulos de software confiáveis e reutilizáveis. Eiffel suporta herança múltipla, genericidade, polimorfismo, encapsulamento, conversões de tipo seguro e covariância de parâmetros. A contribuição mais importante de Eiffel para a engenharia de software é o design by contract (DbC), no qual asserções, pré-condições, pós-condições e invariantes de classe são empregados para ajudar a garantir a correção do programa sem sacrificar a eficiência.
O projeto de Eiffel é baseado na teoria de programação orientada a objetos, com apenas uma pequena influência de outros paradigmas ou preocupação com o suporte de código legado. Eiffel suporta formalmente tipos de dados abstratos. Sob o design de Eiffel, um texto de software deve ser capaz de reproduzir sua documentação de design a partir do próprio texto, usando uma implementação formalizada do "Abstract Data Type".
Implementações e ambientes
EiffelStudio é um ambiente de desenvolvimento integrado disponível sob uma licença de código aberto ou comercial. Ele oferece um ambiente orientado a objetos para engenharia de software. EiffelEnvision é um plug-in para Microsoft Visual Studio que permite aos usuários editar, compilar e depurar projetos Eiffel a partir do IDE do Microsoft Visual Studio. Cinco outras implementações de código aberto estão disponíveis: "The Eiffel Compiler" tecomp; Gobo Eiffel; SmartEiffel, a implementação GNU, baseada em uma versão mais antiga da linguagem; LibertyEiffel, baseado no compilador SmartEiffel; e Visual Eiffel.
Várias outras linguagens de programação incorporam elementos introduzidos pela primeira vez em Eiffel. Sather, por exemplo, foi originalmente baseado em Eiffel, mas desde então divergiu e agora inclui vários recursos de programação funcional. A linguagem de ensino interativo Blue, precursora do BlueJ, também é baseada em Eiffel. A Apple Media Tool inclui um Apple Media Language baseado em Eiffel.
Especificações e padrões
A definição da linguagem Eiffel é um padrão internacional da ISO. O padrão foi desenvolvido pela ECMA International, que primeiro aprovou o padrão em 21 de junho de 2005 como Padrão ECMA-367, Eiffel: Análise, Design e Linguagem de Programação. Em junho de 2006, ECMA e ISO adotaram a segunda versão. Em novembro de 2006, a ISO publicou pela primeira vez essa versão. O padrão pode ser encontrado e usado gratuitamente no site da ECMA. A versão ISO é idêntica em todos os aspectos, exceto na formatação.
Eiffel Software, "O Compilador Eiffel" a tecomp e a Gobo, desenvolvedora da biblioteca Eiffel, se comprometeram a implementar o padrão; Eiffel Software's EiffelStudio 6.1 e "The Eiffel Compiler" tecomp implementam alguns dos principais novos mecanismos - em particular, agentes inline, comandos de atribuição, notação de colchetes, herança não conforme e tipos anexados. A equipe SmartEiffel se afastou desse padrão para criar sua própria versão da linguagem, que eles acreditam estar mais próxima do estilo original de Eiffel. A Object Tools não divulgou se as versões futuras de seu compilador Eiffel cumprirão o padrão. LibertyEiffel implementa um dialeto em algum lugar entre a linguagem SmartEiffel e o padrão.
O padrão cita as seguintes especificações predecessoras da linguagem Eiffel:
- Bertrand Meyer: Eiffel: The Language, Prentice Hall, segunda impressão, 1992 (primeira impressão: 1991)
- Bertrand Meyer: Standard Eiffel (revisão da entrada anterior), em andamento, 1997-presente, na página ETL3 de Bertrand Meyer, e
- Bertrand Meyer: Object-Oriented Software Construction, Prentice Hall: primeira edição, 1988; segunda edição, 1997.
- Bertrand Meyer: Touch of Class: Learning to Program Well with Objects and Contracts, Springer-Verlag, 2009 ISBN 978-3-540-92144-8 lxiv + 876 páginas Impressão de cor completa, inúmeras fotografias de cor
A versão atual do padrão de junho de 2006 contém algumas inconsistências (por exemplo, redefinições de covariantes). O comitê da ECMA ainda não anunciou nenhum cronograma e orientação sobre como resolver as inconsistências.
Sintaxe e semântica
Estrutura geral
Um "sistema" ou "programa" é uma coleção de classes. Acima do nível das classes, Eiffel define cluster, que é essencialmente um grupo de classes, e possivelmente de subclusters (clusters aninhados). Clusters não são uma construção de linguagem sintática, mas sim uma convenção organizacional padrão. Normalmente, um programa Eiffel será organizado com cada classe em um arquivo separado e cada cluster em um diretório contendo arquivos de classe. Nesta organização, os subclusters são subdiretórios. Por exemplo, nas convenções organizacionais e de maiúsculas e minúsculas padrão, x.e
pode ser o nome de um arquivo que define uma classe chamada X.
Uma classe contém recursos, que são semelhantes a "rotinas", "membros", "atributos" ou "métodos" em outras linguagens de programação orientadas a objetos. Uma classe também define seus invariantes e contém outras propriedades, como uma "notas" seção para documentação e metadados. Os tipos de dados padrão de Eiffel, como INTEGER
, STRING
e ARRAY
, são todos classes.
Todo sistema deve ter uma classe designada como "root", com um de seus procedimentos de criação designado como "procedimento root". A execução de um sistema consiste em criar uma instância da classe raiz e executar seu procedimento raiz. Geralmente, isso cria novos objetos, chama novos recursos e assim por diante.
Eiffel tem cinco instruções executáveis básicas: atribuição, criação de objeto, chamada de rotina, condição e iteração. As estruturas de controle de Eiffel são rigorosas na aplicação da programação estruturada: cada bloco tem exatamente uma entrada e exatamente uma saída.
Escopo
Ao contrário de muitas linguagens orientadas a objetos, mas como Smalltalk, Eiffel não permite nenhuma atribuição em atributos de objetos, exceto dentro das características de um objeto, que é a aplicação prática do princípio de ocultação de informações ou abstração de dados, exigindo formal interfaces para mutação de dados. Para colocá-lo na linguagem de outras linguagens de programação orientadas a objetos, todos os atributos Eiffel são "protegidos", e "setters" são necessários para que os objetos do cliente modifiquem valores. Um resultado disso é que os "setters" pode, e normalmente implementa, os invariantes para os quais Eiffel fornece sintaxe.
Embora o Eiffel não permita acesso direto aos recursos de uma classe por um cliente da classe, ele permite a definição de um "comando de atribuição", como:
alguns_attribui: SOME_TYPE atribuição set_some_attribu set_some_attribu (v: VALORIZAÇÃO) -... Definir valor de some_attribute para `v'. do alguns_attribui ? v fim
Embora uma ligeira reverência à comunidade geral de desenvolvedores para permitir algo parecido com acesso direto (por exemplo, quebrando assim o princípio de ocultação de informações), a prática é perigosa, pois oculta ou ofusca a realidade de um "setter" sendo usado. Na prática, é melhor redirecionar a chamada para um configurador em vez de implicar um acesso direto a um recurso como some_attribute
como no exemplo de código acima.
Ao contrário de outras línguas, ter noções de "público", "protegido", "privado" e assim por diante, Eiffel usa uma tecnologia de exportação para controlar com mais precisão o escopo entre as classes de clientes e fornecedores. A visibilidade do recurso é verificada estaticamente em tempo de compilação. Por exemplo, (abaixo), o campo "{NONE}" é semelhante a "protegido" em outras línguas. Escopo aplicado dessa forma a um "conjunto de recursos" (por exemplo, tudo abaixo da palavra-chave 'recurso' para a próxima palavra-chave do conjunto de recursos ou o final da classe) pode ser alterado em classes descendentes usando a opção "exportar" palavra-chave.
recurso (NÃO? -- Inicializaçãodefault_create-- Inicialize uma nova instância decimal 'zero'.doO que fazer?fim
Como alternativa, a falta de uma declaração de exportação {x} implica {QUALQUER} e é semelhante à declaração "pública" abrangência de outras línguas.
recurso - Constantes
Finalmente, o escopo pode ser controlado de forma seletiva e precisa para qualquer classe no universo do projeto Eiffel, como:
recurso (DECIMAL, DCM_MA_DECIMAL_PARSER, DCM_MA_DECIMAL_HANDLER? -- Acesso
Aqui, o compilador permitirá que apenas as classes listadas entre chaves acessem os recursos dentro do grupo de recursos (por exemplo, DECIMAL, DCM_MA_DECIMAL_PARSER, DCM_MA_DECIMAL_HANDLER).
"Olá, mundo!"
A aparência de uma linguagem de programação geralmente é transmitida usando um "Hello, world!" programa. Tal programa escrito em Eiffel pode ser:
classe Olá.criar fazerrecurso fazer do impressão ("Olá, mundo!%N") fimfim
Este programa contém a classe HELLO_WORLD
. O construtor (rotina de criação) para a classe, denominado make
, invoca a rotina da biblioteca do sistema print
para escrever um "Hello,
world!"
para a saída.
Design por contrato
O conceito de Design by Contract é central para Eiffel. Os contratos afirmam o que deve ser verdadeiro antes de uma rotina ser executada (pré-condição) e o que deve ser verdadeiro após o término da rotina (pós-condição). Os contratos invariantes de classe definem quais asserções devem ser verdadeiras antes e depois de qualquer recurso de uma classe ser acessado (rotinas e atributos). Além disso, os contratos codificam em código executável as suposições do desenvolvedor e do designer sobre o ambiente operacional dos recursos de uma classe ou da classe como um todo por meio do invariante.
O compilador Eiffel é projetado para incluir os contratos de recursos e classes em vários níveis. O EiffelStudio, por exemplo, executa todos os contratos de recurso e classe durante a execução no "modo Workbench" Quando um executável é criado, o compilador é instruído por meio do arquivo de configurações do projeto (por exemplo, arquivo ECF) para incluir ou excluir qualquer conjunto de contratos. Assim, um arquivo executável pode ser compilado para incluir ou excluir qualquer nível de contrato, trazendo assim níveis contínuos de teste de unidade e integração. Além disso, os contratos podem ser executados de forma contínua e metódica por meio do recurso Auto-Test encontrado no EiffelStudio.
Os mecanismos Design by Contract são totalmente integrados à linguagem e orientam a redefinição de recursos na herança:
- Precondição de rotina: A pré-condição só pode ser enfraquecida por herança; qualquer chamada que atenda aos requisitos do antepassado atende aos do descendente.
- Pós-condição de rotina: A pós-condição só pode ser reforçada por herança; qualquer resultado garantido pelo ancestral ainda é fornecido pelo descendente.
- Classe invariante: Condições que devem ser verdadeiras após a criação do objeto e após qualquer chamada para uma rotina de classe exportada. Como a invariante é verificada tantas vezes, torna simultaneamente a forma mais cara e poderosa de condição ou contrato.
Além disso, o idioma oferece suporte a uma "instrução de verificação" (um tipo de "assert"), invariantes de loop e variantes de loop (que garantem a terminação do loop).
Capacidade de proteção contra vácuo
O recurso de proteção contra vácuo, como digitação estática, é outro recurso para melhorar a qualidade do software. O software void-safe é protegido contra erros de tempo de execução causados por chamadas para void referências e, portanto, será mais confiável do que o software no qual podem ocorrer chamadas para void alvos. A analogia com a digitação estática é útil. Na verdade, a capacidade void-safe pode ser vista como uma extensão do sistema de tipos, ou um passo além da digitação estática, porque o mecanismo para garantir a segurança do void está integrado ao sistema de tipos.
A proteção contra chamadas de alvo void pode ser vista por meio da noção de anexo e (por extensão) desapego (por exemplo, palavra-chave destacável). O recurso void-safe pode ser visto em um breve retrabalho do código de exemplo usado acima:
alguns_attribui: destacável SOME_TYPE use_some_attribute -... Definir valor de some_attribute para `v'. do se anexo alguns_attribui como I_attribuído então Do_algo (I_attribuído) fim fim Do_algo (A_valor: SOME_TYPE) - Faz alguma coisa com "a_value". do ... fazendo Alguma coisa? com "A_valor' ... fim
O exemplo de código acima mostra como o compilador pode endereçar estaticamente a confiabilidade de se some_attribute
será anexado ou desanexado no ponto em que for usado. Notavelmente, a palavra-chave attached
permite um "attachment local" (por exemplo, l_attribute
), cujo escopo é apenas o bloco de código incluído na construção da instrução if. Assim, dentro deste pequeno bloco de código, a variável local (por exemplo, l_attribute
) pode ser estaticamente garantida como não nula (ou seja, segura para void).
Recursos: comandos e consultas
A principal característica de uma classe é que ela define um conjunto de recursos: como uma classe representa um conjunto de objetos de tempo de execução, ou "instâncias", um recurso é uma operação nesses objetos. Existem dois tipos de recursos: consultas e comandos. Uma consulta fornece informações sobre uma instância. Um comando modifica uma instância.
A distinção comando-consulta é importante para o método Eiffel. Em particular:
- Princípio uniforme-acesso: do ponto de vista de um cliente de software fazendo uma chamada para um recurso de classe, se uma consulta é um atributo (valor de campo) ou uma função (valor calculado) não deve fazer qualquer diferença. Por exemplo,
a_vehicle.speed
poderia ser um atributo acessado no objetoa_vehicle
, ou pode ser computado por uma função que divide a distância pelo tempo. A notação é a mesma em ambos os casos, de modo que seja fácil mudar a implementação da classe sem afetar o software cliente. - Separação de Pergunta de Comando Princípio: As consultas não devem modificar a instância. Isto não é uma regra linguística, mas um princípio metodológico. Assim, em bom estilo Eiffel, não se encontra funções "get" que mudam algo e retornam um resultado; em vez disso, há comandos (procedimentos) para mudar objetos, e consultas para obter informações sobre o objeto, resultando de alterações anteriores.
Sobrecarga
Eiffel não permite sobrecarga de argumentos. Cada nome de recurso dentro de uma classe sempre mapeia para um recurso específico dentro da classe. Um nome, dentro de uma classe, significa uma coisa. Essa escolha de design ajuda na legibilidade das classes, evitando uma causa de ambigüidade sobre qual rotina será invocada por uma chamada. Também simplifica o mecanismo de linguagem; em particular, é isso que torna possível o mecanismo de herança múltipla de Eiffel.
Os nomes podem, é claro, ser reutilizados em diferentes classes. Por exemplo, o recurso plus (junto com seu alias infixo "+") é definido em vários classes: INTEGER, REAL, STRING, etc.
Genericidade
Uma classe genérica é uma classe que varia de acordo com o tipo (por exemplo, LIST [PHONE], uma lista de números de telefone; ACCOUNT [G->ACCOUNT_TYPE], permitindo ACCOUNT [POUPANÇA] e ACCOUNT [CHECKING], etc.). As classes podem ser genéricas, para expressar que são parametrizadas por tipos. Parâmetros genéricos aparecem entre colchetes:
classe LISTA Não.G] ...
G é conhecido como um "parâmetro genérico formal". (Eiffel reserva "argumento" para rotinas e usa "parâmetro" apenas para classes genéricas.) Com tal declaração, G representa dentro da classe um tipo arbitrário; então uma função pode retornar um valor do tipo G, e uma rotina pode receber um argumento desse tipo:
item: G do ... fimPõe-te a andar. (x: G) do ... fim
O LIST [INTEGER]
e o LIST [WORD]
são "derivações genéricas" desta classe. Combinações permitidas (com n: INTEGER
, w: WORD
, il: LIST [INTEGER]
, wl: LIST [WORD]) são:
n ? il.itemO que foi?.Põe-te a andar. (O quê?)
INTEGER
e WORD
são os "parâmetros genéricos reais" nestas derivações genéricas.
Também é possível ter 'restrição' parâmetros formais, para os quais o parâmetro real deve herdar de uma determinada classe, a "restrição". Por exemplo, em
classe QUADRO Não.G, KEY - Sim. QUADRO]
uma derivação HASH_TABLE [INTEGER, STRING]
é válida apenas se STRING
herdar de HASHABLE
(como de fato ocorre nas típicas bibliotecas Eiffel). Dentro da classe, ter KEY
restrito por HASHABLE
significa que para x: KEY
é possível aplicar a x
todos os recursos de HASHABLE
, como em x.hash_code
.
Noções básicas de herança
Para herdar de uma ou mais outras, uma classe incluirá uma cláusula inherit
no início:
classe C herda A B-... O resto da declaração de classe...
A classe pode redefinir (substituir) alguns ou todos os recursos herdados. Isso deve ser explicitamente anunciado no início da classe por meio de uma subcláusula redefine
da cláusula de herança, como em
classe C herda A redefine f, g, h fim B redefine u, v fim
Veja uma discussão completa sobre a herança de Eiffel.
Aulas e recursos adiados
As classes podem ser definidas com deferred class
em vez de class
para indicar que a classe não pode ser instanciada diretamente. As classes não instanciáveis são chamadas de classes abstratas em algumas outras linguagens de programação orientadas a objetos. Na linguagem de Eiffel, apenas um "eficaz" classe pode ser instanciada (pode ser descendente de uma classe adiada). Um recurso também pode ser adiado usando a palavra-chave deferred
no lugar de uma cláusula do
. Se uma classe tiver quaisquer características diferidas, ela deve ser declarada como diferida; no entanto, uma classe sem recursos diferidos pode, no entanto, ser adiada.
As classes adiadas desempenham um pouco do mesmo papel que as interfaces em linguagens como Java, embora muitos teóricos da programação orientada a objetos acreditem que as próprias interfaces são em grande parte uma resposta à falta de herança múltipla do Java (que Eiffel tem).
Renomeando
Uma classe que herda de uma ou mais outras obtém todos os seus recursos, por padrão, com seus nomes originais. Pode, no entanto, alterar seus nomes por meio de cláusulas rename
. Isso é necessário no caso de herança múltipla se houver conflito de nomes entre recursos herdados; sem renomear, a classe resultante violaria o princípio de não sobrecarga observado acima e, portanto, seria inválida.
Tuplos
Os tipos de tuplas podem ser vistos como uma forma simples de classe, fornecendo apenas atributos e o "setter" procedimento. Um tipo típico de tupla lê
TUP Não.Nome: ESTRUTURA; peso: REALIZAÇÃO; data: DECLARAÇÃO]
e pode ser usado para descrever uma noção simples de registro de nascimento se uma classe não for necessária. Uma instância de tal tupla é simplesmente uma sequência de valores com os tipos fornecidos, dados entre colchetes, como
Não."Brigitte", 3.5, Última noite]
Os componentes de tal tupla podem ser acessados como se as tags de tupla fossem atributos de uma classe, por exemplo, se t
foi atribuído à tupla acima, então t.weight
tem valor 3,5.
Graças à noção de comando assigner (veja abaixo), a notação de ponto também pode ser usada para atribuir componentes de tal tupla, como em
).peso ? ).peso + 0,5
As tags de tupla são opcionais, de modo que também é possível escrever um tipo de tupla como TUPLE [STRING, REAL, DATE]
. (Em alguns compiladores, esta é a única forma de tupla, pois as tags foram introduzidas com o padrão ECMA.)
A especificação precisa de, por exemplo, TUPLE [A, B, C]
é que descreve sequências de pelo menos três elementos, sendo os três primeiros dos tipos A
, B
, C
respectivamente. Como resultado, TUPLE [A, B, C]
está em conformidade com (pode ser atribuído a) TUPLE [A, B]
, para TUPLE [A]
e para TUPLE
(sem parâmetros), o tipo de tupla superior ao qual todos os tipos de tupla estão em conformidade.
Agentes
O "agente" O mecanismo envolve as operações em objetos. Esse mecanismo pode ser usado para iteração, programação orientada a eventos e outros contextos nos quais é útil passar operações pela estrutura do programa. Outras linguagens de programação, especialmente aquelas que enfatizam a programação funcional, permitem um padrão semelhante usando continuações, encerramentos ou geradores; Os agentes de Eiffel enfatizam o paradigma orientado a objetos da linguagem e usam uma sintaxe e semântica semelhantes aos blocos de código em Smalltalk e Ruby.
Por exemplo, para executar o bloco my_action
para cada elemento de my_list
, deve-se escrever:
A minha lista.Do_all (Agente my_action)
Para executar my_action
apenas em elementos que satisfaçam my_condition
, uma limitação/filtro pode ser adicionada:
A minha lista.- Sim. (Agente my_action, Agente my_condicionado)
Nesses exemplos, my_action
e my_condition
são rotinas. Prefixá-los com agent
produz um objeto que representa a rotina correspondente com todas as suas propriedades, em particular a capacidade de ser chamado com os argumentos apropriados. Portanto, se a
representa esse objeto (por exemplo, porque a
é o argumento para do_all
), a instrução
um.Chamada (Não.x])
irá chamar a rotina original com o argumento x
, como se tivéssemos chamado diretamente a rotina original: my_action (x)
. Os argumentos para chamar
são passados como uma tupla, aqui [x]
.
É possível manter alguns argumentos para um agente aberto e tornar outros fechados. Os argumentos abertos são passados como argumentos para call
: eles são fornecidos no momento do uso do agente. Os argumentos fechados são fornecidos no momento da definição do agente. Por exemplo, se action2
tiver dois argumentos, a iteração
A minha lista.Do_all (Agente ação2 (?, Sim.)
itera action2 (x, y)
para valores sucessivos de x
, onde o segundo argumento permanece definido como y
. O ponto de interrogação ?
indica um argumento aberto; y
é um argumento fechado do agente. Observe que a sintaxe básica agent f
é uma abreviação para agent f (?, ?,...)
com todos os argumentos abertos. Também é possível abrir o alvo de um agente através da notação {T}?
onde T
é o tipo do alvo.
A distinção entre operandos abertos e fechados (operandos = argumentos + alvo) corresponde à distinção entre variáveis limitadas e livres no cálculo lambda. Uma expressão de agente como action2 (?, y)
com alguns operandos fechados e alguns abertos corresponde a uma versão da operação original curried nos operandos fechados.
O mecanismo do agente também permite definir um agente sem referência a uma rotina existente (como my_action
, my_condition
, action2
), por meio de inline agentes como em
A minha lista.Do_all (Agente (S: ESTRUTURA) exigir Não.: S - Sim. Vóide do S.append_caracter (') Assegurar apêndice: S.contagem = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = velho S.contagem + 1 fim)
O agente inline passado aqui pode ter todas as armadilhas de uma rotina normal, incluindo pré-condição, pós-condição, cláusula de resgate (não usada aqui) e uma assinatura completa. Isso evita definir rotinas quando tudo o que é necessário é uma computação a ser agrupada em um agente. Isso é útil principalmente para contratos, como em uma cláusula invariante que expressa que todos os elementos de uma lista são positivos:
A minha lista.Por favor! (Agente (x: INTEGRAL: BOLEAN do Resultado ? (x > 0) fim)
O mecanismo atual do agente deixa uma possibilidade de erro do tipo run-time (se uma rotina com n argumentos for passada para um agente que espera m argumentos com m < n). Isso pode ser evitado por uma verificação em tempo de execução por meio da pré-condição valid_arguments
de call
. Várias propostas para uma correção puramente estática deste problema estão disponíveis, incluindo uma proposta de mudança de linguagem por Ribet et al.
Rotinas únicas
O resultado de uma rotina pode ser armazenado em cache usando a palavra-chave once
no lugar de do
. Chamadas não-primeiras para uma rotina não requerem computação adicional ou alocação de recursos, mas simplesmente retornam um resultado calculado anteriormente. Um padrão comum para "funções únicas" é fornecer objetos compartilhados; a primeira chamada criará o objeto, as subsequentes retornarão a referência a esse objeto. O esquema típico é:
shared_object: SOME_TYPE uma vez. criar Resultado.fazer (Args) -... Isso cria o objeto e retorna uma referência a ele através de `Result'. fim
O objeto retornado—Result
no exemplo—pode ser mutável, mas sua referência permanece a mesma.
Muitas vezes, "rotinas únicas" execute uma inicialização necessária: várias chamadas para uma biblioteca podem incluir uma chamada para o procedimento de inicialização, mas apenas a primeira dessas chamadas executará as ações necessárias. Usando este padrão, a inicialização pode ser descentralizada, evitando a necessidade de um módulo de inicialização especial. "Uma vez que as rotinas" são semelhantes em propósito e efeito ao padrão singleton em muitas linguagens de programação e ao padrão Borg usado em Python.
Por padrão, uma "rotina única" é chamado uma vez por thread. A semântica pode ser ajustada para uma vez por processo ou uma vez por objeto qualificando-a com uma "chave única", por exemplo uma vez ("PROCESSO")
.
Conversões
Eiffel fornece um mecanismo para permitir conversões entre vários tipos. Os mecanismos coexistem com a herança e a complementam. Para evitar qualquer confusão entre os dois mecanismos, o projeto impõe o seguinte princípio:
- (princípio da conversão) Um tipo pode não se conformar e converter para outro.
Por exemplo, NEWSPAPER
pode estar de acordo com PUBLICATION
, mas INTEGER
converte para REAL
(e não herda a partir dele).
O mecanismo de conversão simplesmente generaliza as regras de conversão ad hoc (como, de fato, entre INTEGER
e REAL
) que existem na maioria das linguagens de programação, tornando-as aplicáveis a qualquer tipo como desde que o princípio acima seja observado. Por exemplo, uma classe DATE
pode ser declarada para converter em STRING
; isso torna possível criar uma string de uma data simplesmente por meio de
A minha corda ? Meu Deus!
como um atalho para usar uma criação de objeto explícito com um procedimento de conversão:
criar A minha corda.O que fazer? (Meu Deus!)
Para tornar o primeiro formulário possível como sinônimo do segundo, basta listar o procedimento de criação (construtor) make_from_date
em uma cláusula convert
no início do aula.
Como outro exemplo, se houver tal procedimento de conversão listado de TUPLE [dia: INTEGER; mês: STRING; ano: INTEGER], então pode-se atribuir diretamente uma tupla a uma data, causando a conversão apropriada, como em
Bastille_day ? Não.14, "July", 1789]
Tratamento de exceções
O tratamento de exceções em Eiffel é baseado nos princípios de design by contract. Por exemplo, uma exceção ocorre quando o chamador de uma rotina falha em satisfazer uma pré-condição ou quando uma rotina não pode garantir uma pós-condição prometida. No Eiffel, o tratamento de exceções não é usado para controle de fluxo ou para corrigir erros de entrada de dados.
Um manipulador de exceção Eiffel é definido usando a palavra-chave rescue. Dentro da seção rescue, a palavra-chave retry executa a rotina novamente. Por exemplo, a rotina a seguir rastreia o número de tentativas de execução da rotina e repete apenas um determinado número de vezes:
Conect_to_server (servidor: Então...) -- Conecte-se a um servidor ou desista após 10 tentativas. exigir servidor - Sim. Vóide e então servidor.endereço - Sim. Vóide local local tentativas: INTEGRAL do servidor.Conecte-se Assegurar conectado: servidor.is_connected resgate se tentativas < 10. então tentativas ? tentativas + 1 Retirar fim fim
Este exemplo é indiscutivelmente falho para qualquer coisa, exceto para os programas mais simples, porque a falha de conexão é esperada. Para a maioria dos programas, um nome de rotina como attempt_connecting_to_server seria melhor, e a pós-condição não prometeria uma conexão, deixando para o chamador tomar as medidas apropriadas se a conexão não fosse aberta.
Simultaneidade
Várias bibliotecas de rede e encadeamento estão disponíveis, como EiffelNet e EiffelThreads. Um modelo de simultaneidade para Eiffel, baseado nos conceitos de design by contract, é o SCOOP, ou Simple Concurrent Object-Oriented Programming, ainda não parte da definição da linguagem oficial, mas disponível no EiffelStudio. CAMEO é uma variação (não implementada) do SCOOP para Eiffel. A simultaneidade também interage com exceções. Exceções assíncronas podem ser problemáticas (onde uma rotina levanta uma exceção depois que seu chamador terminou).
Sintaxe do operador e colchetes, comandos de atribuição
A visão de computação de Eiffel é totalmente orientada a objetos, no sentido de que toda operação é relativa a um objeto, o "alvo". Assim, por exemplo, uma adição como
um + b)
é entendido conceitualmente como se fosse a chamada do método
um.mais (b))
com alvo a
, recurso plus
e argumento b
.
Claro, a primeira é a sintaxe convencional e geralmente preferida. A sintaxe do operador torna possível usar qualquer uma das formas declarando o recurso (por exemplo em INTEGER
, mas isso se aplica a outras classes básicas e pode ser usado em qualquer outra para a qual esse operador seja apropriado):
mais Alias "+" (outros: INTEGRAL: INTEGRAL -... Declaração de função normal... fim
O intervalo de operadores que podem ser usados como "alias" é bastante amplo; eles incluem operadores predefinidos, como "+" mas também "operadores gratuitos" feito de símbolos não alfanuméricos. Isso torna possível projetar notações especiais de infixos e prefixos, por exemplo, em aplicações matemáticas e físicas.
Cada classe pode, além disso, ter uma função com alias para "[]", o "colchete" operador, permitindo a notação a [i,...]
como sinônimo de a.f (i,...)
onde f
é o função escolhida. Isso é particularmente útil para estruturas de contêineres como arrays, tabelas hash, listas, etc. Por exemplo, o acesso a um elemento de uma tabela hash com chaves de string pode ser escrito
número ? Telefone_book Não."JILL SMITH"]
"Comandos de atribuição" são um mecanismo complementar projetado com o mesmo espírito de permitir uma notação conveniente e bem estabelecida reinterpretada na estrutura da programação orientada a objetos. Os comandos do atribuidor permitem uma sintaxe semelhante à atribuição para chamar "setter" procedimentos. Uma atribuição adequada nunca pode estar na forma a.x:= v
, pois isso viola a ocultação de informações; você tem que ir para um comando setter (procedimento). Por exemplo, a classe da tabela hash pode ter a função e o procedimento
item Alias "[] (chave chave: ESTRUTURA: ELEMENTO Não.3] -... O elemento de chave 'key'. - "Query" ("Getter") do ... fimPõe-te a andar. (e: ELEMENTO; chave chave: ESTRUTURA) -... Insira o elemento `e', associando-o com a chave `key'. - comando ("Setter") do ... fim
Então, para inserir um elemento, você deve usar uma chamada explícita para o comando setter:
Não.4] Telefone_book.Põe-te a andar. (Nova pessoa, "JILL SMITH")
É possível escrever isso de forma equivalente como
Não.5] Telefone_book Não."JILL SMITH"] ? Nova pessoa
(da mesma forma que phone_book ["JILL SMITH"]
é sinônimo de number:= phone_book.item ("JILL SMITH")
), desde que a declaração de item
agora comece (substituindo [3]) com
item Alias "[] (chave chave: ESTRUTURA: ELEMENTO atribuição Põe-te a andar.
Isso declara put
como o comando atribuídor associado a item
e, combinado com o alias entre colchetes, torna [5] válido e equivalente a [4]. (Também poderia ser escrito, sem tirar vantagem do colchete, como phone_book.item ("JILL SMITH"):= New_person
.
Observação: A lista de argumentos do atribuídor de um está restrita a: (tipo de retorno de um;toda a lista de argumentos de um...)
Propriedades lexicais e de sintaxe
Eiffel não diferencia maiúsculas de minúsculas. Os tokens make
, maKe
e MAKE
denotam o mesmo identificador. Veja, no entanto, as "regras de estilo" abaixo.
Os comentários são introduzidos por --
(dois traços consecutivos) e se estendem até o final da linha.
O ponto e vírgula, como separador de instrução, é opcional. Na maioria das vezes, o ponto e vírgula é omitido, exceto para separar várias instruções em uma linha. Isso resulta em menos confusão na página do programa.
Não há aninhamento de declarações de recurso e classe. Como resultado, a estrutura de uma classe Eiffel é simples: algumas cláusulas de nível de classe (herança, invariante) e uma sucessão de declarações de características, todas no mesmo nível.
É comum agrupar recursos em "cláusulas de recursos" para maior legibilidade, com um conjunto padrão de tags de recursos básicos aparecendo em uma ordem padrão, por exemplo:
classe QUADRO Não.ELEMENTO, KEY - Sim. QUADRO] herda QUADRO Não.ELEMENTO] recurso -- Inicialização -... Declarações de comandos de inicialização (procedimentos de criação/construtores)... recurso -- Acesso -... Declarações de consultas não-boolean no estado do objeto, por exemplo item... recurso - Relatório de estado -... Declarações de consultas booleanas no estado do objeto, por exemplo, is_empty... recurso - Mudança de elementos -... Declarações de comandos que mudam a estrutura, por exemplo... -- etc.fim
Em contraste com a maioria das linguagens de programação de colchetes, Eiffel faz uma clara distinção entre expressões e instruções. Isso está de acordo com o princípio de Separação Comando-Consulta do método Eiffel.
Convenções de estilo
Grande parte da documentação do Eiffel usa convenções de estilo distintas, projetadas para impor uma aparência consistente. Algumas dessas convenções se aplicam ao próprio formato do código e outras à renderização tipográfica padrão do código Eiffel em formatos e publicações onde essas convenções são possíveis.
Embora a linguagem não diferencie maiúsculas de minúsculas, os padrões de estilo prescrevem o uso de letras maiúsculas para nomes de classes (LIST
), letras minúsculas para nomes de recursos (make
) e maiúsculas iniciais para constantes (Avogadro
). O estilo recomendado também sugere sublinhado para separar os componentes de um identificador de várias palavras, como em average_temperature
.
A especificação do Eiffel inclui diretrizes para a exibição de textos de software em formatos tipográficos: palavras-chave em negrito, identificadores e constantes definidos pelo usuário são mostrados em itálico
, comentários, operadores, e sinais de pontuação em romano
, com o texto do programa em azul
como no presente artigo para distingui-lo do texto explicativo. Por exemplo, a mensagem "Olá, mundo!" programa dado acima seria renderizado como abaixo na documentação Eiffel:
classe Olá.criar fazerrecurso fazer do impressão ("Olá, mundo!") fimfim
Interfaces para outras ferramentas e linguagens
Eiffel é uma linguagem puramente orientada a objetos, mas fornece uma arquitetura aberta para interface com aplicativos "externos" software em qualquer outra linguagem de programação.
É possível, por exemplo, programar operações em nível de máquina e sistema operacional em C. Eiffel fornece uma interface direta para rotinas C, incluindo suporte para "inline C" (escrevendo o corpo de uma rotina Eiffel em C, normalmente para operações curtas em nível de máquina).
Embora não haja conexão direta entre Eiffel e C, muitos compiladores Eiffel (Visual Eiffel é uma exceção) produzem código-fonte C como uma linguagem intermediária, para submeter a um compilador C, para otimização e portabilidade. Como tal, eles são exemplos de transcompiladores. O tecomp do Compilador Eiffel pode executar o código Eiffel diretamente (como um interpretador) sem passar por um código C intermediário ou emitir código C que será passado para um compilador C para obter código nativo otimizado. On.NET, o compilador EiffelStudio gera diretamente o código CIL (Common Intermediate Language). O compilador SmartEiffel também pode produzir bytecode Java.