Eiffel es un lenguaje de programación orientado a objetos diseñado por Bertrand Meyer (un defensor de la orientación a objetos y autor de Object-Oriented Software Construction ) y Eiffel Software. Meyer concibió el lenguaje en 1985 con el objetivo de aumentar la confiabilidad del desarrollo de software comercial; [4] la primera versión estuvo disponible en 1986. En 2005, Eiffel se convirtió en un lenguaje estandarizado ISO .
El diseño del lenguaje está estrechamente relacionado con el método de programación Eiffel. Ambos se basan en un conjunto de principios, que incluyen el diseño por contrato , la separación comando-consulta , el principio de acceso uniforme , el principio de elección única , el principio abierto-cerrado y la separación opción-operando .
Muchos conceptos inicialmente introducidos por Eiffel llegaron más tarde a Java , C# y otros lenguajes. [5] Nuevas ideas de diseño de lenguaje, particularmente a través del proceso de estandarización Ecma / ISO , continúan incorporándose al lenguaje Eiffel.
Las características clave del lenguaje Eiffel incluyen:
Eiffel enfatiza las declaraciones declarativas sobre el código procesal e intenta eliminar la necesidad de instrucciones contables.
Eiffel evita trucos de codificación o técnicas de codificación que pretenden ser sugerencias de optimización para el compilador. El objetivo no es sólo hacer que el código sea más legible, sino también permitir a los programadores concentrarse en los aspectos importantes de un programa sin atascarse en los detalles de implementación. La simplicidad de Eiffel tiene como objetivo promover respuestas simples, extensibles, reutilizables y confiables a los problemas informáticos. Los compiladores de programas informáticos escritos en Eiffel proporcionan amplias técnicas de optimización, como la inserción automática, que alivian al programador de parte de la carga de optimización.
Eiffel fue desarrollado originalmente por Eiffel Software, una empresa fundada por Bertrand Meyer . La construcción de software orientada a objetos contiene un tratamiento detallado de los conceptos y la teoría de la tecnología de objetos que llevaron al diseño de Eiffel. [9]
El objetivo de diseño detrás del lenguaje, las bibliotecas y los métodos de programación de Eiffel es permitir a los programadores crear módulos de software confiables y reutilizables. Eiffel admite herencia múltiple , genericidad , polimorfismo , encapsulación , conversiones de tipo seguro y covarianza de parámetros . La contribución más importante de Eiffel a la ingeniería de software es el diseño por contrato (DbC), en el que se emplean aserciones , condiciones previas , condiciones posteriores e invariantes de clase para ayudar a garantizar la corrección del programa sin sacrificar la eficiencia.
El diseño de Eiffel se basa en la teoría de la programación orientada a objetos, con sólo una pequeña influencia de otros paradigmas o preocupación por el soporte del código heredado. Eiffel admite formalmente tipos de datos abstractos . Según el diseño de Eiffel, un texto de software debería poder reproducir su documentación de diseño a partir del texto mismo, utilizando una implementación formalizada del "Tipo de datos abstractos".
EiffelStudio es un entorno de desarrollo integrado disponible bajo licencia comercial o de código abierto . Ofrece un entorno orientado a objetos para la ingeniería de software . EiffelEnvision es un complemento para Microsoft Visual Studio que permite a los usuarios editar, compilar y depurar proyectos Eiffel desde el IDE de Microsoft Visual Studio. Hay otras cinco implementaciones de código abierto disponibles: tecomp "The Eiffel Compiler"; Gobo Eiffel; SmartEiffel , la implementación de GNU, basada en una versión anterior del lenguaje; LibertyEiffel , basado en el compilador SmartEiffel; y Visual Eiffel .
Varios otros lenguajes de programación incorporan elementos introducidos por primera vez en Eiffel. Sather , por ejemplo, se basó originalmente en Eiffel, pero desde entonces se ha diversificado y ahora incluye varias características de programación funcional . El lenguaje de enseñanza interactiva Blue, precursor de BlueJ , también tiene su sede en Eiffel. Apple Media Tool incluye un Apple Media Language basado en Eiffel.
La definición del lenguaje Eiffel es un estándar internacional de la ISO . El estándar fue desarrollado por ECMA International , que aprobó el estándar por primera vez el 21 de junio de 2005 como Estándar ECMA-367, Eiffel: Análisis, Diseño y Lenguaje de Programación. En junio de 2006, ECMA e ISO adoptaron la segunda versión. En noviembre de 2006, ISO publicó por primera vez esa versión. El estándar se puede encontrar y utilizar de forma gratuita en el sitio de ECMA. [10] La versión ISO [11] es idéntica en todos los aspectos excepto en el formato.
Eiffel Software, tecomp "The Eiffel Compiler" y Gobo, desarrollador de la biblioteca Eiffel, se han comprometido a implementar el estándar; EiffelStudio 6.1 de Eiffel Software y el tecomp "The Eiffel Compiler" implementan algunos de los principales mecanismos nuevos, en particular, agentes en línea, comandos de asignadores, notación entre corchetes, herencia no conforme y tipos adjuntos. El equipo de SmartEiffel se ha alejado de este estándar para crear su propia versión del lenguaje, que creen que se acerca más al estilo original de Eiffel. Object Tools no ha revelado si las versiones futuras de su compilador Eiffel cumplirán con el estándar. LibertyEiffel implementa un dialecto entre el lenguaje SmartEiffel y el estándar.
La norma cita las siguientes especificaciones predecesoras del lenguaje Eiffel:
La versión actual del estándar de junio de 2006 contiene algunas inconsistencias (por ejemplo, redefiniciones covariantes) [ cita necesaria ] . El comité ECMA aún no ha anunciado ningún cronograma ni dirección sobre cómo resolver las inconsistencias.
Un "sistema" o "programa" Eiffel es una colección de clases . Por encima del nivel de clases, Eiffel define cluster , que es esencialmente un grupo de clases y posiblemente de subclusters (clusters anidados). Los grupos no son una construcción del lenguaje sintáctico , sino más bien una convención organizativa estándar. Normalmente, un programa Eiffel se organizará con cada clase en un archivo separado y cada grupo en un directorio que contiene archivos de clase. En esta organización, los subclústeres son subdirectorios. Por ejemplo, según las convenciones estándar de organización y mayúsculas, x.e
podría ser el nombre de un archivo que define una clase llamada X.
Una clase contiene características que son similares a "rutinas", "miembros", "atributos" o "métodos" en otros lenguajes de programación orientados a objetos. Una clase también define sus invariantes y contiene otras propiedades, como una sección de "notas" para documentación y metadatos. Los tipos de datos estándar de Eiffel, como y INTEGER
, son en sí mismos clases.STRING
ARRAY
Cada sistema debe tener una clase designada como "raíz", con uno de sus procedimientos de creación designado como "procedimiento raíz". Ejecutar un sistema consiste en crear una instancia de la clase raíz y ejecutar su procedimiento raíz. Generalmente, al hacerlo se crean nuevos objetos, se invocan nuevas funciones, etc.
Eiffel tiene cinco instrucciones ejecutables básicas: asignación, creación de objetos, llamada a rutina, condición e iteración. Las estructuras de control de Eiffel son estrictas a la hora de hacer cumplir la programación estructurada : cada bloque tiene exactamente una entrada y exactamente una salida.
A diferencia de muchos lenguajes orientados a objetos, pero al igual que Smalltalk , Eiffel no permite ninguna asignación a atributos de objetos, excepto dentro de las características de un objeto, que es la aplicación práctica del principio de ocultación de información o abstracción de datos, que requiere interfaces formales para los datos. mutación. Para ponerlo en el lenguaje de otros lenguajes de programación orientados a objetos, todos los atributos de Eiffel están "protegidos" y se necesitan "definidores" para que los objetos del cliente modifiquen los valores. Un resultado de esto es que los "definidores" pueden implementar, y normalmente lo hacen, las invariantes para las cuales Eiffel proporciona sintaxis.
Si bien Eiffel no permite el acceso directo a las características de una clase por parte de un cliente de la clase, sí permite la definición de un "comando de asignador", como por ejemplo:
some_attribute : SOME_TYPE asignar set_some_attribute set_some_attribute ( v : VALUE_TYPE ) - Establece el valor de some_attribute en `v'. hacer algún_atributo : = v final
Si bien es una ligera reverencia a la comunidad de desarrolladores en general para permitir algo que parezca un acceso directo (por ejemplo, rompiendo así el principio de ocultación de información), la práctica es peligrosa ya que oculta u ofusca la realidad de que se está utilizando un "configurador". En la práctica, es mejor redirigir la llamada a un configurador en lugar de implicar un acceso directo a una función como some_attribute
en el código de ejemplo anterior. [ cita necesaria ]
A diferencia de otros lenguajes, que tienen nociones de "público", "protegido", "privado", etc., Eiffel utiliza una tecnología de exportación para controlar con mayor precisión el alcance entre las clases de clientes y proveedores. La visibilidad de las funciones se verifica estáticamente en tiempo de compilación. Por ejemplo, (a continuación), "{NONE}" es similar a "protegido" en otros idiomas. El alcance aplicado de esta manera a un "conjunto de características" (por ejemplo, todo lo que está debajo de la palabra clave 'característica' hasta la siguiente palabra clave del conjunto de características o el final de la clase) se puede cambiar en clases descendientes usando la palabra clave "exportar".
característica { NONE } - Inicialización default_create - Inicializa una nueva instancia decimal "cero". hacer make_zero final
Alternativamente, la falta de una declaración de exportación {x} implica {ANY} y es similar al alcance "público" de otros idiomas.
característica - Constantes
Finalmente, el alcance se puede controlar de manera selectiva y precisa para cualquier clase en el universo del proyecto Eiffel, como por ejemplo:
característica { DECIMAL , DCM_MA_DECIMAL_PARSER , DCM_MA_DECIMAL_HANDLER } - Acceso
Aquí, el compilador permitirá que solo las clases enumeradas entre llaves accedan a las funciones dentro del grupo de funciones (por ejemplo, DECIMAL, DCM_MA_DECIMAL_PARSER, DCM_MA_DECIMAL_HANDLER ).
La apariencia de un lenguaje de programación a menudo se transmite mediante un "¡Hola, mundo!" programa. Un programa escrito en Eiffel podría ser:
clase HELLO_WORLD crear hacer característica hacer imprimir ( "¡ Hola , mundo!%N" ) fin fin
Este programa contiene la clase HELLO_WORLD
. El constructor (crear rutina) para la clase, llamado make
, invoca la print
rutina de la biblioteca del sistema para escribir un "Hello,
world!"
mensaje en la salida.
El concepto de Diseño por Contrato es central para Eiffel. Los contratos afirman lo que debe ser cierto antes de que se ejecute una rutina (condición previa) y lo que debe ser verdadero después de que finalice la rutina (condición posterior). Los contratos de clase invariante definen qué afirmaciones deben ser verdaderas antes y después de que se acceda a cualquier característica de una clase (tanto rutinas como atributos). Además, los contratos codifican en código ejecutable las suposiciones de los desarrolladores y diseñadores sobre el entorno operativo de las características de una clase o de la clase en su conjunto mediante el invariante.
El compilador Eiffel está diseñado para incluir contratos de características y clases en varios niveles. EiffelStudio, por ejemplo, ejecuta todos los contratos de características y clases durante la ejecución en el "modo Workbench". Cuando se crea un ejecutable, el compilador recibe instrucciones a través del archivo de configuración del proyecto (por ejemplo, archivo ECF) para incluir o excluir cualquier conjunto de contratos. Por lo tanto, se puede compilar un archivo ejecutable para incluir o excluir cualquier nivel de contrato, aportando así niveles continuos de pruebas unitarias y de integración. Además, los contratos se pueden ejercer de forma continua y metódica mediante la función de prueba automática que se encuentra en EiffelStudio.
Los mecanismos de Diseño por contrato están estrechamente integrados con el lenguaje y guían la redefinición de características en herencia:
Además, el lenguaje admite una "instrucción de verificación" (una especie de "afirmación"), invariantes de bucle y variantes de bucle (que garantizan la terminación del bucle).
La capacidad de protección contra vacíos, como la escritura estática, es otra posibilidad para mejorar la calidad del software. El software a prueba de anulación está protegido contra errores de tiempo de ejecución causados por llamadas para anular referencias y, por lo tanto, será más confiable que el software en el que pueden ocurrir llamadas para anular objetivos. La analogía con la escritura estática es útil. De hecho, la capacidad de seguridad contra vacíos podría verse como una extensión del sistema tipográfico, o un paso más allá de la tipificación estática, porque el mecanismo para garantizar la seguridad contra vacíos está integrado en el sistema tipográfico.
La protección contra las llamadas de objetivos vacíos puede verse a través de la noción de apego y (por extensión) desapego (por ejemplo, palabra clave desmontable). La función de seguridad contra vacíos se puede ver en una breve revisión del código de ejemplo usado anteriormente:
some_attribute : desmontable SOME_TYPE use_some_attribute - Establece el valor de some_attribute en `v'. hacer si se adjunta algún_atributo como l_attribute entonces hacer_algo ( l_attribute ) end end hacer_algo ( a_value : SOME_TYPE ) - Hacer algo con `a_value'. hacer ... haciendo algo con ` a_value ' ... fin
El ejemplo de código anterior muestra cómo el compilador puede abordar estáticamente la confiabilidad de si some_attribute
se adjuntará o desconectará en el punto en que se usa. En particular, la attached
palabra clave permite un "adjunto local" (p. ej. l_attribute
), que tiene como alcance únicamente el bloque de código encerrado por la construcción de declaración if. Por lo tanto, dentro de este pequeño bloque de código, l_attribute
se puede garantizar estáticamente que la variable local (por ejemplo, ) no será nula (es decir, segura para nulos).
La característica principal de una clase es que define un conjunto de características: como una clase representa un conjunto de objetos en tiempo de ejecución, o "instancias", una característica es una operación sobre estos objetos. Hay dos tipos de funciones: consultas y comandos. Una consulta proporciona información sobre una instancia. Un comando modifica una instancia.
La distinción comando-consulta es importante para el método Eiffel. En particular:
a_vehicle.speed
podría ser un atributo al que se accede en el objeto a_vehicle
o podría calcularse mediante una función que divida la distancia por el tiempo. La notación es la misma en ambos casos, por lo que es fácil cambiar la implementación de la clase sin afectar el software del cliente.Eiffel no permite la sobrecarga de argumentos . Cada nombre de característica dentro de una clase siempre se asigna a una característica específica dentro de la clase. Un nombre, dentro de una clase, significa una cosa. Esta elección de diseño ayuda a la legibilidad de las clases, al evitar una causa de ambigüedad sobre qué rutina será invocada por una llamada. También simplifica el mecanismo del lenguaje; en particular, esto es lo que hace posible el mecanismo de herencia múltiple de Eiffel. [12]
Por supuesto, los nombres se pueden reutilizar en diferentes clases. Por ejemplo, la característica plus (junto con su alias infijo "+" ) se define en varias clases: INTEGER , REAL , STRING , etc.
Una clase genérica es una clase que varía según el tipo (por ejemplo, LISTA [TELÉFONO], una lista de números de teléfono; CUENTA [G->TIPO_CUENTA], que permite CUENTA [AHORROS] y CUENTA [CHEQUES], etc.). Las clases pueden ser genéricas, para expresar que están parametrizadas por tipos. Los parámetros genéricos aparecen entre corchetes:
clase LISTA [ G ] ...
G se conoce como "parámetro genérico formal". (Eiffel reserva "argumento" para rutinas y usa "parámetro" sólo para clases genéricas). Con tal declaración, G representa dentro de la clase un tipo arbitrario; entonces una función puede devolver un valor de tipo G, y una rutina puede tomar un argumento de ese tipo:
elemento : G hacer ... fin poner ( x : G ) hacer ... fin
Los LIST [INTEGER]
y LIST [WORD]
son "derivaciones genéricas" de esta clase. Las combinaciones permitidas (con n: INTEGER
, w: WORD
, il: LIST [INTEGER]
, wl: LIST [WORD]
) son:
n := il . artículo wl . poner ( w )
INTEGER
y WORD
son los "parámetros genéricos reales" en estas derivaciones genéricas.
También es posible tener parámetros formales 'restringidos', para los cuales el parámetro real debe heredar de una clase determinada, la "restricción". Por ejemplo, en
clase HASH_TABLE [ G , LLAVE -> HASHABLE ]
una derivación HASH_TABLE [INTEGER, STRING]
es válida sólo si STRING
se hereda de HASHABLE
(como ocurre en las bibliotecas típicas de Eiffel). Dentro de la clase, habiendo KEY
restringido por HASHABLE
medio de que x: KEY
es posible aplicar a x
todas las características de HASHABLE
, como en x.hash_code
.
Para heredar de una o más otras, una clase incluirá una inherit
cláusula al principio:
clase C hereda A B -- ... Resto de declaración de clase ...
La clase puede redefinir (anular) algunas o todas las características heredadas. Esto debe anunciarse explícitamente al comienzo de la clase a través de una redefine
subcláusula de la cláusula de herencia, como en
clase C heredar A redefinir f , g , h end B redefinir u , v end
Véase [13] para una discusión completa sobre la herencia Eiffel.
Las clases se pueden definir con deferred class
en lugar de con class
para indicar que no se puede crear una instancia directa de la clase. Las clases no instanciables se denominan clases abstractas en algunos otros lenguajes de programación orientados a objetos. En el lenguaje de Eiffel, sólo se puede instanciar una clase "efectiva" (puede ser descendiente de una clase diferida). Una característica también se puede aplazar utilizando la deferred
palabra clave en lugar de una do
cláusula. Si una clase tiene alguna característica diferida debe declararse como diferida; sin embargo, una clase sin características diferidas puede, no obstante, ser diferida.
Las clases diferidas desempeñan aproximadamente el mismo papel que las interfaces en lenguajes como Java, aunque muchos teóricos de la programación orientada a objetos creen que las interfaces en sí mismas son en gran medida una respuesta a la falta de herencia múltiple de Java (que tiene Eiffel). [14] [15]
Una clase que hereda de una o más otras obtiene todas sus características, de forma predeterminada, con sus nombres originales. Podrá, sin embargo, cambiar sus denominaciones mediante rename
cláusulas. Esto es necesario en el caso de herencia múltiple si hay conflictos de nombres entre características heredadas; sin cambiar el nombre, la clase resultante violaría el principio de no sobrecarga mencionado anteriormente y, por lo tanto, no sería válida.
Los tipos de tuplas pueden verse como una forma simple de clase, que proporciona sólo atributos y el correspondiente procedimiento "establecedor". Un tipo de tupla típico lee
TUPLE [ nombre : STRING ; peso : REAL ; fecha : FECHA ]
y podría usarse para describir una noción simple de acta de nacimiento si no se necesita una clase. Una instancia de dicha tupla es simplemente una secuencia de valores con los tipos dados, entre paréntesis, como
[ "Brigitte" , 3.5 , Anoche ]
Se puede acceder a los componentes de dicha tupla como si las etiquetas de la tupla fueran atributos de una clase; por ejemplo, si se le t
ha asignado la tupla anterior, entonces t.weight
tiene el valor 3,5.
Gracias a la noción de comando de asignación (ver más abajo), la notación de puntos también se puede utilizar para asignar componentes de dicha tupla, como en
t . peso := t . peso + 0,5
Las etiquetas de tupla son opcionales, por lo que también es posible escribir un tipo de tupla como TUPLE [STRING, REAL, DATE]
. (En algunos compiladores esta es la única forma de tupla, ya que las etiquetas se introdujeron con el estándar ECMA).
La especificación precisa de eg TUPLE [A, B, C]
es que describe secuencias de al menos tres elementos, siendo los tres primeros de tipo A
, B
, C
respectivamente. Como resultado, TUPLE [A, B, C]
se ajusta a (se puede asignar a) TUPLE [A, B]
, a TUPLE [A]
y a TUPLE
(sin parámetros), el tipo de tupla superior al que se ajustan todos los tipos de tupla.
El mecanismo de "agente" de Eiffel envuelve las operaciones en objetos. Este mecanismo se puede utilizar para iteración, programación basada en eventos y otros contextos en los que sea útil pasar operaciones alrededor de la estructura del programa. Otros lenguajes de programación, especialmente aquellos que enfatizan la programación funcional , permiten un patrón similar usando continuaciones , cierres o generadores ; Los agentes de Eiffel enfatizan el paradigma orientado a objetos del lenguaje y usan una sintaxis y semántica similar a los bloques de código en Smalltalk y Ruby .
Por ejemplo, para ejecutar el my_action
bloque para cada elemento de my_list
, se escribiría:
mi lista . do_all ( agente mi_acción )
Para ejecutar my_action
solo en elementos que cumplan my_condition
, se puede agregar una limitación/filtro:
mi lista . do_if ( agente mi_acción , agente mi_condición )
En estos ejemplos, my_action
y my_condition
son rutinas. Al anteponerlos agent
se obtiene un objeto que representa la rutina correspondiente con todas sus propiedades, en particular la capacidad de ser llamado con los argumentos apropiados. Entonces, si a
representa ese objeto (por ejemplo, porque a
es el argumento de do_all
), la instrucción
a . llamar ( [ x ] )
llamará a la rutina original con el argumento x
, como si hubiésemos llamado directamente a la rutina original: my_action (x)
. Los argumentos se call
pasan como una tupla, aquí [x]
.
Es posible mantener abiertos algunos argumentos para un agente y cerrar otros . Los argumentos abiertos se pasan como argumentos a call
: se proporcionan en el momento del uso del agente . Los argumentos cerrados se proporcionan en el momento de la definición del agente . Por ejemplo, si action2
tiene dos argumentos, la iteración
mi lista . do_all ( agente acción2 ( ? , y ))
itera action2 (x, y)
para valores sucesivos de x
, donde el segundo argumento permanece establecido en y
. El signo de interrogación ?
indica un argumento abierto; y
es un argumento cerrado del agente. Tenga en cuenta que la sintaxis básica agent f
es una abreviatura de agent f (?, ?, ...)
con todos los argumentos abiertos. También es posible hacer que el objetivo de un agente se abra mediante la notación {T}?
donde T
está el tipo de objetivo.
La distinción entre operandos abiertos y cerrados (operandos = argumentos + objetivo) corresponde a la distinción entre variables ligadas y libres en el cálculo lambda . Una expresión de agente, como por ejemplo action2 (?, y)
con algunos operandos cerrados y otros abiertos, corresponde a una versión de la operación original aplicada a los operandos cerrados.
El mecanismo del agente también permite definir un agente sin referencia a una rutina existente (como my_action
, my_condition
, action2
), a través de agentes en línea como en
mi lista . do_all ( agente ( s : STRING ) requiere not_void : s /= Void do s . append_character ( ',' ) asegúrese de agregar : s . count = old s . count + 1 end )
El agente en línea pasado aquí puede tener todos los elementos de una rutina normal, incluidas condiciones previas, condiciones posteriores, cláusula de rescate (no utilizada aquí) y una firma completa. Esto evita definir rutinas cuando todo lo que se necesita es un cálculo que se incluirá en un agente. Esto es útil en particular para contratos, como en una cláusula invariante que expresa que todos los elementos de una lista son positivos:
mi lista . for_all ( agente ( x : INTEGER ): BOOLEAN do Resultado : = ( x > 0 ) end )
El mecanismo del agente actual deja una posibilidad de error de tipo en tiempo de ejecución (si se pasa una rutina con n argumentos a un agente que espera m argumentos con m < n ). Esto se puede evitar mediante una verificación en tiempo de ejecución mediante la condición previa valid_arguments
de call
. Se encuentran disponibles varias propuestas para una corrección puramente estática de este problema, incluida una propuesta de cambio de idioma de Ribet et al. [dieciséis]
El resultado de una rutina se puede almacenar en caché utilizando la once
palabra clave en lugar de do
. Las llamadas que no son las primeras a una rutina no requieren ningún cálculo adicional ni asignación de recursos, sino que simplemente devuelven un resultado calculado previamente. Un patrón común para las "funciones únicas" es proporcionar objetos compartidos; la primera llamada creará el objeto, las siguientes devolverán la referencia a ese objeto. El esquema típico es:
objeto_compartido : SOME_TYPE una vez creado el resultado . make ( args ) : esto crea el objeto y devuelve una referencia a él a través de "Resultado". fin
El objeto devuelto ( Result
en el ejemplo) puede ser mutable, pero su referencia sigue siendo la misma.
A menudo, las "rutinas únicas" realizan una inicialización requerida: varias llamadas a una biblioteca pueden incluir una llamada al procedimiento de inicialización, pero solo la primera llamada realizará las acciones requeridas. Con este patrón, la inicialización se puede descentralizar, evitando la necesidad de un módulo de inicialización especial. Las "rutinas Once" son similares en propósito y efecto al patrón singleton en muchos lenguajes de programación y al patrón Borg utilizado en Python.
De forma predeterminada, una "rutina única" se llama una vez por subproceso . La semántica se puede ajustar a una vez por proceso o una vez por objeto calificándola con una "clave única", por ejemplo once ("PROCESS")
.
Eiffel proporciona un mecanismo para permitir conversiones entre varios tipos. El mecanismo coexiste con la herencia y la complementa. Para evitar cualquier confusión entre los dos mecanismos, el diseño aplica el siguiente principio:
Por ejemplo, NEWSPAPER
puede ajustarse a PUBLICATION
, pero INTEGER
se convierte a REAL
(y no hereda de él).
El mecanismo de conversión simplemente generaliza las reglas de conversión ad hoc (como entre INTEGER
y REAL
) que existen en la mayoría de los lenguajes de programación, haciéndolas aplicables a cualquier tipo siempre que se respete el principio anterior. Por ejemplo, DATE
se puede declarar una clase para convertirla en STRING
; esto hace posible crear una cadena a partir de una fecha simplemente a través de
mi_cadena := mi_fecha
como un atajo para usar una creación explícita de objetos con un procedimiento de conversión:
crear mi_cadena . make_from_date ( mi_fecha )
Para que la primera forma sea posible como sinónimo de la segunda, basta con enumerar el procedimiento de creación (constructor) make_from_date
en una convert
cláusula al comienzo de la clase.
Como otro ejemplo, si existe un procedimiento de conversión de este tipo en la lista TUPLE [day: INTEGER; month: STRING; year: INTEGER]
, entonces se puede asignar directamente una tupla a una fecha, provocando la conversión adecuada, como en
Día_de la Bastilla := [ 14 , "julio" , 1789 ]
El manejo de excepciones en Eiffel se basa en los principios del diseño por contrato. Por ejemplo, se produce una excepción cuando el llamador de una rutina no cumple una condición previa o cuando una rutina no puede garantizar una condición posterior prometida. En Eiffel, el manejo de excepciones no se utiliza para controlar el flujo ni para corregir errores en la entrada de datos.
Un controlador de excepciones Eiffel se define utilizando la palabra clave de rescate . Dentro de la sección de rescate , la palabra clave reintentar ejecuta la rutina nuevamente. Por ejemplo, la siguiente rutina rastrea la cantidad de intentos de ejecutar la rutina y solo reintenta una cierta cantidad de veces:
connect_to_server ( servidor : SOCKET ) : conéctese a un servidor o abandone después de 10 intentos. require server /= Void y luego server . dirección /= Intentos locales nulos : INTEGER do server . conectar asegúrese de estar conectado : servidor . rescate is_connected si intentos < 10 entonces intentos : = intentos + 1 reintento fin fin
Sin embargo, se puede decir que este ejemplo es defectuoso para cualquier cosa que no sean los programas más simples, porque es de esperar que se produzcan fallos en la conexión. Para la mayoría de los programas, un nombre de rutina como intento_conexión_al_servidor sería mejor, y la poscondición no prometería una conexión, dejando a la persona que llama tomar las medidas apropiadas si la conexión no se abre.
Se encuentran disponibles varias bibliotecas de redes y subprocesos, como EiffelNet y EiffelThreads. Un modelo de concurrencia para Eiffel, basado en los conceptos de diseño por contrato, es SCOOP , o Programación Orientada a Objetos Concurrente Simple , que aún no forma parte de la definición del lenguaje oficial pero está disponible en EiffelStudio . CAMEO [17] es una variación (no implementada) de SCOOP para Eiffel. La concurrencia también interactúa con las excepciones. Las excepciones asincrónicas pueden ser problemáticas (donde una rutina genera una excepción después de que el autor de la llamada haya terminado). [18]
La visión de Eiffel sobre la computación está completamente orientada a objetos en el sentido de que cada operación es relativa a un objeto, el "objetivo". Así, por ejemplo, una adición como
a + b
se entiende conceptualmente como si fuera la llamada al método
a . más ( segundo )
con objetivo a
, característica plus
y argumento b
.
Por supuesto, la primera es la sintaxis convencional y normalmente se prefiere. La sintaxis del operador permite utilizar cualquiera de las formas declarando la característica (por ejemplo en INTEGER
, pero esto se aplica a otras clases básicas y se puede utilizar en cualquier otra para la que dicho operador sea apropiado):
más alias "+" ( otro : INTEGER ): INTEGER -- ... Declaración de función normal... fin
La gama de operadores que se pueden utilizar como "alias" es bastante amplia; incluyen operadores predefinidos como "+", pero también "operadores libres" compuestos de símbolos no alfanuméricos. Esto hace posible diseñar notaciones especiales de infijos y prefijos, por ejemplo en aplicaciones de matemáticas y física.
Cada clase puede tener además una función con el alias "[]", el operador "corchete", permitiendo la notación a [i, ...]
como sinónimo de a.f (i, ...)
dónde f
está la función elegida. Esto es particularmente útil para estructuras de contenedores como matrices, tablas hash , listas, etc. Por ejemplo, se puede escribir el acceso a un elemento de una tabla hash con claves de cadena.
número := directorio telefónico [ "JILL SMITH" ]
Los "comandos del asignador" son un mecanismo complementario diseñado con el mismo espíritu de permitir una notación conveniente y bien establecida reinterpretada en el marco de la programación orientada a objetos. Los comandos del asignador permiten una sintaxis similar a la de una asignación para llamar a procedimientos "establecedores". Una asignación propiamente dicha nunca puede ser de la forma a.x := v
ya que esto viola el ocultamiento de información; tienes que buscar un comando de establecimiento (procedimiento). Por ejemplo, la clase de tabla hash puede tener la función y el procedimiento
alias del elemento "[]" ( clave : STRING ): ELEMENTO [ 3 ] - El elemento de la clave 'clave'. -- (consulta "Getter") hacer ... finalizar put ( e : ELEMENT ; key : STRING ) - Inserta el elemento `e', asociándolo con la clave `key'. -- (comando "Setter") hacer ... finalizar
Luego, para insertar un elemento, debe usar una llamada explícita al comando de configuración:
[ 4 ] directorio telefónico . poner ( Nueva_persona , "JILL SMITH" )
Es posible escribir esto de manera equivalente como
[ 5 ] agenda_telefónica [ "JILL SMITH" ] := Nueva_persona
(de la misma manera que phone_book ["JILL SMITH"]
es sinónimo de number := phone_book.item ("JILL SMITH")
), siempre que la declaración de item
now comience (reemplazo de [3]) con
alias del elemento "[]" ( clave : CADENA ): ELEMENTO asignar poner
Esto se declara put
como el comando del asignador asociado item
y, combinado con el alias del corchete, hace que [5] sea legal y equivalente a [4]. (También se podría escribir, sin aprovechar el corchete, como phone_book.item ("JILL SMITH") := New_person
.
Nota: La lista de argumentos del asignador de a está restringida a ser: (el tipo de retorno de a; toda la lista de argumentos de a...)
Eiffel no distingue entre mayúsculas y minúsculas. Los tokens make
y maKe
todos MAKE
denotan el mismo identificador. Consulte, sin embargo, las "reglas de estilo" a continuación.
Los comentarios se introducen con --
(dos guiones consecutivos) y se extienden hasta el final de la línea.
El punto y coma, como separador de instrucciones, es opcional. La mayoría de las veces simplemente se omite el punto y coma, excepto para separar varias instrucciones en una línea. Esto da como resultado menos desorden en la página del programa.
No hay anidamiento de declaraciones de características y clases. Como resultado, la estructura de una clase Eiffel es simple: algunas cláusulas a nivel de clase (herencia, invariante) y una sucesión de declaraciones de características, todas al mismo nivel.
Es habitual agrupar características en "cláusulas de características" separadas para una mayor legibilidad, con un conjunto estándar de etiquetas de características básicas que aparecen en un orden estándar, por ejemplo:
clase HASH_TABLE [ ELEMENT , KEY -> HASHABLE ] heredar TABLE [ ELEMENT ] característica -- Inicialización -- ... Declaraciones de comandos de inicialización (procedimientos de creación/constructores) ... característica -- Acceso -- ... Declaraciones de consultas no booleanas sobre el estado del objeto, por ejemplo, elemento ... característica - Informe de estado - ... Declaraciones de consultas booleanas sobre el estado del objeto, por ejemplo, is_empty ... característica - Cambio de elemento - ... Declaraciones de comandos que cambian la estructura, por ejemplo, poner ... -- etc. fin
A diferencia de la mayoría de los lenguajes de programación entre llaves , Eiffel hace una clara distinción entre expresiones e instrucciones. Esto está en línea con el principio de separación comando-consulta del método Eiffel.
Gran parte de la documentación de Eiffel utiliza convenciones de estilo distintivas, diseñadas para imponer una apariencia consistente. Algunas de estas convenciones se aplican al formato del código en sí y otras a la representación tipográfica estándar del código Eiffel en formatos y publicaciones donde estas convenciones son posibles.
Si bien el lenguaje no distingue entre mayúsculas y minúsculas, los estándares de estilo prescriben el uso de mayúsculas para nombres de clases ( LIST
), minúsculas para nombres de características ( make
) y mayúsculas iniciales para constantes ( Avogadro
). El estilo recomendado también sugiere un guión bajo para separar los componentes de un identificador de varias palabras, como en average_temperature
.
La especificación de Eiffel incluye pautas para mostrar textos de software en formatos tipográficos: las palabras clave en negrita, los identificadores y constantes definidos por el usuario se muestran en italics
, los comentarios, los operadores y los signos de puntuación en Roman
, con el texto del programa en blue
como en el presente artículo para distinguirlo de texto explicativo. Por ejemplo, el mensaje "¡Hola mundo!" El programa proporcionado anteriormente se representaría como se muestra a continuación en la documentación de Eiffel:
clase HELLO_WORLD crear hacer característica hacer imprimir ( "¡ Hola , mundo!" ) fin fin
Eiffel es un lenguaje puramente orientado a objetos, pero proporciona una arquitectura abierta para interactuar con software "externo" en cualquier otro lenguaje de programación.
Es posible, por ejemplo, programar operaciones a nivel de máquina y de sistema operativo en C. Eiffel proporciona una interfaz sencilla para las rutinas C, incluido el soporte para "C en línea" (escribir el cuerpo de una rutina Eiffel en C, generalmente para operaciones cortas a nivel de máquina).
Aunque no existe una conexión directa entre Eiffel y C, muchos compiladores de Eiffel ( Visual Eiffel es una excepción) generan código fuente de C como lenguaje intermedio , para enviarlo a un compilador de C, para su optimización y portabilidad . Como tales, son ejemplos de transcompiladores . El compilador Eiffel tecomp puede ejecutar código Eiffel directamente (como un intérprete) sin pasar por un código C intermedio o emitir código C que se pasará a un compilador C para obtener código nativo optimizado. En .NET, el compilador EiffelStudio genera directamente código CIL (Lenguaje Intermedio Común). El compilador SmartEiffel también puede generar código de bytes de Java .
En menor medida, Python, LISP, Eiffel, Ada y C++ también han influido en Ruby.