¿Cómo “evaluar” un lenguaje? También es importante determinar cuando un lenguaje es eficiente, para esto existen determinados criterios o características que nos ayudan, cabe mencionar que no existen lenguajes buenos o malos sino apropiados e inapropiados para una determinada situación o problema. Para esto cada autor refleja según su criterio que características importantes debe cubrir un lenguaje para un buen desempeño del mismo, cabe mencionar, que los lenguajes son diseñados e implementados, por esta razón es necesario conocer todos los puntos de vista. CRITERIOS DE LENGUAJE SEGÚN DORIS APPLEBY Y JULIUS VANDEKOPPLE Definiciones bien definidas. Los programadores de Fortran o Pl/1 trabajan a menudo como un grupo. Si uno no sabía o había olvidado como escribir el código para efectuar una tarea particular, la cosa más fácil por hacer era bajar al vestíbulo y preguntarle a un amigo. Los manuales eran volúmenes inmensos pobremente organizados que enseñaban mediante ejemplos con más frecuencia que por cualquier otro medio. Sintaxis BNF y EBNF. Los diseñadores de Algol 60 rectificaron esto al proporcionar una sencilla descripción del lenguaje en 18 páginas. La sintaxis del lenguaje está descrita en los forma Backus Naur (BNF), seguida de ejemplos de programación. BNF es un ejemplo de un metalenguaje, un lenguaje utilizado para describir otro lenguaje, en este caso uno de programación. BNF tiene símbolos llamados metasímbolos, y reglas propias, las cuales son empleadas para definir la sintaxis del lenguaje particular de programación en cuestión. Por sintaxis entendemos una colección de instrucciones formada al seguir un conjunto de reglas que diferencian los programas válidos de los no válidos. La sintaxis por sí misma no da significado a un lenguaje; meramente define la colección de frases y sentencias que son combinaciones válidas de los caracteres del lenguaje. Este punto lo estudiaremos en la unidad 2. Semántica. Un lenguaje también debe de estar definido semánticamente al describir la manera precisa lo que significa una construcción particular. Por ejemplo, la expresión (X < 3) significa en pseudocódigo que X debe tener un valor; ese valor es comparable al entero 3, y la expresión es verdadera si el valor es menor que 3, y es falsa en otros casos. El lenguaje natural es notoriamente ambiguo, de manera que hace esfuerzos para describir formalmente la semántica del lenguaje así como también la sintaxis. Comprobabilidad. Probar con certeza matemática que un programa es correcto es un proceso lento. Sin embargo, C.A.R. Hoare cree que “las ventajas prácticas de la comprobación de programas eventualmente se sobrepondrán a las dificultades, en vista de los costos creciente de los errores de programación”. La prueba de que un programa es correcto involucra tres pasos: primero la comprobación de que el programa cumple con la intención del programador; segundo la prueba de que el compilador traduce de manera correcta a código máquina la sintaxis y la semántica del lenguaje empleado; y tercero, que se compruebe que la máquina misma funciona correctamente. Una meta para cualquier lenguaje de programación es probar que un compilador para el lenguaje lo interpreta de manera precisa. Esto es a menudo difícil de hacer si la definición del lenguaje incluye descripciones en lenguaje natural de lo que se desea mediante un trozo particular de sintaxis, si la sintaxis puede describirse en un lenguaje formal, y la semántica puede escribirse axiomáticamente, un compilador puede ser probado formalmente para satisfacer por completo tanto la definición sintáctica como la semántica del lenguaje. La sintaxis de Pascal fue definida en BNF, y su semántica definida axiomáticamente por su diseñador Nicklaus Wirth, en colaboración con C.A.R. Hoare. El PL/1 fue diseñado utilizando la definición Viena (VDL, Vienna Definition Language) y Algol 68 fue definida en una gramática vW de dos niveles (llamada así por el nombre de su inventor, A. van Wijngaarden) que era demasiado enigmática para la mayoría de los usuarios. Estos últimos dos metalenguajes forman bases para comprobación de compiladores. Si un lenguaje está definido en VDL, incluye una descripción de lo que pasa cuando cada declaración del lenguaje se ejecuta teóricamente en una computadora teórica. Si un compilador implementa fielmente la computadora teórica, puede probarse que la ejecución del programa es correcta. La gramática vW no describe una computadora teórica, pero permite que parte de la semántica que trata con declaraciones sea definida en la gramática. Por lo tanto no pueden generarse programas correctos gramaticalmente que vuelvan a declarar variables o que las definan de una manera inconsistente. Confiabilidad. El software se considera confiable si se comporta como es anunciado y produce los resultados que el usuario espera. Cuando se presenta un error, debería ser fácilmente detectado y corregido. Un lenguaje de programación fomenta la escritura de programas confiables de maneras a menudo sutiles. La declaración goto es quizá la característica más notoria de lenguaje pensada para dar como resultado programas no confiables. El problema que subyace aquí es que los programas con muchos gotos, hacia atrás y hacia adelante son difíciles de leer para cualquiera que no sea su creador, y por lo tanto difíciles de modificar y de depurar. Las características de sintaxis poco usuales también pueden fomentar errores. El lenguaje C utiliza = como un operador de asignación. X=5 asigna el valor de 5 a la localidad de almacenamiento designada para X. Para hace comparaciones se utiliza ==. X == 5 compara el valor de X con 5 y si es verdadero o falso, dependiendo si X es igual o no a 5. Puesto que C permite asignaciones casi en cualquier sitio de una declaración, la substitución inadvertida de = por el símbolo poco familiar == puede no producir un error, únicamente resultados ininteligibles. Los identificadores tanto en Modula como en C son sensibles a la capa tipográfica de las letras. Así cuenta y Cuenta representan variables distintas, que son confundidas fácilmente tanto por un programador como por un revisor subsecuente. Un lenguaje confiable debería ser capaz de manejar errores durante el tiempo de ejecución una sobrecarga (overflow) aritmética ocurre cuando se calcula un entero que es mayor de lo que puede ser soportado por el hardware particular involucrado. Puede presentarse gran variedad de errores durante la entrada de datos, desde la lectura al pasar el final de un archivo hasta un valor no permitido introducido de manera interactiva. Estas clases de errores son llamadas excepciones y las provisiones del lenguaje para tratar con ellas se llaman manejadores de excepciones. La interrupción de un programa no siempre es aceptable, en particular para las aplicaciones en tiempo real. Para los lenguajes de programación, la confiabilidad por lo general se refiere a los mecanismos que promueve la escritura, mantenimiento y depuración de los programas correctos, y el subsecuente manejo de excepciones cuando un programa se ejecuta. Traducción rápida. Los lenguajes de programación que se consideran en esta materia, generalmente son independientes de la máquina. Es decir, un programa escrito en el lenguaje puede ser traducido y ejecutado en una variedad de máquinas diferentes. Un programa que escribimos se encuentra en código fuente. Este debe ser traducido a un lenguaje que una máquina particular pueda reconocer, y por último en código de máquina que pueda ejecutarse en realidad. La máquina en la que un programa se ejecuta se denomina anfitrión y su(s) lenguaje(s), lenguaje(s) anfitrión(es). Colocamos la (s) opcional después del lenguaje porque una máquina puede tener más de un lenguaje anfitrión. Cualquier máquina debe tener un lenguaje asociado de máquina de bajo nivel escrito en código binario. También puede tener un lenguaje ensamblador de nivel superior específico de la máquina. Con frecuencia resulta práctico traducir primero el código fuente a código intermedio, el cual es intermedio entre el código de máquina y el código fuente. El código intermedio puede ser o puede no ser uno de los lenguajes anfitrión. La traducción del código fuente involucra tres pasos: análisis lexicográfico, análisis sintáctico y análisis semántico. El análisis lexicográfico, o rastreo, identifica cuáles tokens representan valores, identificadores, operadores, etc. El análisis sintáctico, llamado simplemente sintáctico, reconoce las declaraciones no válidas del lenguaje fuente. El análisis semántico determina el significado de una declaración. Algunos traductores pueden realizar dos o más de estos tres procesos en un solo paso sobre el código fuente. Los traductores son intérpretes o generativos, los cuales generan un código intermedio. Un intérprete es en sí mismo un programa que traduce una expresión o declaración de lenguaje, calcula y luego imprime o utiliza de otro modo su resultado. Los intérpretes son por lo regular más fáciles de escribir que los traductores generativos, pero se ejecutan más lentamente. Una ventaja de un intérprete es que los errores de ejecución así como los de sintaxis son detectados a medida que se encuentra cada declaración, eliminando así cualquier duda acerca de dónde reside el problema. Los lenguajes Lisp y Prolog tienen tanto intérpretes como compiladores, siendo los primeros utilizados para el aprendizaje y la experimentación, donde los resultados línea por línea son deseables. Un compilador es generalmente más ventajoso para programas extensos. Las partes más comunes de un traductor generativo son el compilador, el ligador y el cargador. El compilador traduce el código fuente a código intermedio orientado a la máquina, denominado código objeto. El ligador enlaza de manera conjunta código intermedio compilado independientemente en un solo módulo de carga, resolviendo las diferencias entre tokens. Su salida puede estar en el mismo código intermedio como su entrada pero está libre de referencias de un módulo a otro. El código resultante es así relocalizable, puesto que contiene cualquier información que necesita y es independiente de otros segmentos del programa. El cargador hace la traducción final en código máquina y carga el programa en diversas localidades de memoria. La salida del cargador es un módulo ejecutable en código de máquina. Durante cada fase, se hacen entradas en varias tablas que mantienen el registro de los tipos variables, direcciones de memoria, etc. Es importante en algunos casos, por ejemplo una aplicación interactiva, que el código fuente se traduzca rápidamente. Por otro lado, si un programa se va a compilar solamente una vez y va a ejecutarse a menudo, la velocidad de compilación puede no ser una preocupación principal. Se han hecho intentos exitosos para compiladores de un paso, los que rastrean el código fuente sólo una vez, mientras que algunos traductores efectúan muchos pasos (por ejemplo, algunos de los primeros compiladores Pl/1 de IBM, que ejecutan más de 30 pasos para compilar un programa completo). Algunos factores que afectan el número de pasos necesarios para un compilador en particular son: 1. ¿Cuánta memoria está disponible? ¿Pueden caber simultáneamente en la memoria tanto el código fuente como el código objeto que están siendo generados? 2. ¿Qué tan rápido es el compilador mismo y cuanta memoria requiere? 3. ¿Qué tan grande es el programa objeto y que tan rápido debe ejecutarse? ¿Debe optimizarse el código objeto? 4. ¿Qué clase de características de depuración se requieren para el código fuente? 5. ¿Qué clases de detección y recuperación de errores se requieren para el código ejecutable? 6. ¿Cuántas personas estarán involucradas en la escritura del compilador? ¿Podría ser ventajoso permitir que cada una escriba un paso independiente realizando una fase simple del proceso de compilación? Código objeto eficiente. Después de que el código fuente se compila en código objeto, no se hace referencia adicional al lenguaje fuente. Así es en tiempo de compilación que los asuntos de la eficiencia en el uso de memoria y tiempo de ejecución deben ser considerados. Existe generalmente un balance comparativo entre el trabajo que el programador debe hacer y el trabajo que el compilador puede hacer. Por ejemplo, un lenguaje que tiene todas las declaraciones de tipo y de variables precediendo a otro código puede asignar todas las localidades de memoria en un momento, acelerando la compilación. Por supuesto el programador tendrá que hacer estas declaraciones entes que un programa pueda ser compilado. Algunos compiladores, llamados compiladores de optimización, ejecutan uno o dos pasos más después del análisis semántico para incrementar la eficiencia del código compilado. Las primeras optimizaciones, tales como la eliminación de subexpresiones comunes son independientes de la máquina, mientras que las mejoras finales dependen de la máquina particular en la que el programa se ejecutará. Los lenguajes de muy alto nivel, donde los programas manipulan estructuras complejas tales como registros, listas, relaciones o conjuntos, dependen de compiladores de optimización por eficiencia. Los lenguajes de programación ejecutan la gama de los parecidos a C, donde el programador puede trabajar muy cerca del CPU mismo, hasta lenguajes de manipulación de bases de datos (DML, por sus siglas en inglés), donde las estructuras físicas subyacentes están profundamente ocultas. En los lenguajes de menor nivel, un código objeto eficiente refleja con frecuencia la habilidad o capacidad de los escritores de compiladores. Ortogonalidad. La palabra ortogonal viene del griego y se refiere a líneas rectas cruzándose en ángulos rectos. Las variables aleatorias se consideran ortogonales. Con esto queremos decir que los componentes son independientes entre sí y que se comportan en la misma manera en cualquier circunstancia. Un ejemplo se encuentra en los conceptos de tipo y funciones. Un tipo describe la estructura de los elementos de datos, Una función es un procedimiento por el que pasa un número finito de valores de parámetro y devuelve un único valor hacia el procedimiento que la invoca. En un lenguaje ortogonal, los tipos son independientes de las funciones, y no se aplican restricciones a los tipos de parámetros que pueden ser pasados o al tipo de valor que puede ser devuelto. Así, podríamos ser capaces de pasar una función a una función, y recibir una función de regreso. Lisp incorpora esta característica particular, pero deben comprenderse ciertas dificultades inherentes y tratar con ellas. Algol 68 fue pensado y diseñado como un lenguaje completamente ortogonal. Tiene muy pocas construcciones integradas, y el programador es capaz de construir lo que quiera mediante la combinación de diversas características. Nunca llegó a ser popular en los Estados Unidos, en parte debido a que era demasiado ortogonal. Los programadores querían estructuras especiales que se comportaran de maneras predecibles. La no ortogonalidad puede ser molesta y conducir a errores. Para el programador novato en Pascal, parece no haber una buena razón por la que una función no pueda devolver un registro o por la que un archivo deba ser pasado como un parámetro var. Generalidad. La generalidad está relacionada con la ortogonalidad. Se refiere a la existencia de sólo características necesarias del lenguaje, con las otras compuestas en una manera libre y uniforme sin limitación y con efectos previsibles. Como ejemplo de una carencia de generalidad, considere la del tipo de unión libre en Pascal. Una unión libre es un registro que puede tener un campo que varía dependiendo de su uso. En un registro de esta clase, la variable de campo variante puede funcionar como un apuntador y no ser directamente accesible para impresión u otros usos. En otro momento durante la misma ejecución, puede ser tipificado (declaración de tipos) como un entero, con su valor disponible para impresión, operaciones aritméticas, etc. Esta característica no es general, porque la localidad de memoria relacionada con las variables de campo variante no se trata de manera uniforme y los efectos no son previsibles. Consistencia en notaciones comunes. Como hemos mencionado antes, los problemas para solución por computadora con frecuencia son concebidos en el lenguaje de las matemáticas. De este modo, la notación de los lenguajes de programación debería ser consistente con las notaciones comúnmente usadas en este campo. Usamos “-“ para indicar resta y números negativos. Así, 5-3 y –5 deberían permitirse en lenguajes que soporten aritméticas de enteros. 1 ∈{1,2,3} es la notación común para la pertenencia a un conjunto, y por ello es preferible a la versión en Pascal 1 In [1,2,3]. Sin embargo, no todos los conjuntos de caracteres soportan ∈, {, y }, de modo que en ocasiones se hacen sustituciones. Uniformidad. La consistencia está relacionada con la uniformidad (en algunos casos es llamada regularidad). Con esto queremos decir que nociones similares deberían verse y comportarse de la misma manera. Una cuestión de uniformidad tiene que ver con la necesidad de tener inicios y finales. ¿Debería todo “fin” estar precedido por un “inicio” correspondiente? De manera similar, ¿debería toda declaración finalizar con un signo de punto y coma (;)? En un lenguaje completamente uniforme, la respuesta debería ser sí a ambos asuntos. Subconjuntos. Un subconjunto de un lenguaje es una implementación de sólo una parte del mismo, sin características especiales. Las especificaciones originales para el lenguaje Ada del DOD no permiten subconjuntos. La motivación para esto fue el deseo del DOD para hacer que sus contratistas produjeran software que explotará un Ada con todas sus características. Después de todo, las características innecesarias no fueron incluidas. Una de las desventajas de este enfoque era que los estudiantes no podían empezar a aprender el lenguaje hasta que tuvieran disponibles compiladores completamente validados; por esta razón no existió un cuerpo de programadores hasta varios años después de que el lenguaje había sido completado. Algunos lenguajes son extensos, con muchos componentes especiales. Éstos pueden ejecutarse solamente en máquinas grandes y no están disponibles para compañías y escuelas más pequeñas a menos que se trate de un subconjunto de los mismos. Otra ventaja de los subconjuntos es el desarrollo incremental de un lenguaje. Con esto nos referimos a la versión inicial de un lenguaje de núcleo pequeño, con otras características que van siendo liberadas a medida que se van desarrollando. Extensibilidad. El inverso de los subconjuntos es la extensibilidad. Un lenguaje puede tener un núcleo estándar, el cual es invariable en cada implementación, pero con varias extensiones. Las ventajas de los subconjuntos son mejoradas cuando un lenguaje puede ser extendido en formas útiles. A principios de 1968, los desarrolladores de Cobol adoptaron este enfoque mediante la definición de un “núcleo” que todos los compiladores debían satisfacer. Once módulos estandarizados fueron agregados los cuales pueden o no pueden ser incluidos en cualquier compilador Cobol dado. Ada 95 ha adoptado un enfoque modular semejante. Los diseñadores de Pascal incluso usaron otro enfoque, definiendo un pequeño lenguaje estándar portátil que carecía de algunas características deseables, tales como capacidades de gráficos y manejo de cadenas de caracteres. Los implementadores de Pascal agregaron varias mejoras, las cuales hicieron a sus compiladores atractivos para los programadores, pero los programas resultantes eran menos portátiles. Por ejemplo, el Pascal estándar no tiene tipo de cadenas (string), pero casi todos los compiladores de Pascal proporcionan uno integrado en el lenguaje mismo o en un módulo especial para ser incluido con la mayoría de los archivos fuente. Transportabilidad. Un lenguaje es transportable si sus programas pueden compilarse y ejecutarse en diferentes máquinas sin tener que reescribir el código fuente. Para conseguir la transportabilidad se han establecido las organizaciones estándares nacionales e internacionales para producir descripciones de lenguajes a las cuales deben adherirse las implementaciones. Las más activas de éstas son el Instituto Americano de Estándares (ANSI), la Institución Británica de Estándares (BSI) la Organización Internacional de Estándares (ISO) y el Instituto de Ingenieros Eléctricos y Electrónicos (IEEE). Éstos grupos tienen varios comités oficiales que preparan y revisan estándares para diferentes lenguajes. Los estándares pueden desarrollarse después de ganar alguna experiencia con un lenguaje en particular, como es el caso de Pascal, o antes de que un lenguaje sea diseñado, como ocurre con Ada. La estandarización temprana puede perpetuar características de diseño deficientes no reconocidas, al tiempo que demora el fomento de dialectos incompatibles. Lisp es quizás el lenguaje con la mayor longevidad no estandarizada. Lisp fue diseñado e implementado a principios de los años sesenta, pero es solamente hasta ahora que se está estandarizando a Common Lisp. Sin embargo, la parte estandarizada será solamente un pequeño núcleo, con diferentes implementadores libres de hacer cualquier extensión que ellos deseen. PROPIEDADES DE UN BUEN LENGUAJE SEGÚN JORGE CASTRO Y OTROS Aspectos de diseño Claridad, simplicidad y unidad de conceptos. Un lenguaje de programación ha de suministrar al programador un conjunto de conceptos claros, simples y unificados, de manera que puedan ser usados como primitivas para el diseño de algoritmos con esta finalidad, es deseable tener un número pequeño de conceptos distintos y que las reglas para combinarlos sean tan simples y regulares como sea posible. Sintaxis y semántica bien definidas. La necesidad de una sintaxis y una semántica bien definidas es necesario ya que por una parte una definición sintáctica formal nos puede asegurar la inambigüedad y completitud del lenguaje y, por otra, la definición formal de la semántica obligará a que todas las implementaciones de éste se comporten igual. De la misma manera también es importante dar al lenguaje una sintaxis clara que lo haga legible. La legibilidad tiene su contrapunto en la facilidad para escribir programas, especialmente respecto a la rapidez, por eso, en ciertos casos, puede ser deseable que el lenguaje sea críptico – para incrementar la rapidez en la escritura de los programas – y, por tanto que pierda legibilidad. Consistencia con las notaciones usuales. Es la propiedad de que las cosas signifiquen lo que uno espera que signifiquen. Por ejemplo, que los símbolos de operación sean los usuales, las palabras claves tengan un uso relacionado con lo que significan, o bien que si definimos constructores que ya aparecen en otros lenguajes su significado no sea diferente. Soporte para la abstracción. Debido a que en muchos casos el conjunto de operaciones y tipos de datos básicos está muy lejos de lo que querríamos a la hora de resolver un problema, el lenguaje nos ha de dar mecanismos para poder abstraer: poder crear tipos de datos nuevos y asociarles nuevas operaciones, poder definir diferentes niveles dentro de un programa y tratarlos por separado, etc. Independencia de la máquina. La máquina sobre la que se trabaja no debe condicionar la programación. Esta propiedad está ligada a la portabilidad, es decir, la capacidad de mover programas de una máquina a otra. Esto se consideraba una faceta esencial para los lenguajes de alto nivel, pero en muchos casos (especialmente en los lenguajes imperativos) no se ha cumplido. Verificabilidad. En principio, se trata de que los programas sean verificables formalmente. Esta propiedad, además, garantiza una cierta legibilidad en los programas. Redundancia. Es una propiedad muy importante a la hora de detectar errores. Un ejemplo de redundancia (repetición de información) sería la definición explícita de variables, pues el solo hecho de usarlas ya nos dice que son variables y también de qué tipo son. La redundancia puede permitir detectar errores tipográficos, por ejemplo, al escribir una variable, ya que el nombre que hayamos escrito por error (si no es por casualidad) no estará definido. En general, es muy importante que los errores se detecten cuanto antes mejor, porque cuanto más se tarde en detectarlos más costoso será corregirlos. Ortogonalidad. Un lenguaje de programación ha de tener sólo un conjunto de herramientas básicas, comprensibles por separado y sin ningún tipo de iteración cuando se combinan. Esto quiere decir esencialmente que haya pocas excepciones dentro del conjunto de cosa que podemos hace combinando los diferentes elementos del lenguaje. Aspectos de Implementación. Portabilidad. Para muchos proyectos, la portabilidad del programa resultantes es un criterio muy importante, pues las computadoras donde se desarrollen los programas no son normalmente los mismos que habrán de ejecutarlos finalmente. Ya hemos visto que esta propiedad de las implementaciones de un lenguaje depende directamente de dos aspectos del diseño: la independencia de la máquina y la definición formal de semántica. Soporte Externo. La existencia de un entorno de trabajo apropiado puede hacer que un lenguaje, en principio menos potente, sea más fácil de usar que uno más potente que no tenga este soporte. Dentro de este entorno podemos incluir, por ejemplo, programas para encontrar errores (debuggers) o editores especiales que agilicen el proceso de creación de programas. Calidad del compilador o intérprete. Si se trata de un compilador incluye el costo (en tiempo) de traducción, como la calidad del código generado. Según el tipo de objetivo que tengamos (si tenemos que compilar muy a menudo y después ejecutar muy poco, bien al revés), potenciaremos más la eficiencia en un sentido o en otro. Si el lenguaje es interpretado, entonces se refiere a la eficiencia (en tiempo) de la interpretación. Bajo costo de mantenimiento. Muchos estudios han demostrado que la mayor parte del costo que implica cualquier programa que use durante un cierto período de tiempo (años), no está en el costo inicial del diseño e implementación, sino en el mantenimiento que recibe una vez instalado. Este mantenimiento incluye la reparación de errores o las extensiones del programa para responder a nuevas necesidades y es hecho, posiblemente, por programadores que no son los que hicieron el programa inicialmente. Por lo tanto, un lenguaje que permita la modificación, reparación, y extensión a lo largo del tiempo de programas por parte de diferentes programadores, puede reducir notablemente los costos. Documentación. Este punto sólo hace referencia a la necesidad de disponer de una buena documentación sobre el lenguaje. Esta documentación se compondrá de diversos manuales: de la implementación del lenguaje, de los complementos del entorno, etc. CRITERIOS DE EVALUACIÓN SEGÚN TERRENCE PRATT Claridad, sencillez y unidad de los conceptos del Lenguaje. Un lenguaje de programación proporciona a la vez un marco conceptual para pensar acerca de los algoritmos y un medio de expresar esos algoritmos. El lenguaje debe constituir una ayuda para el programador mucho antes de la etapa misma de codificación. Debe proveer un conjunto claro, sencillo y unificado de conceptos que se puedan usar como primitivas en el desarrollo de algoritmos. Para ello es deseable contar con un número mínimo de conceptos que se puedan usar como primitivas en el desarrollo de algoritmos. Para ello es deseable contar con un número mínimo de conceptos distintos cuyas reglas de combinación sean tan sencillas y regulares como sea posible. Llamamos a este atributo Integridad Conceptual. Esta claridad de semántica y de conceptos es el factor determinante del valor de un lenguaje Claridad en la sintaxis del programa. La sintaxis de un lenguaje afecta la facilidad con la que un programa puede escribirse, probarse y más tarde entenderse y modificarse. La facilidad de lectura (legibilidad) de un programa en un lenguaje es una consecuencia central aquí. Una sintaxis que sea particularmente breve o enigmática con frecuencia hace que un programa sea fácil de escribir (por el programador con experiencia), pero difícil de leer cuando el programa debe modificarse posteriormente. Los programas APL a veces resultan tan enigmáticos que sus propios diseñadores no pueden descifrarlos con facilidad unos meses después de que los terminaron. Muchos lenguajes contienen construcciones sintácticas que fomentan la dificultad de lectura, produciendo dos instrucciones casi idénticas que significan realmente dos cosas distintas. Por ejemplo la presencia de un solo carácter blanco en un proposición Snobol4 puede alterar completamente su significado. Un lenguaje debe tener la propiedad de que las construcciones que tienen un significado diferente también tengan un aspecto diferente: por ejemplo, las diferencias semánticas deben reflejarse en la sintaxis del lenguaje. Para el programador no basta que una sintaxis no sea engañosa o propensa a errores sino que además debe ser una sintaxis que usada adecuadamente permita que las estructuras lógicas señaladas en el algoritmo. En el enfoque de programación conocido como programación estructurada, los lenguajes se diseñan jerárquicamente de arriba hacia abajo (del programa principal a los subprogramas de niveles más bajos), usando sólo un conjunto restringido de estructuras de control en cada nivel - secuencias de instrucciones simples, iteraciones y ciertos tipos de ramificación condicional. Cuando se hace en forma adecuada, las estructuras algorítmicas resultantes son fáciles de entender, depurar y modificar. En forma ideal debería ser posible traducir tal programa directamente a instrucciones de programación adecuadas que reflejaran la estructura del algoritmo. Con frecuencia la sintaxis del lenguaje no permite tal codificación directa. Por ejemplo, Fortran se basa en etiquetar las proposiciones e instrucciones goto como estructuras de control y proporciona poca alternativas. Por eso la forma de un programa Fortran no se puede hacer de manera ordinaria para reflejar con mucha claridad la estructura de control del algoritmo adyacente, y gran parte del esfuerzo utilizado en el desarrollo de un algoritmo estructurado es posible que se pierda en la traducción al Fortran. Esto es parte de la controversia que provoca el uso del goto, lo cual se verá en la unidad de control de secuencias. uno de los argumentos principales que favorecen a Pascal sobre Fortran en la enseñanza de la programación introductoria, a pesar de que se este se utilizaba mucho, es que Pascal favorece al diseño elegante de los programas. Ortogonalidad. El término ortogonalidad se refiere al atributo de ser capaz de combinar varias características de un lenguaje en todas las combinaciones posibles, de manera que todas ellas tengan un significado. Por ejemplo, supóngase que un lenguaje dispone de una expresión que puede producir un valor, y también dispone de un enunciado condicional que evalúa una expresión para obtener un valor falso o verdadero. Estas dos características del lenguaje, la expresión y el enunciado condicional son ortogonales si se puede usar y evaluar cualquier expresión dentro del enunciado condicional. Cuando las características de un lenguaje son ortogonales, entonces es más fácil de aprender y de escribir los programas porque hay menos excepciones y casos especiales que recordar. El aspecto negativo de la ortogonalidad es que un programa suele compilar sin errores a pesar de contener una combinación de características que son lógicamente incoherentes o cuya ejecución es en un extremo ineficiente. A causa de estas cualidades en oposición, la ortogonalidad como atributo de un diseño de lenguaje es todavía motivo de controversia, pues a ciertas personas le s gusta y a otras no. Naturalidad para la aplicación. El lenguaje debe proporcionar estructuras de datos adecuadas, operaciones, estructuras de control y una sintaxis natural para resolver un problema. Una de las razones principales de la proliferación de lenguajes es justamente esta necesidad de naturalidad. Un lenguaje ajustado particularmente a cierta clase de aplicaciones puede simplificar mucho la creación de está área. Es importante mencionar que los algoritmos secuenciales, algoritmos concurrentes, algoritmos lógicos, etc. Todos ellos tienen estructuras naturales diferentes que están representadas por programas en esos lenguajes. Cobol, para aplicaciones en negocios incluyendo el manejo de archivos; Snobol4, para procesamiento de cadenas; Prolog con su predisposición hacia propiedades deductivas y c++ para diseño orientado a objetos son lenguajes con una inclinación obvia a clases particulares de aplicaciones. Apoyo para la abstracción. Aun con el lenguaje más natural para una aplicación, hay una brecha sustancial de las estructuras de datos abstractos y las operaciones que caracterizan la solución de un problema con las estructuras de datos particulares de datos primarios y operaciones construidas en un lenguaje. Por ejemplo, Pascal puede ser un lenguaje apropiado para construir un programa que haga el horario de clases de una universidad pero las estructuras de datos abstractos de “estudiante”, “clase”, “instructor”, “salón de lectura” y las operaciones abstractas de “asignar un estudiante a una clase”, “planear una clase en un salón de lectura”, etc., que son naturales de la aplicación no son proporcionadas directamente por Pascal. Una parte substancial de la tarea del programador es diseñar las abstracciones usando las características más primitivas proporcionadas por el lenguaje de programación real. El lenguaje debe ayudar sustancialmente o impedir la expresión de estas abstracciones. En teoría debe permitir que las estructuras de datos, tipos de datos y operaciones definan y mantengan como abstracciones contenidas por sí mismas, para que el programador pueda emplearlos en otras partes del programa conociendo sólo sus propiedades de abstracción sin importar los detalles de su implementación. Casi todos los lenguajes proporcionan mecanismos de subprogramas para definir las operaciones de abstracción, pero la mayoría son muy débiles en su soporte de otros tipos de abstracción a excepción de los lenguajes de Ada y C++. Facilidad para verificar programas. La confiabilidad de programas escritos en un lenguaje es casi siempre una preocupación central. Existen muchas técnicas para verificar que un programa ejecuta correctamente la función requerida. Se puede probar que un programa es correcto a través de un método formal de verificación, se puede probar informalmente que es correcto por la verificación de escritorio, es decir, verificar visualmente el texto del programa; se puede poner a prueba ejecutándose con los datos de entrada de prueba y comparando los resultados de salida con las especificaciones, etc. Para programas grandes se suele emplear la combinación de algunos de estos métodos. El uso de un programa que dificulta la verificación de programas puede ser más problemático que el de uno que apoya y simplifica la verificación no obstante que el primero pueda proporcionar muchas capacidades que superficialmente parecen hacer más fácil la programación. La sencillez de la estructura semántica y sintáctica es un aspecto primordial que tiende a simplificar la verificación de programas. Entorno de programación. La estructura técnica de un lenguaje de programación es sólo uno de los aspectos que afectas su utilidad. La presencia de un entorno de programación adecuado puede facilitar el trabajo con un lenguaje técnicamente débil en comparación con un lenguaje más fuerte con apoyo externo. Se podría incluir una larga lista de factores como parte del entorno de programación. La disponibilidad de una implementación confiable, eficiente y bien documentada del lenguaje debe encabezar la lista. Los editores especiales y paquetes de prueba hechos a la medida para el lenguaje pueden acelerar mucho la creación y puesta a prueba de los programas. Los recursos para el mantenimiento y modificación de múltiples versiones de un programa puede simplificar mucho el trabajo con programas grandes. Uno de los lenguajes que ofrece un entorno de programación compuesto es el Smalltalk Portabilidad de Programas. Un criterio importante para muchos proyectos de programación es el de la portabilidad de los programas resultantes de la computadora en la cual se desarrollaron para otros sistemas de computadoras. Un lenguaje que está ampliamente disponible y cuya definición es independiente de las características de una máquina en particular constituye una base útil para la producción de programas transportables. Ada, Fortran, C y Pascal tienen todos ellos definiciones estandarizadas que permiten la implementación de aplicaciones transportables. Otros, como Ml, provienen de una implementación de fuente única que permite al diseñador de lenguajes cierto control sobre las características transportables del lenguaje. Costo de uso. Es indudable que el costo es un elemento importante en la evaluación de cualquier lenguaje de programación, pero son factibles diferentes medidas del mismo. Costo de ejecución de los programas. En los primeros días de la computación, las cuestiones de costo se referían casi exclusivamente a la ejecución de los programas. Era importante el diseño de compiladores que optimizaran la asignación eficiente de registros y el diseño de mecanismos eficientes de apoyo al tiempo de ejecución. El costo de ejecución del programa, aunque siempre ha tenido cierta importancia en el diseño del lenguaje es de importancia primordial para grandes programas de producción que se van a ejecutar con frecuencia. En la actualidad, sin embargo, para muchas aplicaciones la velocidad de ejecución no es la preocupación mayor. Con máquinas de escritorio que trabajan a varios millones de instrucciones por segundo y que están ociosas la mayor parte del tiempo, se puede tolerar el incremento de un 10 o 20% del tiempo de ejecución si ello significa un mejor diagnóstico o un control más fácil por parte del usuario sobre el desarrollo y mantenimiento del programa. Costo de traducción de programas. Cuando un lenguaje como Fortran o C se utiliza en la enseñanza, la cuestión de traducción eficiente, más que la ejecución eficiente, puede ser de importancia capital. Típicamente los programas estudiantiles se compilan muchas veces cuando se están depurando pero se ejecutan sólo pocas veces. En casos así, es importante contar con un compilador rápido y eficiente en vez de un compilador que produzca un código optimizado. Costo de creación, prueba y uso de programas. Un tercer aspecto del costo de un lenguaje de programación queda ejemplificado por el lenguajes Smalltalk. Para una cierta clase de problemas se puede diseñar, codificar, probar, modificar y usar una solución con una inversión mínima en tiempo y energía del programador. Smalltalk es económico en cuanto a que se minimizan el tiempo y el esfuerzo totales que se invierten en la solución de un problema en la computadora. La preocupación por esta clase de costo de conjunto en el uso de un lenguaje se ha vuelto tan importante en muchos casos como la inquietud por la ejecución y compilación eficiente de los programas. Costo de mantenimiento de programas. Muchos estudios han demostrado que el costo más grande que interviene en cualquier programa que se utiliza a lo largo de un período de años no es el costo del diseño, codificación y prueba inicial del programa, sino los costos totales del ciclo vital, que incluye los costos de desarrollo y el costo de mantenimiento del programa en tanto continúa en uso productivo. El mantenimiento incluye la reparación de errores, los que se descubren después de que se comienza a usar el programa, cambios que requiere el programa cuando se actualiza el hardware subyacente o el sistema operativo, y extensiones y mejoras al programa que se requieren para satisfacer nuevas necesidades. Un lenguaje que facilita la modificación, reparación y extensión frecuente del programa por parte de diferentes programadores durante un período de varios años puede ser a la larga, mucho menos costoso que usar cualquier otro. FUNDAMENTOS DE LOS LENGUAJES SEGÚN ROGER S. PRESSMAN. Tipos de datos y tipificación. Hoy en día, el mérito de un lenguaje de programación moderno se juzga por algo más que sintaxis y la amplitud de construcciones procedimentales. La tipificación de datos y los tipos de datos específicos soportados por un lenguaje de programación son un aspecto importante en la calidad del lenguaje. Un objeto de datos hereda un conjunto de atributos fundamentales del tipo de datos al que pertenece. Un objeto de datos puede tomar un valor que se encuentre dentro del rango de valores legítimos del tipo de datos y puede ser manipulado por operaciones que se apliquen a ese tipo de datos Los tipos de datos simples abarcan un amplio rango que incluye los tipos numéricos, tipos enumerados, tipos lógicos y tipos de cadena. Los tipos de datos más complejos engloban estructuras de datos engloban estructuras que van desde los array simples unidimensionales hasta las estructuras de listas y los complejos y heterogéneos arrays y registros. Las operaciones que se pueden realizar sobre un tipo de datos particular y la forma en que se pueden manipular distintos tipos en una misma sentencia se controlan por la comprobación de tipos incorporada en el intérprete o compilador del lenguaje de programación. Fairley define cinco niveles de comprobación de tipos que se encuentran normalmente en los lenguajes de programación. Nivel 0: sin tipos. Los lenguajes de programación sin tipos no incluyen medios explícitos para la tipificación de datos y, por lo tanto, no conllevan ninguna comprobación de tipos. Los lenguajes ANSI estándar de algunos como Basic, Apl, Lisp, e incluso Cobol entran dentro de esta categoría. Aunque cada lenguaje permite al usuario definir las estructuras de datos, la representación de los datos contenida en cada objeto de datos esta predefinida. Nivel 1: coerción automática de tipos es un mecanismo de comprobación de tipos que permite al programador mezclar diferentes tipos incompatibles, permitiendo así que se den las operaciones requeridas. Por ejemplo Pl/1 asigna un valor numérico de 0 al valor lógico false (falso) y un valor numérico de 1 al valor lógico true (verdadero). Así, las operaciones aritméticas, aplicadas normalmente a tipos de datos numéricos, se pueden aplicar a tipos de datos lógicos en Pl/1. Nivel 2: modo mixto conversión de tipos es similar en muchos aspectos a la coerción automática de tipos. Para que se pueda dar una determinada operación, diferentes tipos de datos dentro de una misma categoría de tipos, se convierten a un único tipo destino. La aritmética de modo mixto de Fortran permite que se usen números enteros y reales en una misma secuencia de lenguajes de programación. Nivel 3: comprobación de tipos pseudorrígida tiene todas las características de la fuerte comprobación de tipos, pero que se implementan de forma que existan una o más vías escapes. Por ejemplo aunque Pascal, comprueba la compatibilidad de interfaces dentro de un mismo programa compilado, no lo hace para procedimientos compilados por separado – un escape en la aplicación de la fuerte comprobación de datos. Nivel 4: fuerte comprobación de tipos se da en lenguajes de programación que sólo permiten llevar a cabo operaciones entre objetos de datos que sean del mismo tipo de datos previamente especificado. La comprobación de tipos se lleva a cabo sobre los operadores, los operandos y las interfaces de los subprogramas, tanto en tiempo de compilación como en tiempo de carga y en tiempo de ejecución. Los compiladores de Ada llevan a cabo una fuerte comprobación de tipos. Subprogramas. Un subprograma es un componente de un programa compilado por separado que contiene datos y estructuras de control. Generalmente se le menciona con el nombre de módulo como una manifestación genérica del subprograma. Dependiendo del lenguaje de programación, un subprograma se puede denominar subrutina, procedimiento, función u otro nombre especializado. Sin tener en cuenta cual sea el nombre, el subprograma tiene un conjunto de características genéricas Una sección de especificación que incluye su nombre y la descripción de la interfaz. Una sección de implementación que incluye los datos y las estructuras de control Un mecanismo de activación que permite que el subprograma sea requerido desde cualquier punto del programa. En los lenguajes de programación convencionales, cada subprograma es una entidad en sí misma, operando sobre los datos en la forma que dictan las estructuras de control de un programa mayor. En lenguajes de programación orientados a objetos, el punto de vista clásico del subprograma es reemplazado por el de objeto. Estructuras de Control. Es un nivel fundamental, todos los lenguajes de programación modernos permiten al programador representar secuencias, condiciones y repetidores – las construcciones lógicas de la programación estructurada. La mayoría de los lenguajes de programación modernos proporcionan una sintaxis para la especificación directa del “if then else”, del “do while” y del “repeat until” (así como el case). Otros lenguajes, como Lisp y Apl, requieren que el programador emule esas construcciones dentro de los límites de sintaxis del lenguaje. Además de las construcciones prodecimentales básicas de la programación estructurada, puede haber otras estructuras de control. La recursividad crea una segunda activación de un subprograma durante la primera activación. O sea, el subprograma se llama a sí mismo como parte del procedimiento definido, la concurrencia da soporte a la creación de múltiples tareas, a la sincronización de tareas y a la comunicación de tareas en general esta característica del lenguaje es de mucho valor cuando se llevan a cabo aplicaciones de tipo real. El manejo de excepciones es una característica del lenguaje que atrapa los errores del sistema o los definidos por el usuario, pasando el control a un manipulador de excepciones para que los procese. Soporte para el enfoque orientado a objetos. En teoría, la creación de objetos y la construcción de software orientado a objetos se puede llevar utilizando cualquier lenguaje de programación convencional (por ejemplo, C o PASCAL). Pero en la práctica, el soporte para métodos orientados a los objetos debe estar incorporado directamente en el lenguaje de programación que se vaya a utilizar para implementar un diseño orientado a objetos Un lenguaje de programación orientado a objetos debe proporcionar un soporte directo para la definición de clases la herencia, la encapsulación y el paso de mensajes. Además de estas construcciones orientadas a los objetos, muchos de estos lenguajes implementan características adicionales, como herencia múltiple y polimorfismo (diferentes objetos que pueden recibir mensajes con el mismo nombre). La definición de clases es fundamental en un enfoque orientado a los objetos. Un lenguaje de programación orientado a los objetos define un nombre de clase y específica los componentes privados y públicos de la clase. Para ilustrar la forma general de las construcciones orientadas a los objetos vamos a utilizar C++. Se puede definir una clase, denominada “contador”, de la siguiente forma: class contador { private : unsigned int valor; public : contador ( ); void incrementar ( ); void decrementar ( ); unsigned int acceder_valor ( ); }; A partir de la definición básica de la clase se puede obtener una nueva clase, de la siguiente manera: class contador_especial: public contador { una copia de todos los datos privados de contador private : Datos privados de contador_especial; … }; Los objetos derivados de la clase “contador_especial” pueden utilizar todos los métodos definidos para la “clase madre” contador. La definición de una clase encapsula las abstracciones de los datos y los componentes del programa (métodos) que operan sobre dichos datos. En C++, los mensajes se envían a los objetos (la instanciación de una clase) de la siguiente forma: Nombre_objeto.mensaje (argumentos); Donde nombre_objeto identifica el objeto y mensaje (argumentos) describe el mensaje a enviar. Los detalles de implementación y la terminología para las definiciones de clases, herencia, encapsulación y paso de mensajes puede variar dependiendo del lenguaje. Por ejemplo, el lenguaje de programación Smalltalk define una clase de la siguiente manera Definición: identifica a la clase. Datos privados: atributos cuyos valores son privados de las instancias individuales de la clase. Datos compartidos: atributos cuyos valores son compartidos por todas las instancias de la clase. Diccionarios: atributos cuyos valores son compartidos por múltiples clases. Métodos de instancia: los procedimientos que implementan mensajes que se pueden enviar a las instancias de la clase. Métodos de clase: los procedimientos que implementan mensajes que se pueden enviar a la propia clase (por ejemplo: inicialización de los datos compartidos) Aunque estos términos difieren ligeramente de los usados en la definición de C++, el concepto fundamental de clase permanece igual. De forma parecida, la herencia, la encapsulación y el paso de mensajes, se implementan con una sintaxis diferente, pero con los mismos fundamentos semánticos. Cualquier construcción está disponible en cualquier lenguaje que verdaderamente sea orientado a objetos. CARACTERÍSTICAS DE UN BUEN LENGUAJE SEGÚN ALLEN B. TUCKER Expresividad. La habilidad de un lenguaje para reflejar claramente el significado deseado por el diseñador del algoritmo, por lo que, permite que una declaración se establezca compactamente y refuerce el uso de sentencias asociadas con la programación estructurada Bien definido. Que la sintaxis y semántica del lenguaje no contengan ambigüedades, son internamente consistentes y completas. Entonces el implementador de un lenguaje bien definido debe tener dentro de su definición una especificación completa de todas las formas expresivas del lenguaje y sus significados. El programador, por la misma virtud, debe poder predecir exactamente el comportamiento de cada expresión antes de que realmente se ejecute. Por lo tanto, los lenguajes bien definidos aumentan la transportabilidad y la prueba. Tipos y Estructuras de Datos. La habilidad de un lenguaje para soportar una gran variedad de valores de datos y colecciones no elementales de éstos. Los últimos incluyen principalmente a los arrays y registros, pero no excluye a las estructura de datos dinámicas, tales como listas, colas, pilas y árboles. Modularidad. Esta características tiene dos aspectos: El soporte del lenguaje para la subprogramación, es decir, la habilidad de definir procedimientos y funciones independientes y comunicarlos vía parámetros o variables globales con el programa llamador. La extensibilidad del lenguaje en el sentido de permitir operadores y tipos de datos definidos por el programador Facilidades de Entrada – Salida. En las Entradas y Salidas de un lenguaje, nosotros estamos examinando cómo soporta los archivos de acceso secuencial, indexado y directos, así como las funciones sobre base de datos y de recuperación de Información. Los archivos de datos generalmente residen sobre el almacenamiento secundario. Un archivo es normalmente demasiado grande para que todos sus registros quepan en la memoria, al mismo tiempo y, por lo tanto se les aplican diferentes estrategias de programación. Transportabilidad. Es cuando el lenguaje que está implementado en distintas computadoras. Esto es, su diseño es relativamente independiente de la máquina. Los Estándares de los lenguajes generalmente son más transportables. Eficiencia. Permite la ejecución y traducción rápida sobre las máquinas en dónde está implementado. Pedagogía. La facilidad de enseñar y aprender, existencia de mejores libros de texto, están implementados en un entorno de desarrollo de programas mejor; son ampliamente conocidos y usados por los mejores programadores en el área de aplicación. Generalidad. Que el lenguaje sea útil en un amplio rango de aplicaciones de programación. CRITERIOS DE EVALUACIÓN DE LOS LENGUAJES SEGÚN ROBERT SEBESTA Legibilidad. Uno de los más importantes criterios para juzgar a un lenguaje de programación es la facilidad con la cual los programas pueden ser leídos o entendidos. Antes de 1970, el desarrollo de software fue intentado largamente en los términos del código de escritura. En la década de 1970, sin embargo el ciclo de vida de los conceptos de software fue desarrollado; la codificación fue relegada a una representación en un papel más pequeño y el mantenimiento fue reorganizado como gran parte del ciclo, esto en los términos de costo. Porque la facilidad de mantenimiento es determinada en gran parte por la legibilidad de los programas, la legibilidad es un punto muy importante en de la calidad de programas y los lenguajes de programación. Sencillez. Dentro de las características que contribuye a la legibilidad de un lenguaje de programación es la sencillez, la cual puede afectar fuertemente a la legibilidad. Un lenguaje que tiene un gran número de componentes elementales es más difícil de aprender que uno que contenga un número pequeño. Los programadores que utilizan los lenguajes de programación tienen la tendencia de aprender una parte de los mismos sin embargo ignoran otras características de ellos. Esta razón muchas veces es utilizada como pretexto, cuando el lenguaje tiene un gran número de componentes o elementos, pero este argumento no es válido. Los problemas de legibilidad ocurren cuando el programador aprende de una forma diferente una aplicación, con la cual el analista este familiarizado. Teniendo también muchas características no es solamente el detrimento de la simplicidad del lenguaje Otro problema es la multiplicidad de características, es decir, tener más de una forma determinada para realizar una operación en particular. Por ejemplo, en el lenguaje C se puede incrementar una simple variable entera de cuatro diferentes maneras: Contador = contador + 1 Contador =+ 1 ++ contador contador ++ Aunque la última sentencia tiene una pequeña diferencia semántica en comparación con las otros en algunos usos, las cuatro tiene el mismo significado cuando son usadas como expresiones estando solas. Un tercer problema potencial es el operador sobrecargado, en el cual un simple símbolo operador tiene más de un significado. Aunque esto es una característica útil, puede reducir la legibilidad al leer si los usuarios están permitiendo ellos mismos la generación de sobrecarga y ellos no lo hacen evidentemente. Por ejemplo, es claramente aceptada la sobrecarga del signo + para usarlo en la suma tanto de enteros como de reales o punto flotantes. En efecto esta sobrecarga simplifica un lenguaje porque reduce el número de operadores. Sin embargo, supuestamente, la definición del programador del signo + usado entre arreglo de dimensión simple puede significar la suma de todos los elementos de ambos arreglos. Porque el significado usual de la adición de vectores es completamente diferente de esto, puede hacer más confuso el programa para ambos, el autor y los lectores. Un nivel de ejemplo más extremo de la confusión de programa puede ser un usuario definiendo el signo + usado entre dos vectores para significar la diferencia entre los primeros elementos de los mismos. Las sentencias del lenguaje pueden ser también muy simples. Por ejemplo la sintaxis y el significado de las sentencias del lenguaje ensamblador son un modelo de simplicidad. Es muy simple, sin embargo hacer programas en lenguaje ensamblador es poco legible. Porque ellos carecen por completo de sentencias de control, su estructura es más sutil, porque sus sentencias son más simples, para realizar mejor esto se requiere un lenguaje equivalente de alto nivel Ortogonalidad. Ortogonalidad en un lenguaje de programación significa que hay una pequeña relación entre un conjunto de construcciones primitivas que pueden ser combinados en un numero relativamente pequeño de manera que se pueden construir las estructuras de control y de datos en el lenguaje. Además, cada combinación posible de primitivas es legal y significativa así, la ortogonalidad sigue de una simetría de lazos entre primitivos, mientras un concepto del uso de ortogonalidad en el diseño puede ser ilustrado comparando un aspecto de los lenguajes ensamblador de los ordenadores centrales de la IBM y de la serie de VAX de superminicomputadoras. Nosotros consideraremos solamente una sola situación simple, la de agregar dos valores numéricos de 32bit que residen o en memoria o en un registro y substituir uno de los dos valores con suma para este propósito, la unidad IBM tiene dos instrucción que tienen la siguiente forma: A Reg1, memory_cell. R Reg1, Reg2 Donde Reg1 y Reg2 representan registros. La semántica de ellos es Reg1 contenido(reg1) + contenido (memory_cell) Reg1 contenido(reg1) + contenido(reg2) La instrucción de adición VAX para valores enteros de 32 bits es ADDL operando1, operando2 La semántica de la misma es: Operando2 contenido(operando1) + contenido(operando2) En este caso, cualquier operando puede ser uno colocado en una celda de memoria con una instrucción. El diseño de VAX tiene ortogonalidad por que aquí con una sola instrucción se puede utilizar para colocar, en la celda memoria, un operando. Hay dos maneras de especificar el operando, que puede ser la combinación en cualquier manera concebible. El diseño IBM diseñar no es ortogonal. Solamente dos combinaciones de operaciones son legales fuera de las cuatro posibilidad, y se requiere dos diferente instrucciones, A y AR. El diseño IBM es ser más estricto y por lo tanto menos fácil de escribir, por ejemplo, usted no puede agregar un valor y colocarle el valor en una localización de memoria. Además, el diseño de IBM es más difícil de aprender debido a las restricciones y adiciones de la instrucción. La ortogonalidad se relaciona de cerca con la simplicidad: cuanto más ortogonal es el diseño de un lenguaje, menos son las excepciones que se requieren de las reglas del lenguaje. Pocas excepciones significan un grado más alto de regularidad en el diseño, que hace que el lenguaje sea más fácil de aprender, leer y entender. Cualquiera que ha aprendido una parte significativa del lenguaje inglés puede atestiguar la dificultad de la multiplicidad de aprender las excepciones de una regla (por ejemplo, i antes de e excepto después c). Sin embargo Pascal es un moderno lenguaje relativo, su diseño fue un largo número de tipos de reglas inconsistentes para ser seguidas. Los procedimientos pueden tener a los dos tipos de parámetros “var” y “value”, a menos que el procedimiento mismo lo pase como un parámetro. La función puede retornar solamente tipos no estructurados. Un parámetro de tipo formal para ser llamado; ello no puede ser completado con la descripción de tipos. Los archivos no pueden ser pasados por valor. La lista de regla en y en. En otras palabras los tipos de reglas de Pascal no son ortogonales. Demasiado ortogonalidad también puede causar problemas. Quizás el lenguaje de programación más ortogonal es ALGOL 68. Cada construcción del lenguaje en ALGOL 68 tiene un tipo, y no hay restricciones en esos tipos. Además, la mayoría de las construcciones producen valores. Esta libertad de combinaciones permite construcciones extremadamente complejas. Por ejemplo, los símbolos condicionales pueden aparecer como los izquierdos en las asignaciones, junto con la declaración y otras declaraciones clasificadas, mientras el resultado es una localización. Esta forma extrema de ortogonalidad conduce a una innecesaria complejidad. Además, porque los lenguajes requieren una gran cantidad de primitivas, un alto grado de ortogonalidad da lugar a una explosión de la combinación. Tan incluso si las combinaciones son simples, sus números escarpados conducen a la complejidad. La simplicidad en un lenguaje, por lo tanto, es por lo menos en parte un resultado de una combinación de un número relativamente pequeño de construcciones primitivas y del uso limitado del concepto del ortogonalidad. Algunos creen que los lenguajes funcionales ofrecen una buena combinación de la simplicidad y de ortogonalidad. Un lenguaje funcional es aquél que: sus operaciones son hechas aplicando funciones a parámetros dados. En contraste, los lenguajes imperativos, tales como C y PASCAL, las operaciones se especifican sobre todo con variables y declaraciones de asignación. El Lisp es actualmente el lenguaje más extensamente usado que utiliza la programación funcional. El Lisp no es un lenguaje funcional puro, porque también contiene algunas características de lenguaje imperativo. Los lenguajes funcionales ofrecen potencialmente la simplicidad total más grande porque pueden lograr todo con una sola construcción, la llamada de la función, que, se puede combinar con otras llamadas de funciones de manera simple. Esta elegancia simple es la razón por la que atraen al investigador de muchos lenguajes a los lenguajes funcionales, como alternativa primaria a los lenguajes no funcionales complejos, tales como Ada y PL1 Sentencias de Control. La programación estructurada revoluciona en la década de 1970 fue un reacción para la pobre legibilidad causada por la limitadas estructuras de control de algunos lenguajes surgidos en la décadas de 1950 y 1960. Particularmente, se reconoció ampliamente que el uso indiscriminado de las declaraciones del goto reduce seriamente la legibilidad del programa. Los programas que se pueden leer de arriba hacia abajo (top –down) son mucho más fáciles de entender que los programas que requieren saltar visualmente la lectura del programa a partir de una declaración a otra declaración no adyacente para seguir el orden de la ejecución. Sin embargo, en cierto lenguaje, los gotos que ramifican hacia arriba son a veces necesarios; por ejemplo, se requieren para construir los ciclos MIENTRAS QUE en FORTRAN 77. Sin embargo, las siguientes restricciones del goto puede hacer que los programas sean menos legibles: 1. Deben preceder sus tarjetas, exceptúan cuando están utilizados en los ciclos 2. Que sus tarjetas deben nunca ser demasiado distantes. 3. El número de gotos debe ser limitado Las versiones de Basic y de FORTRAN que estaban disponible en los años 70 le faltaban el control de sentencias que permitían fuertes restricciones en el uso de gotos, así que los programas eran altamente legibles pero la escritura en esos lenguajes era difícil. La mayoría de los lenguajes de programación se diseñaron desde los últimos años de la década de 1960, sin embargo, han incluido suficientes sentencias de control que se necesitaban para eliminar en las declaraciones el uso de goto. Así, el diseño de las sentencias de control en un lenguaje puede ser un factor importante para la legibilidad de los programas escritos en ese lenguaje. Estructura de Datos. La presencia de facilidades adecuada para la definición de estructuras de datos en un lenguaje es otra característica significativa de la legibilidad. Por ejemplo, se supone que un tipo de dato entero es usado para una bandera indicadora porque no hay tipos de datos boleanos en los lenguajes. En tal lenguaje, nosotros podemos tener una sentencia semejante a la siguiente: Suma_es_muy_grande ≔ 1 Mientras este significado no es muy claro, entonces en un lenguaje que incluye los tipos booleanos nosotros podemos tener la sentencia: Suma_es_muy_grande ≔ true Entonces este significado es perfectamente claro, de manera similar, los tipos de datos registro proveen un método para representar como se emplean los registros que es más legible que usar una colección de arreglos: un arreglo para cada ítem de dato en un registro empleado, el cual es mas usado en los lenguajes que no tiene el tipo registro Diseño de sintaxis. La sintaxis o forma de los elementos de un idioma tiene un efecto significante en la legibilidad de los programas. Lo siguientes son tres ejemplos de diseño sintáctico de opciones que afectan la legibilidad Forma de los identificadores. Los identificadores restringidos a longitudes muy cortas disminuyen la legibilidad, si los identificadores pueden tener seis caracteres a lo sumo, como en FORTRAN 77, no es a menudo posible usar nombres connotativos para las variables. Un extremó más es el ejemplo del Instituto Nacional Estándar Americano original (ANSI) (BASIC) (ANSI 197b) en que un identificador pudiera consistir de una sola carta seguida de otra. La posibilidad del carácter de conexión como lo raya baja de los identificadores es una gran ayuda a la legibilidad SUMA_DE_CUADRADOS en ciertamente el que SUMADECUADRADOS. Se discuten otros problemas del plan acerca de las formas de identificadores en el capítulo 4. Las palabras especiales. La apariencia y la legibilidad del programa es fuertemente influenciado por las formas de palabras especiales de un idioma (por ejemplo begin, end, y for) especialmente importante en el método de formar declaraciones compuestas o grupos de las declaraciones principalmente en estructuras del mundo. Varios idiomas usan emparejando pares de palabras especiales o símbolos para formar grupos. Pascal requiere begin, los pares del extremo a los grupos de las formas para todo el mando de estructuras excepto repeat, declaraciones en las que ellas pueden omitirse (otros ejemplos de la falta de Pascal es la ortografía). C usa abrazaderas para el mismo propósito. Los dos de estos idiomas sufren porque siempre se termina los grupos de las declaraciones de la misma manera, que hechuras si es difícil determinar que grupos esta acabándose cuando un extremo aparece. FORTRAN 77 y ADA hacen a este el que despeja cuando una sintaxis del cierre distinta para cada tipo de grupos de las declaraciones para los ejemplos, ADA usa un extremo si para terminar una estructura de la selección y vuelta del extremo para una estructura de la vuelta. Esto es y ejemplo de conflicto entre simplicidad que es el resultado de usar palabras reservadas, como en Pascal, y la legibilidad mayor que los resultados de usar palabras reservadas, como ADA. Otro problema importante si se pueden usar las palabras especiales de un idioma como nombres para las variables del programa. En ese caso, los programas resultantes pueden ser muy confusos, por ejemplo en FORTRAN 77 la apariencia especial de estas palabras en un programa puede o no encontrar algo especial. Forma y significado. Declaraciones anteriores para que en su apariencia, por lo menos parcialmente, indica que su propósito es una ayuda obvia a al legibilidad. Semántica o significado debe seguir directamente de la sintaxis o apariencia. En algunos casos, este principio es violado a través de dos idiomas por las estructuras que son similares en apariencia pero tienen significados diferentes. En FORTRAN 77, por ejemplo, hay dos declaraciones, el GOTO asignado y los GOTO computados cuyas apariencias son muy similares pero cuyo los significados son diferentes, aunque los dos son múltiples saltos alejados. Por ejemplo, las declaraciones GO TO (10, 20, 30), I GO TO I, (10, 20, 30) Se usa bastante diferentemente. En el primero, la variable I es de tipo ENTERO; en el segundo, etiqueta valores. Una de las quejas primarias sobre los órdenes de la cáscara de siempre haga pensar en su función. Por ejemplo, el comando grep de UNIX puede ser descifrado a través de conocimientos anteriores, o quizás la destreza y familiaridad con el editor, ed de UNIX. Su apariencia no connota nada a los principiantes de UNIX. En ed [comando/ expresión_reguladora] busca una subcadena igual a la expresión_reguladora. Precediendo esto con hechuras de g, haciendo de esto un comando global que hace que el archivo entero se revise hasta alcanzar la búsqueda. Siguiendo el comando con p se especifica esa línea con la subcadena equivalente para que se imprima. Para que el g/expresión_reguladora/p, pueda abreviarse, por su puesto como grep, las impresiones de todas las líneas en un archivo que contiene subcadena que son iguales a la expresión_reguladora Fácil Escritura. Escriturabilidad es que la medida de qué fácilmente un lenguaje puede usarse para crear programas para un dominio del problema escogido. La mayoría de las características del lenguaje que también efectúan legibilidad la escriturabilidad de efecto. Esto sigue directamente del hecho que el proceso de escritura un programa exige la frecuencia del programador de releer las porciones del programa existente. Rescriturabilidad debe ser considerado en el contexto del dominio del problema designado de un lenguaje en el reino de una aplicación particular y el otro no era. Por ejemplo, la escriturabilidad de COBOL y APL es extremadamente diferente para crear un programa para tratar con estructura de datos bidimensionales, por que de los dos APL es ideal. Sus escriturabilidades también son bastante diferentes para la pizca de los informes producto de los formatos complejos, para eso es la finalidad por lo que COBOL se diseñó. Las subdivisiones siguientes describen los factores más importantes que influyen en la escriturabilidad de un lenguaje Sencillez y Ortogonalidad. Sí un lenguaje, tiene una gran cantidad de diversas construcciones, algunos programadores pueden no ser familiares con todos ellos. Esto puede conducir a un uso erróneo de algunas características y de un desuso de otros que puedan ser más elegantes o más eficientes, o ambos, que los que se utilicen. Esto puede ser posible, según lo observado por Hoare, para utilizar características desconocidas accidentalmente, cuando los resultados son extraños. Por lo tanto, un número pequeño de primitivas construcciones y del conjunto de las reglas constante para combinarlas (esto es ortogonalidad) es mucho mejor que simplemente teniendo una gran cantidad de números primitivos. Un programador puede diseñar una solución para un problema complejo después de aprender solamente un conjunto simple de construcciones primitivas. En la misma vena, demasiada ortogonalidad puede ser detrimento de la fácil escritura. Los errores en programas de la escritura pueden ir desapercibidos cuando casi cualquier combinación de primitivos es legal. Esto puede conducir a las absurdidades en el código que puede no ser descubierto por el compilador. Soporte para la abstracción. Abreviadamente, medios de la abstracción, la capacidad de definir y después de utilizar las estructuras complicadas de operaciones de manera que muchos de los detalles. La abstracción es un concepto dominante en los lenguajes de programación contemporáneos. Esta es reflexión del rol central que las abstracciones juegan en la designación de las metodologías de la programación moderna. El grado de la abstracción que permite un lenguaje de programación y la naturaleza de sus expresiones es muy importante para su codificación. Un ejemplo simple de un proceso de abstracción es el uso de un subprograma en la implementación de un algoritmo, que en ejecución se requiera varias veces en un programa. Saliendo del subprograma, el código tendría que ser replegado en todos los lugares donde sea necesario, que haría el programa mucho más largo y más aburrido para la escritura. Más importante, si el subprograma no fue utilizado, el código del subprograma podría ser eliminado del algoritmo y así evitar, el entorpecimiento del flujo del código. Como ejemplo de la abstracción de los datos, considere un árbol binario que salve datos del número entero en sus nodos. Un árbol binario sería puesto en ejecución generalmente en FORTRAN 77 en tres matrices paralelas del número entero, donde dos de los números enteros se utilizan como subíndices para especificar nodos del descendiente. En PASCAL, estos árboles pueden ser implementados usando una abstracción de un nodo del árbol en la forma de una unidad de registro simple con dos punteros y un número entero. Lo natural de la última representación hace mucho más fácil escribir un programa de PASCAL que utilice un árbol binario que para escribir uno en el FORTRAN. El dominio de la solución del problema del lenguaje está más cercano al dominio del problema. La ayuda total para la abstracción es claramente un factor importante en la codificación del lenguaje. Expresividad. La expresividad en un lenguaje puede referirse a varías características diferentes. En un lenguaje como APL, sus operaciones existentes cuentan con poderosos operadores que permiten un problema grande de computación pueda realizarse con un pequeño programa. Generalmente los medios que un lenguaje tiene son relativamente cómodos antes que incómodas rutas de especificación computacional. Por ejemplo, en C, la expresión Contador++ es más cómoda y más pequeña que Contador = Contador + 1. También el operador boleano AND THEN de Ada es un camino cómodo para especificar la valoración de expresiones booleanas de corto circuito. La inclusión de la declaración FOR en Pascal hace más fácil de escribir ciclos que con el uso de WHILE. Todo esto ayuda a incrementar la facilidad de escritura en un lenguaje de programación. Confiabilidad. Un programa es confiable si ejecuta sus especificaciones bajo todas las condiciones. Aunque una meta deseable en los lenguajes de programación es la facilidad para permitir el diseño de programas confiables, pero no es completamente claro que ésta sea una característica para la estimación y comparación entre los lenguajes de programación. Legibilidad y Fácil Escritura. Ambos facilidad de escritura y la legibilidad implica confiabilidad un programa escrito en un lenguaje que no soporta formas naturales para expresar los algoritmos requeridos necesitara métodos de uso menos posible corregir en todas las situaciones. El programa más fácil para escribir, es el más conveniente para corregir. Legibilidad da confiabilidad en ambas fases del cielo de vida de mantenimiento y escritura. Los programas que son difíciles para leer también son difíciles de escribir y modificar. Verificación de Tipos. La verificación de tipos es probar la compatibilidad de los tipos entre dos variables o una variable y una constante, es de algún modo la relación de uno con otro. Dos de las formas más comunes de tales impedimentos son los operadores de las operaciones aritméticas y el resultado de la operación, teniendo en cuenta ambas partes (izquierda y derecha) del signo de asignación. La verificación de tipos es un factor importante en la confiabilidad del lenguaje. Esto se debe a que la verificación de tipos en el tiempo de ejecución es bastante cara, por lo que es más deseable la verificación de tipos en el momento de la compilación. Además que permite de una manera más fácil la detección de errores, por lo que es menos costoso la reparación de los mismos. En el diseño de los lenguajes contemporáneos, como el ADA, se requiere la verificación de tipos de casi todas la variable en tiempo de compilación, salvo en el caso de que el usuario utilice estados explícitos entonces la verificación de tipos se suspende. Esto elimina la posibilidad de errores en tiempo de ejecución en los programas realizados en ADA. La manera de cómo fracasa la verificación de tipos tanto en tiempo de compilación como en el de ejecución, es manejado por los innumerables errores de los programas que envuelven a los parámetros de los subprogramas en el lenguaje C. En este programa el tipo de parámetros reales en una llamada a función no se verificaba para determinar si correspondiente parámetro formal de la función era el mismo. Por ejemplo una variable de tipo int puede ser usada como parámetro real en una llamada a una función que espera un float en su parámetro formal, por lo que el compilador descubre el problema de la inconsistencia en el tiempo de ejecución, por lo que da como resultados una serie de Problemas, recurso del cual es con frecuencia difícil de determinar (En respuesta a este problema, el sistema “UNIIX” incluye un programa de utilidad llamado “LINT” que checa programas en C para dichos problemas), Métodos de parámetros – paso y subprogramas son discutidos en el capítulo 8. En General, el rango escrito de una variable de un arreglo es parte del tipo de chequeo. Por eso el chequeo del rango escrito es parte del tipo de chequeo, aunque debe ser realizado al momento de correrlo. Porque muchos tipos son checados en Pascal, los rangos del algoritmo también son checados. El chequeo es extremadamente importante para la confiabilidad del programa, debido aquel rango de salida de algoritmo con frecuencia causa errores que no aparecen hasta mucho después de las violaciones actuales. Manejo de Excepciones. La habilidad de un programa para detectar errores al momento del corrido, así como otras condiciones inusuales, para tomar medidas correctas y para continuar es una gran ayuda de confiabilidad. Esta facilidad es llamada Manejo de excepción; El lenguaje “Ada” incluye capacidades extensas para manejo de excepción, pero las facilidades son prácticamente no existen en muchos lenguajes usados como Pascal, C, y Fortran. Restricción de Alias. De manera holgada el alias quiere decir que se tienen dos métodos de referencia distintos, o dos nombres, para la misma celda de memoria. Ahora es muy aceptado que el alias represente un peligro en un lenguaje de programación, lo anterior, porque a la mayoría de los lenguajes de programación se les permite hacer algunos tipos de alias, por ejemplo: variables equivalentes en Fortran y punteros en Pascal. En ambos casos, las dos variables de diferentes subprogramas pueden significar la misma celda de memoria; algunos tipos de restricciones de alias pueden ser prevenidos por la designa aún del lenguaje. En algunos lenguajes, el alias es usado para prevenir deficiencias en la facilidad de abstracción de datos del lenguaje. Otros lenguajes restringen extremadamente el alias para incrementar su confiabilidad. Costo. El costo total de los lenguajes de programación es una función de muchas de estas características. Los primeros costos de entrenamiento y el costo de la escritura del programa en un lenguaje puede reducirse significativamente en un buen ambiente de programación. Entrenamiento del programador. Esta es una función de la simplicidad y ortogonalidad del lenguaje, esto aproxima en propósito a una aplicación en particular, y a la experiencia de los programadores. Aunque el más poderoso de los lenguajes no necesita ser duro para leer, y ellos frecuentemente lo son. Escritura del programa. Esta es una función de la fácil escritura de los lenguajes. El esfuerzo original para diseñar e implementar un lenguaje de alto nivel es manejado por el deseo para bajar el costo de creación de software. Compilación. El tercero es el costo de compilación de los programas en el lenguaje. Un impedimento principal para el uso adelantado de Ada fue la prohibición de los altos costos de ejecución en la tercera generación de los compiladores Ada. Este problema es conveniente en compiladores menos seguros como el Ada llega a ser el más apropiada. Ejecución. El cuarto es el costo de ejecución de programas escritos en un lenguaje es influenciado grandemente por el diseño del lenguaje. Un lenguaje, tal como el PL1, que requiere mucho tiempo de ejecución para la verificación de los tipos impide que el código se ejecute rápidamente, a pesar de la calidad del compilador. Mantenimiento. Finalmente tenemos al costo de mantenimiento de programas, con la cual se incluyen las correcciones y modificaciones para añadir nuevas características. El costo de mantenimiento depende de un gran número de características del lenguaje especialmente la legibilidad. Porque el mantenimiento es realizado frecuentemente por otras personas diferentes al autor original del software, la pobre legibilidad puede hacer lar tarea extremadamente desafiante. La importancia del mantenimiento del software no puede ser desafiante. Esto se ha estimado para un software de aplicación de aplicación con una vida relativamente larga, los costos de mantenimiento pueden subir de dos a tres cuartos de tiempo tanto como el desarrollo. Todas estas contribuciones para los costos de un lenguaje nos indican que hay dos muy importantes: el de desarrollo y el de mantenimiento de los programas. Porque son las funciones de legibilidad y fácil escritura las que intervienen en ellos, y éstos son los dos criterios más importantes en la evaluación de los lenguajes de programación. Una nota final para el criterio. Los criterios, especialmente legibilidad y escriturabilidad, no se pueden precisar en su definición ni limitarse de una manera exacta. Los conceptos son útiles sin embargo nos proporcionan una perspicacia en el diseño y evaluación de los lenguajes de programación. Existen por supuesto otros criterios para la evaluación de los lenguajes de programación, por ejemplo la portabilidad (la facilidad con el que el programa puede ser movido de una máquina a otra), su generalidad (la aplicación del lenguaje a un extenso ramo de aplicaciones) y sobre todo Buenas Definiciones (la integridad y precisión del lenguaje en la definición de la documentación). Sin embargo las que hemos explicado consideramos que son las más importantes.
© Copyright 2024