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 fiabilidad del desarrollo de software comercial; [4] la primera versión estuvo disponible en 1986. En 2005, Eiffel se convirtió en un lenguaje estandarizado por la 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, entre los que se incluyen el diseño por contrato , la separación entre comandos y consultas , el principio de acceso uniforme , el principio de elección única , el principio de apertura y cierre y la separación entre opciones y operandos .
Muchos conceptos introducidos inicialmente por Eiffel encontraron luego su camino hacia Java , C# y otros lenguajes. [5] Nuevas ideas de diseño de lenguajes, 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 de procedimiento e intenta eliminar la necesidad de instrucciones de contabilidad.
Eiffel evita trucos de codificación o técnicas de codificación que pretendan ser sugerencias de optimización para el compilador. El objetivo no es sólo hacer el código más legible, sino también permitir a los programadores concentrarse en los aspectos importantes de un programa sin empantanarse en 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 para programas de computadora escritos en Eiffel proporcionan técnicas de optimización extensas, como la inserción automática en línea, que liberan al programador de parte de la carga de optimización.
Eiffel fue desarrollado originalmente por Eiffel Software, una empresa fundada por Bertrand Meyer . Object-Oriented Software Construction contiene un tratamiento detallado de los conceptos y la teoría de la tecnología de objetos que condujo 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 tipos seguros 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 afirmaciones , precondiciones , poscondiciones 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 una influencia menor de otros paradigmas o preocupación por el soporte de 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 abstracto".
EiffelStudio es un entorno de desarrollo integrado disponible bajo licencia de código abierto o comercial. 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 de Eiffel desde el IDE de Microsoft Visual Studio. Hay otras cinco implementaciones de código abierto disponibles: "The Eiffel Compiler" tecomp; Gobo Eiffel; SmartEiffel , la implementación de GNU, basada en una versión anterior del lenguaje; LibertyEiffel , basada 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 desviado y ahora incluye varias características de programación funcional . El lenguaje de enseñanza interactiva Blue, precursor de BlueJ , también está basado 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ó por primera vez el estándar 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 usar 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, "The Eiffel Compiler" tecomp y el desarrollador de la biblioteca Eiffel Gobo se han comprometido a implementar el estándar; EiffelStudio 6.1 y "The Eiffel Compiler" tecomp de Eiffel Software implementan algunos de los principales mecanismos nuevos, en particular, agentes en línea, comandos de asignación, notación entre corchetes, herencia no conforme y tipos adjuntos. El equipo 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 futuras versiones de su compilador Eiffel cumplirán con el estándar. LibertyEiffel implementa un dialecto en algún punto intermedio entre el lenguaje SmartEiffel y el estándar.
La norma cita las siguientes especificaciones del lenguaje Eiffel predecesoras:
La versión actual de la norma, de junio de 2006, contiene algunas inconsistencias (por ejemplo, redefiniciones de covariantes) [ cita requerida ] . El comité de la ECMA aún no ha anunciado ningún cronograma ni dirección sobre cómo resolver las inconsistencias.
Un "sistema" o "programa" de 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 clusters no son una construcción sintáctica del lenguaje , sino más bien una convención organizativa estándar. Normalmente, un programa de Eiffel se organizará con cada clase en un archivo separado y cada cluster en un directorio que contiene archivos de clase. En esta organización, los subclusters son subdirectorios. Por ejemplo, según las convenciones organizativas y de mayúsculas y minúsculas estándar, 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 las "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 la documentación y los metadatos. Los tipos de datos estándar de Eiffel, como INTEGER
, STRING
y ARRAY
, son todos ellos clases.
Todo sistema debe tener una clase designada como "raíz", con uno de sus procedimientos de creación designado como "procedimiento raíz". La ejecución de 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 objeto, llamada de rutina, condición e iteración. Las estructuras de control de Eiffel son estrictas en la aplicación de 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 en atributos de objetos, excepto dentro de las características de un objeto, que es la aplicación práctica del principio de ocultamiento de información o abstracción de datos, que requiere interfaces formales para la mutación de datos. 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 "setters" para que los objetos cliente modifiquen los valores. Una consecuencia de esto es que los "setters" pueden, y normalmente lo hacen, implementar las invariantes para las que 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 asignador", como por ejemplo:
some_attribute : SOME_TYPE asignar set_some_attribute set_some_attribute ( v : VALUE_TYPE ) -- Establecer el valor de some_attribute en 'v'. hacer some_attribute := v fin
Si bien es una pequeña concesión a la comunidad de desarrolladores en general al permitir algo que parezca un acceso directo (por ejemplo, violando así el principio de ocultamiento 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 requerida ]
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 cliente y proveedor. La visibilidad de las características se verifica de forma estática en tiempo de compilación. Por ejemplo, (a continuación), "{NONE}" es similar a "protegido" en otros lenguajes. El alcance aplicado de esta manera a un "conjunto de características" (por ejemplo, todo lo que está por debajo de la palabra clave 'feature' hasta la siguiente palabra clave del conjunto de características o el final de la clase) se puede cambiar en las clases descendientes utilizando la palabra clave "export".
feature { NONE } - Inicialización default_create - Inicializa una nueva instancia decimal 'cero'. do make_zero end
Alternativamente, la falta de una declaración de exportación {x} implica {ANY} y es similar al alcance "público" de otros lenguajes.
característica -- Constantes
Finalmente, el alcance se puede controlar de forma 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 características dentro del grupo de características (por ejemplo, DECIMAL, DCM_MA_DECIMAL_PARSER, DCM_MA_DECIMAL_HANDLER ).
La apariencia y el funcionamiento de un lenguaje de programación suelen transmitirse mediante un programa del tipo "¡Hola, mundo!" . Un programa escrito en Eiffel podría ser:
clase HOLA_MUNDO crear hacer función hacer imprimir ( " Hola, mundo!%N" ) fin fin
Este programa contiene la clase HELLO_WORLD
. El constructor (rutina de creación) de 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 verdad antes de que se ejecute una rutina (precondición) y lo que debe seguir siendo verdad después de que finalice la rutina (poscondición). Los contratos de invariantes de clase definen qué afirmaciones deben ser verdaderas tanto antes como 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 por medio de los invariantes.
El compilador Eiffel está diseñado para incluir los contratos de clase y de características en varios niveles. EiffelStudio, por ejemplo, ejecuta todos los contratos de clase y de características 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, el archivo ECF) para incluir o excluir cualquier conjunto de contratos. De este modo, se puede compilar un archivo ejecutable para incluir o excluir cualquier nivel de contrato, lo que permite realizar pruebas unitarias y de integración de forma continua. Además, los contratos se pueden ejecutar 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 la 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 seguridad frente a vacíos, al igual que la tipificación estática, es otra función para mejorar la calidad del software. El software de seguridad frente a vacíos está protegido de errores de tiempo de ejecución causados por llamadas a referencias void y, por lo tanto, será más confiable que el software en el que pueden ocurrir llamadas a destinos void. La analogía con la tipificación estática es útil. De hecho, la capacidad de seguridad frente a vacíos podría verse como una extensión del sistema de tipos, o un paso más allá de la tipificación estática, porque el mecanismo para garantizar la seguridad frente a vacíos está integrado en el sistema de tipos.
La protección contra llamadas de destino nulas se puede ver a través de la noción de conexión y (por extensión) desconexión (por ejemplo, la palabra clave detachable). La función de seguridad frente a nulos se puede ver en una breve reelaboración del código de ejemplo utilizado anteriormente:
some_attribute : desprendible SOME_TYPE use_some_attribute -- Establece el valor de some_attribute en `v'. do si se adjunta some_attribute como l_attribute entonces do_something ( l_attribute ) fin fin do_something ( a_value : SOME_TYPE ) -- Haz algo con `a_value'. do ... haciendo algo con ` a_value ' ... fin
El ejemplo de código anterior muestra cómo el compilador puede abordar de forma estática la fiabilidad de si some_attribute
se adjuntará o separará en el punto en que se utiliza. En particular, la attached
palabra clave permite una "adjunción local" (eg l_attribute
), que tiene como ámbito únicamente el bloque de código incluido en la construcción de la declaración if. Por lo tanto, dentro de este pequeño bloque de código, se puede garantizar de forma estática que la variable local (eg l_attribute
) no sea nula (es decir, que sea 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 características: consultas y comandos. Una consulta proporciona información sobre una instancia. Un comando modifica una instancia.
La distinción entre comandos y consultas 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 divide 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 al 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, LIST [PHONE], una lista de números de teléfono; ACCOUNT [G->ACCOUNT_TYPE], que permite CUENTA [SAVINGS] y CUENTA [CHECKING], 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 un "parámetro genérico formal". (Eiffel reserva el "argumento" para las rutinas y utiliza el "parámetro" sólo para las clases genéricas). Con una declaración de este tipo, G representa dentro de la clase un tipo arbitrario; por lo tanto, 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 . elemento 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 dada, la "restricción". Por ejemplo, en
clase HASH_TABLE [ G , CLAVE -> HASHABLE ]
una derivación HASH_TABLE [INTEGER, STRING]
es válida solo si STRING
hereda de HASHABLE
(como de hecho ocurre en las bibliotecas típicas de Eiffel). Dentro de la clase, haber KEY
restringido por HASHABLE
significa que para 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 clases, una clase incluirá una inherit
cláusula al comienzo:
La clase C hereda A y B -- ... Resto de la 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 mediante una redefine
subcláusula de la cláusula de herencia, como en
clase C hereda A redefine f , g , h fin B redefine u , v fin
Véase [13] para una discusión completa de la herencia de Eiffel.
Las clases se pueden definir con deferred class
en lugar de con class
para indicar que la clase no se puede instanciar directamente. Las clases no instanciables se denominan clases abstractas en algunos otros lenguajes de programación orientados a objetos. En el lenguaje de Eiffel, solo se puede instanciar una clase "efectiva" (puede ser descendiente de una clase diferida). Una característica también se puede diferir 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, diferirse.
Las clases diferidas cumplen algunas de las mismas funciones que las interfaces en lenguajes como Java, aunque muchos teóricos de la programación orientada a objetos creen que las interfaces son en sí mismas en gran medida una respuesta a la falta de herencia múltiple de Java (que Eiffel tiene). [14] [15]
Una clase que hereda de una o más clases obtiene todas sus características, por defecto, con sus nombres originales. Sin embargo, puede cambiar sus nombres mediante rename
cláusulas. Esto es necesario en el caso de herencia múltiple si hay conflictos de nombres entre las características heredadas; sin el cambio de 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 considerarse como una forma simple de clase, que proporciona solo atributos y el procedimiento "establecedor" correspondiente. Un tipo de tupla típico se lee
TUPLE [ nombre : CADENA ; peso : REAL ; fecha : FECHA ]
y podría usarse para describir una noción simple de registro de nacimiento si no se necesita una clase. Una instancia de una tupla de este tipo 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 t
se le ha asignado la tupla anterior, entonces t.weight
tiene el valor 3,5.
Gracias a la noción de comando asignador (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 primeros tres de los tipos A
, B
, C
respectivamente. Como resultado, TUPLE [A, B, C]
se ajusta a (puede asignarse 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 iteraciones, programación basada en eventos y otros contextos en los que resulta útil pasar operaciones por la estructura del programa. Otros lenguajes de programación, especialmente los que enfatizan la programación funcional , permiten un patrón similar utilizando continuaciones , cierres o generadores ; los agentes de Eiffel enfatizan el paradigma orientado a objetos del lenguaje y utilizan una sintaxis y una semántica similares a los bloques de código de Smalltalk y Ruby .
Por ejemplo, para ejecutar el my_action
bloque para cada elemento de my_list
, uno escribiría:
mi_lista.do_all ( agente mi_acción )
Para ejecutar my_action
solo en elementos que satisfagan 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. Si se les antepone agent
un objeto, se obtiene un objeto que representa la rutina correspondiente con todas sus propiedades, en particular la capacidad de ser llamada con los argumentos adecuados. Por lo tanto, 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éramos llamado directamente a la rutina original: my_action (x)
. Los argumentos a call
se pasan como una tupla, aquí [x]
.
Es posible mantener abiertos algunos argumentos para un agente y hacer que otros estén cerrados . 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
my_list .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 sea abierto mediante la notación {T}?
donde T
es el tipo del 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 action2 (?, y)
con algunos operandos cerrados y otros abiertos corresponde a una versión de la operación original ejecutada sobre 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
my_list .do_all ( agente ( s : STRING ) requiere not_void : s /= Void hacer s .append_character ( ' , ' ) asegurar que se agregue : s .count = old s .count + 1 fin )
El agente en línea que se pasa aquí puede tener todos los elementos de una rutina normal, incluida la condición previa, la condición posterior, la cláusula de rescate (no se utiliza aquí) y una firma completa. Esto evita definir rutinas cuando todo lo que se necesita es un cálculo que se envuelva 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 . para_todos ( agente ( x : ENTERO ): BOOLEAN hacer Resultado := ( x > 0 ) fin )
El mecanismo actual del agente deja abierta la posibilidad de un 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 comprobación en tiempo de ejecución a través de la condición previa valid_arguments
de call
. Existen varias propuestas para una corrección puramente estática de este problema, incluida una propuesta de cambio de lenguaje de Ribet et al. [16]
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 cálculos adicionales 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 y las subsiguientes devolverán la referencia a ese objeto. El esquema típico es:
shared_object : SOME_TYPE una vez creado Result . make ( args ) -- Esto crea el objeto y devuelve una referencia a él a través de 'Result'. end
El objeto devuelto ( Result
en el ejemplo) puede ser mutable, pero su referencia sigue siendo la misma.
A menudo, las "rutinas de una sola vez" realizan una inicialización requerida: varias llamadas a una biblioteca pueden incluir una llamada al procedimiento de inicialización, pero solo la primera de esas llamadas realizará las acciones requeridas. Al usar este patrón, la inicialización se puede descentralizar, lo que evita la necesidad de un módulo de inicialización especial. Las "rutinas de una sola vez" 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 manera 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 que permite realizar conversiones entre varios tipos. Este mecanismo coexiste con la herencia y la complementa. Para evitar cualquier confusión entre ambos 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 indeed entre INTEGER
y REAL
) que existen en la mayoría de los lenguajes de programación, lo que las hace aplicables a cualquier tipo siempre que se respete el principio anterior. Por ejemplo, DATE
se puede declarar una clase para convertir a STRING
; esto hace posible crear una cadena a partir de una fecha simplemente a través de
mi_cadena := mi_fecha
como atajo para utilizar una creación de objeto explícita 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 enumerado en TUPLE [day: INTEGER; month: STRING; year: INTEGER]
, entonces se puede asignar directamente una tupla a una fecha, lo que provoca 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, una excepción ocurre 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 de Eiffel se define mediante la palabra clave rescue . Dentro de la sección rescue , la palabra clave retry ejecuta la rutina nuevamente. Por ejemplo, la siguiente rutina registra la cantidad de intentos de ejecución de la rutina y solo vuelve a intentarlo una cierta cantidad de veces:
connect_to_server ( servidor : SOCKET ) - Conectarse a un servidor o darse por vencido después de 10 intentos. require server /= Void y luego server . address /= Void local attempts : INTEGER hacer server . connect asegurar conectado : server . is_connected rescatar si intentos < 10 entonces intentos := intentos + 1 reintento fin fin
Sin embargo, este ejemplo es indiscutiblemente defectuoso para cualquier programa que no sea el más simple, ya que es de esperar que falle la conexión. Para la mayoría de los programas, un nombre de rutina como attempt_connecting_to_server sería mejor y la condición posterior no prometería una conexión, dejando en manos del llamador la tarea de tomar las medidas adecuadas si la conexión no se abriera.
Hay 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 ( Simple Concurrent Object-Oriented Programming ), que aún no forma parte de la definición oficial del lenguaje 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 (cuando una rutina lanza una excepción después de que su llamador ha 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 suma como
a + b
se entiende conceptualmente como si fuera la llamada al método
a . más ( b )
con objetivo a
, característica plus
y argumento b
.
Por supuesto, la primera es la sintaxis convencional y generalmente la preferida. La sintaxis del operador permite utilizar cualquiera de las dos 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; incluye operadores predefinidos como "+" pero también "operadores libres" compuestos por símbolos no alfanuméricos. Esto permite diseñar notaciones especiales de infijo y prefijo, por ejemplo en aplicaciones de matemáticas y física.
Cada clase puede tener además una función con alias "[]", el operador "corchete", lo que permite la notación a [i, ...]
como sinónimo de a.f (i, ...)
donde f
está la función elegida. Esto es particularmente útil para estructuras contenedoras como matrices, tablas hash , listas, etc. Por ejemplo, el acceso a un elemento de una tabla hash con claves de cadena se puede escribir
número := libreta telefónica [ "JILL SMITH" ]
Los "comandos de asignación" 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 de asignación permiten que una sintaxis similar a la asignación llame a procedimientos "setter". Una asignación propiamente dicha nunca puede tener la forma a.x := v
ya que esto viola el ocultamiento de información; debe optar por un comando setter (procedimiento). Por ejemplo, la clase de tabla hash puede tener la función y el procedimiento
alias de elemento "[]" ( clave : STRING ): ELEMENT [ 3 ] -- El elemento de la clave `key'. -- (consulta "Getter") hacer ... fin put ( e : ELEMENTO ; clave : CADENA ) -- Inserta el elemento `e', asociándolo con la clave `key'. -- (comando "Setter") do ... end
Luego, para insertar un elemento, debes utilizar una llamada explícita al comando setter:
[ 4 ] agenda_telefonica .put ( Nueva_persona , " JILL SMITH" )
Es posible escribir esto de manera equivalente como
[ 5 ] libreta 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
ahora comience (reemplazo de [3]) con
alias de elemento "[]" ( clave : CADENA ): ELEMENTO asignar poner
Esto se declara put
como el comando asignador asociado con item
y, combinado con el alias de corchete, hace que [5] sea legal y equivalente a [4]. (También podría escribirse, 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: (tipo de retorno de a; toda la lista de argumentos de a...)
Eiffel no distingue entre mayúsculas y minúsculas. Los tokens make
, maKe
y MAKE
denotan el mismo identificador. No obstante, consulte las "reglas de estilo" que aparecen 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, el punto y coma simplemente se omite, excepto para separar varias instrucciones en una línea. Esto genera menos desorden en la página del programa.
No existe anidación de declaraciones de características y clases. Como resultado, la estructura de una clase Eiffel es simple: algunas cláusulas de nivel de clase (herencia, invariante) y una sucesión de declaraciones de características, todas en el mismo nivel.
Es habitual agrupar las características en "cláusulas de características" independientes para facilitar la lectura, con un conjunto estándar de etiquetas de características básicas que aparecen en un orden estándar, por ejemplo:
clase HASH_TABLE [ ELEMENTO , CLAVE -> HASHABLE ] hereda TABLA [ ELEMENTO ] 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, p. ej. 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, p. ej. poner ... -- etc. fin
A diferencia de la mayoría de los lenguajes de programación que utilizan llaves , Eiffel hace una distinción clara entre expresiones e instrucciones. Esto está en línea con el principio de separación de comandos y consultas del método Eiffel.
Gran parte de la documentación de Eiffel utiliza convenciones de estilo distintivas, diseñadas para garantizar una apariencia uniforme. 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 los nombres de clase ( LIST
), minúsculas para los nombres de características ( make
) y mayúsculas iniciales para las constantes ( Avogadro
). El estilo recomendado también sugiere el uso de guiones bajos 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 de composición tipográfica: las palabras clave en negrita, los identificadores y constantes definidos por el usuario se muestran en italics
, los comentarios, operadores y signos de puntuación en Roman
, y el texto del programa en , blue
como en el presente artículo para distinguirlo del texto explicativo. Por ejemplo, el programa "¡Hola, mundo!" que se muestra arriba se representaría de la siguiente manera en la documentación de Eiffel:
clase HOLA_MUNDO crear hacer función 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 rutinas de C, incluido soporte para "C en línea" (escribir el cuerpo de una rutina de 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 de 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 de C para obtener código nativo optimizado. En .NET, el compilador EiffelStudio genera directamente código CIL (Common Intermediate Language). El compilador SmartEiffel también puede generar código de bytes de Java .
Python, LISP, Eiffel, Ada y C++ también han influido en Ruby.