Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Este capítulo intenta dar un tratamiento unificado del tema, con profundidad suficiente para ser
útil personas que realmente diseñan un lenguaje de programación.
Aunque el primer primitivo los lenguajes de programación surgieron hace más de 25 años, hasta
hace poco tiempo hubo poco orden en el proceso de diseño de nuevos lenguajes.
Los primeros idiomas fueron pioneros, explorando un nuevo campo. No lo es sorprendente que
estaban mal diseñados.
Después del desarrollo inicial de lenguajes de alto nivel y la implementación de los primeros
compiladores, se produjo un período bastante largo en el que se hicieron intentos para diseñar
nuevos lenguajes sin los defectos de los antiguos. La mayoría de estos intentos fueron fracasos, no
tanto por la falta de ideas sobre cómo diseñar mejores idiomas a partir de un exceso de ideas. Un
buen ejemplo de este proceso es el noción de que "si pudiera significar algo, debería" (Radin y
Rogoway, 1965), que llevó a PL / I.
Una proposición básica de todo este capítulo es que un buen lenguaje no es solo un azar colección
de características, pero un todo unificado.
Casi. Cualquier enfoque alternativo será más simple y más rápido que intentar el tarea difícil y
lenta de diseñar un lenguaje completamente nuevo.
¿Hay un idioma existente que pueda usarse para llenar el requisito? Incluso si requiere una nueva
implementación, implementar un lenguaje existente es más fácil y más rápido que diseñar y luego
implementar un nuevo idioma.
¿Se puede extender un idioma existente? Es más fácil diseñar una extensión limpia a un idioma
existente, "incluso si la extensión \ implica un compilador nuevo, que diseñar un idioma
completamente nuevo. Si se toma este enfoque, sin embargo, se debe tener cuidado tomado para
no hacer la extensión tan grande y compleja que se convierta, en efecto, nuevo idioma. En tales
casos, la necesidad de mantener alguna interfaz con el viejo el lenguaje probablemente
comprometerá seriamente el diseño de la extensión. También si uno está extendiendo un idioma
existente, es necesario seleccionar el idioma base cuidadosamente para que el trabajo de
extensión se minimice y la extensión se ajuste con gracia en el lenguaje. El objetivo debe ser
producir un lenguaje que es algo más grande pero igualmente bien construido.
Quizás no haya otro problema relacionado con la computadora que se vea tan tentadoramente
fácil y es tan terriblemente difícil como hacer un buen trabajo de diseño del lenguaje. Prescindir de
la idea de que es posible impulsar un diseño durante el fin de semana y comenzar implementar un
traductor para ello el lunes. Un mes después, todavía habrá puntos menores de diseño del
lenguaje para resolver, y la implementación tendrá conseguido casi en ninguna parte.
Suponiendo que se haya tomado la decisión de que ninguno de los precedentes los enfoques
serán suficientes, el siguiente punto de interés es:
Un lenguaje a menudo está diseñado específicamente para un área de aplicación. Cuanto más
atención que se le da a la restricción de la aplicación de área del idioma, la mejor será el idioma
para problemas en el área. De hecho, no es recomendable intentar diseñar un lenguaje de
propósito general adecuado para cualquier p! ~ blem. AIVsuch los intentos hasta la fecha han sido
desalentadores (especialmente PL / I y ALGOL 68).
En la actualidad, toda evidencia indica que nadie sabe cómo hacer un trabajo adecuado de
diseñando un lenguaje que será "bueno para todo".
Finalmente, la relación del nuevo idioma con los idiomas existentes debe ser considerada.
Weinberg (1971) discute el fenómeno psicológico de "i ~ hibition" que ocurre cuando un idioma
antiguo y un nuevo idioma son similares pero no idéntico. El usuario está sujeto a una seria
confusión debido a la incertidumbre sobre cómo gran parte del lenguaje antiguo se traslada al
nuevo. Por ejemplo. FORTRAN
Los programadores que aprenden E / S con formato PL / I tienen grandes problemas con los tipos E
y F formatos, que son similares pero no idénticos a FORTRAN equivalente construcciones
En resumen, es mejor hacer que el nuevo idioma sea claramente diferente de en lugar de muy
similar a cualquier idioma existente. Si los idiomas nuevos y antiguos son tan similares, tal vez la
necesidad del nuevo idioma no ha sido adecuadamente examinada.
3-3 FUENTES DE IDEAS
En términos generales, los constructos derivados del lenguaje natural son valiosos para su
obviedad y legibilidad. Lo hacen (al menos aproximadamente) lo que dicen. Esta es una gran
ventaja incluso para los programadores experimentados y puede ser crucial para idiomas
destinados a principiantes y no programadores. Por otro lado, el problema de inhibición con
respecto a otros lenguajes de programación puede operar aquí. Los idiomas que se parecen
demasiado al inglés pueden producir tasas de error más altas ya que los usuarios intentan otras
construcciones en inglés solo para descubrir que no lo hacen trabajar como se espera Usar el
lenguaje natural como modelo también puede resultar en un exceso palabrería. COBOL es un
ejemplo obvio. Tenga en cuenta también que los lenguajes naturales a menudo contienen mucha
amiguidad, lo cual es indeseable en un lenguaje de programación.
En términos generales, una buena estrategia es utilizar el lenguaje natural como guía cuando
diseñando la sLuego pasamos a las matemáticas como fuente de ideas. Matemáticas ha sido un
fuente importante de convenciones y dispositivos utilizados en lenguajes de programación (más
obviamente, la expresión aritmética). Es completamente posible que nuevo los lenguajes de
programación incorporarán otros préstamos de las matemáticas.
En general, los programadores y los matemáticos usan diferentes métodos y resuelven diferentes
problemas al trabajar hacia diferentes objetivos. Se debería notar en particular que, para lograr la
brevedad que es tan importante para manipulación compleja de fórmulas, los matemáticos a
menudo muestran una completa indiferencia por claridad y obviedad. Esto se ejemplifica en la
increíble proliferación de juegos de caracteres en notación matemática. Mientras que las
matemáticas son una fuente útil de ideas, particularmente con respecto a los tipos de datos, el
diseñador del lenguaje debe ser muy cuidado con la adopción de la notación matemática para un
concepto dado. Considerar, por ejemplo, el lenguaje APL, que está fuertemente orientado
matemáticamente. Un verdadero problema de implementación que surge es la representación de
algunos de los APL símbolos en dispositivos de E / S tradicionales. sintaxis detallada de nuevos
constructos, mientras se evita su influencia en otra parte Los lenguajes de programación
existentes pueden ser la mejor fuente de ideas para diseñadores de lenguaje de programación. Los
diseñadores deben tener mucho cuidado al incluir tales ideas en su propio producto, sin embargo,
porque los diseñadores en el p cometió serios errores en el diseño.
Se pueden enunciar algunos principios básicos para distinguir buenas ideas dignas de la
perpetuación de malas ideas dignas de extinción. Quizás el mayor principio es preguntar "¿Por qué
se hizo de esa manera?" Una vez que obtenga una respuesta a esta pregunta, pregunte "¿Es esa
razón (todavía) válida?" A menudo la respuesta a esta pregunta será no. Por ejemplo, las extrañas
restricciones de FORTRAN sobre los subíndices de matriz se remontan a su primera
implementación: los implementadores deseaban hacer eficiente uso de las características de
direccionamiento de su hardware y tenían miedo de que esto podría no se debe hacer si se
permitió alguna expresión como subíndice. Aunque esto puede quizás se considere razonable (o al
menos comprensible) en las circunstancias, ciertamente no es defendible hoy, considerando la
seria reducción en usabilidad que resulta.
También vale la pena recordar que incluso si el veredicto general sobre un el lenguaje es "un mal
diseño", esto no significa que no oculta características que valen la pena en algún lugar en el
fondo. Por ejemplo, aunque APL puede ser criticados en muchos frentes, sus poderosos
operadores de manipulación de matrices pueden ser vale la pena copiar
Del mismo modo, el hecho de que una característica esté comúnmente disponible no puede
implicar que es una buena idea. Muchos idiomas han seguido el liderazgo de ALGOL 60 al permitir
tamaño de matrices que se decidirá en el tiempo de ejecución, una característica que introduce
considerables problemas de implementación e interfiere con la verificación de errores en tiempo
de compilación. Esta la característica puede tener un valor limitado en ciertas áreas de
aplicaciones. El fenómeno de las declaraciones predeterminadas, heredadas de FORTRAN, es otro
ejemplo de mal diseño del lenguaje. Esta característica en particular ilustra el hecho de que
algunos las características actualmente populares son, de hecho, muy perjudiciales para la calidad
del programa.
Otra consideración es que una característica mal diseñada puede ser el resultado de tratando de
cumplir dos requisitos al mismo tiempo, con el resultado de que uno de ellos (quizás el más
conspicuo) está mal servido. Un ejemplo de esto es el parámetros de paso-como-si-por-macro-
sustitución de ALGOL 60 (también conocido como "passby-name"), que parecen haber resultado
de la confusión de las dos nociones "procedimiento", un concepto lógico y "macro", una técnica de
implementación para procedimiento.
Por ejemplo, parte de la motivación para las fuerzas anti-GOTO proviene de la observación de que
los buenos programadores no usan todo el poder del GOTO-ellos use el GOTO para simular
estructuras menos generales pero más comprensibles.
Del mismo modo, al observar los patrones de uso establecidos, uno puede determinar qué las
características son deseables. Por ejemplo, mediciones de programas reales por
Alexander (1972) estableció que dos tercios de todas las ifs no tienen un ELSE cláusula; por lo que
probablemente sería irrazonable requerir el ELSE en todas las FI. Los noción relativamente
reciente de experimentar con cambios de diseño (Gannon, 1975) bajo condiciones controladas
ofrece básicamente los mismos tipos de conclusiones. Indudablemente la investigación continuará
en las áreas de medición del lenguaje de programación uso y experimentación en el diseño de
lenguaje de programación.
Cuando se diseña un lenguaje de programación, se debe prestar especial atención a los objetivos
del lenguaje. Una serie de objetivos importantes, como la comunicación humana, la prevención y
detección de errores, usabilidad, efectividad del programa, compatibilidad, eficiencia,
independent machine, and simplicidad, se describen en giro.
Esta sección también se ocupa de las filosofías de diseño. Filosofías como uniformidad,
ortogonalidad, generalidad, modularidad del lenguaje, minimización y nivel de la abstracción se
discuten.
Es importante 'darse cuenta de que los problemas de la comunicación humana no pueden dejarse
enteramente a comentarios o documentación externa. A los programadores no les gusta
escribiendo comentarios excesivos y tienden a evitarlos. La documentación externa es muy a
menudo desactualizado e incompleto. Además, esto a veces puede aplicarse a documentación
interna. Una buena y muy confiable forma de documentación es la programa en sí mismo, si es
legible. Los lenguajes de programación deben diseñarse con atención constante a la claridad y
comprensibilidad.
Es vital distinguir entre legibilidad y escritura. Es importante ser capaz de escribir programas
fácilmente. Es necesario poder leer programas fácilmente. La legibilidad de los programas es
mucho más importante a largo plazo que su capacidad de escritura (Hoare, 1973). Esto es de
particular importancia porque muchos las características "potentes" y "convenientes" tienden a
producir monumentalmente oscuro programas (por ejemplo, APL "one-liners").
La implicación más básica del problema de legibilidad para el diseño de los lenguajes de
programación es que la sintaxis debe reflejar la semántica. Si la sintaxis del lenguaje de
programación no refleja de manera precisa y completa las manipulaciones que se están llevando a
cabo, hay pocas esperanzas de comprender el programa. Hacer que la sintaxis coincida con la
semántica implica varias cosas. Los la sintaxis debe establecer claramente lo que está sucediendo
con suficiente detalle como para ser comprensible pero no en detalles tan innecesarios como para
ser abrumador. Cuidado debe ser tomado para asegurar que una construcción que realiza una
operación particular no lea como si estuviera haciendo algo similar, pero no del todo. Además, es
más indeseable para acciones significativas que se llevarán a cabo sin ninguna indicación en la
sintaxis en absoluto. Esta regla puede parecer obvia, pero un número considerable de idiomas lo
violan (notablemente PL / I con sus conversiones y unidades ON, y ALGOL 68 con sus coacciones).
Una faceta final que debe mencionarse con respecto a la comunicación humana a través de los
lenguajes de programación es que los programadores no son computadoras. Sólo porque un
compilador puede entender una construcción dada, no hay garantía de que programador puede.
Las limitaciones de la mente humana lo hacen bastante fácil para una estructura complicada para
volverse inmanejable, mientras que el compilador no tiene problema. Por ejemplo, un algoritmo
de pila simple maneja construcciones anidadas para un compilador. Los seres humanos, sin
embargo, probablemente no funcionen en forma de pila manera, y las construcciones
profundamente anidadas pueden muy fácilmente volverse completamente incomprensibles.
Los seres humanos están mal equipados para hacer frente a situaciones en las que el efecto de
una constructo depende de una manera compleja en el contexto (como puede surgir a través del
uso de PL / I 0 unidades). También es muy posible que una construcción sea ambigua a una
humano Whl es claro y obvio para un compilador (Weinberg, 1971). Un bien ejemplo de th es la
expresión aritmética simple "a / b / c". En general, un analizador no considerará esto ambiguo,
pero ¿cuántos programadores humanos escribirían sin usar paréntesis? Aunque puede tomar más
tiempo para un compilador para rechazar tal construcción, la protección puede valer la pena.
Es un hecho reconocido, los programadores de lat cometen errores. Aunque es muy actual el
trabajo en el campo de la ingeniería de software está dirigido a detener los errores en el fuente (el
programador), no hay posibilidades previsibles de que se cometan errores eliminado por
completo. Por lo tanto, es necesario ayudar al programador en la tarea de no solo prevenir sino
también detectar, identificar y corregir errores. UN un buen lenguaje de programación puede ser
un factor importante en esto. De hecho, los esfuerzos de Lampson et a1. (1977) en el diseño de
EUCLID, un lenguaje basado en PASCAL destinados a la expresión de programas del sistema que
deben verificarse, indica que un programa afectará significativamente la facilidad con la que se
puede verificar que un programa esté libre de errores. Sin dudas, habrá más investigación
conducido en esta área importante.
Uno de los servicios más fundamentales que realiza un lenguaje de alto nivel es hacer ciertas
clases de errores imposibles. Un buen ejemplo de esto es el viejo
Un beneficio similar de los lenguajes de alto nivel es que ciertas clases de errores son hecho
improbable, aunque no imposible. Por ejemplo, el programador es mucho es menos probable que
se produzca un error de flujo de control en un lenguaje como PASCAL que en lenguaje
ensamblador, porque PASCAL proporciona construcciones que organizan el control fluye mejor
que las ramas condicionales del lenguaje ensamblador (¡o FORTRAN!).
Este tipo de prevención errM generalmente es una cuestión de proporcionarle al programador con
construcciones que son convenientes de usar, están cerca del programador procesos de
pensamiento, e imponer restricciones razonables en el programa.
Incluso con toda la ayuda posible en la prevención de errores, algunos errores seguirán hacerse.
Por lo tanto, es deseable que el lenguaje de programación detecte errores y hacerlos tan visibles
como sea posible, para que puedan ser eliminados.
La detección de errores se basa en una redundancia útil. Un error es detectado por notando que
dos porciones del programa, llevando algo de la misma información, no coinciden entre sí.
Cabe señalar que no toda la redundancia es una redundancia útil. Una excelente ejemplo de
redundancia que no solo es inútil sino que puede ser activamente dañino es necesidad · en
FORTRAN de repetir las definiciones COMUNES en cada programa componente usándolos. Un
error en una de tales repeticiones generalmente pasa desapercibido y generalmente tendrá serias
consecuencias.
La redundancia útil se puede construir fácilmente en un idioma. Lo más significativo Las fuentes de
redundancia útil son declaraciones obligatorias de todas las variables y verificación de tipo fuerte
(del tipo utilizado en ALGOL y PASCAL). Un problema lateral de la verificación de tipo fuerte es la
ausencia de conversiones automáticas o coacciones que intentan convertir el tipo de datos
proporcionado en uno que es "obviamente" deseado en el contexto. Tal fraude detrás de escena
derrota gran parte del valor de verificación de tipo y puede dar como resultado un código oscuro.
Por ejemplo, en ALGOL 68 (que tiene coerciones), si x es un puntero a entero, la variable que no
puede ser alterado por la tarea
x:= x + 1
Habiendo discutido algunos de los aspectos más importantes de la prevención de errores, deja que
volvemos nuestra atención a la detección de errores. La detección de errores debe ser confiable.
La detección de errores no fiable es peor que ninguna, ya que los programadores tienden a confía
en él cuando no deberían. Esto puede provocar errores que son casi imposible de encontrar. Un
ejemplo de esto es el tipo de "unión" de ALGOL 68 (p. el valor de una variable puede ser real o
entero), que no se puede verificar en tiempo de compilación. Si uno combina módulos compilados
por separado en un solo programa, es casi imposible proporcionar una verificación
completamente confiable para sindicatos sin gastos ruinosos.
La detección de errores también debe ser precisa. El usuario debe ser informado exactamente
dónde y cómo se produjo el error, para que pueda analizarse y corregirse.
Aunque el mayor factor individual en esto es el compilador (ver Capítulo 5), el lenguaje el diseño
también tiene un gran impacto. Por ejemplo, un idioma con puntero sin restricciones las variables
(por ejemplo, PL / I) harán la detección y el análisis del puntero fugitivo errores casi imposibles
Las rutinas de soporte en tiempo de ejecución deben configurarse para que las verificaciones en
tiempo de ejecución sean simples y no excesivamente caro. Idealmente, debería ser tan simple y
barato de hacer el tiempo de ejecución comprueba que las verificaciones se pueden dejar para
ejecuciones de "producción", cuando la detección de errores es aún más vital. Como dice Hoare
(Hoare, 1973), "¿Qué haría pensamos en un entusiasta de la navegación que usa su chaleco
salvavidas cuando entrena en seco tierra, pero se lo quita tan pronto como se va al mar? "
3-4.3 Usabilidad
Pasamos ahora al tema de la usabilidad. Es obviamente importante que un lenguaje sea fácil de
usar. Ciertos aspectos de esta propiedad merecen una mención específica. El más importante
consideración, legibilidad, ya se ha discutido y por lo tanto no estar cubierto aquí.
Es deseable que las construcciones de un idioma sean fáciles de aprender y de recuerda. Una vez
que los programadores están familiarizados con el idioma, no debería ser necesario para que
consulten los manuales constantemente. El lenguaje debe ser tan simple y directo que la forma
correcta de hacer algo es razonable aparente. Por otro lado, no debería haber muchas formas
diferentes de hacer el lo mismo, ya que el programador caerá impotente tratando de decidir cuál
es el mejor. El mejor ejemplo de violación general de este aspecto de la usabilidad es ALGOL 68,
que puede hacer casi cualquier cosa, si el programador solo puede imaginar ¡cómo! PL / I es un
buen ejemplo de un lenguaje que proporciona muchos diferentes formas de hacer algo (la mayoría
de los idiomas se conforman con un tipo de entero; PL / I ¡tiene tres!).
Otro principio importante es "sin sorpresas", también conocido como la "regla de menos asombro.
"Un programador que ha hecho el esfuerzo de aprender el idioma debería ser capaz de predecir el
comportamiento de una construcción con precisión si puede ser predijo en absoluto. PL / I es el
principal mal ejemplo ~ está - cubierto de construcción esque no hacen lo que el programador
piensa, como se ejemplifica con FIXED división (véase Hughes, 1973).
Algunos lenguajes son muy adorados por su flexibilidad. La flexibilidad es como redundancia:
puede ser útil o inútil. Obviamente es necesario que las lenguas ser capaz de satisfacer demandas
que no pueden ser anticipadas por completo por sus diseñadores.
Sin embargo, la flexibilidad inútil puede ser un problema. Un ejemplo de esto es la característica
de ALGOL 60 que permite que un nombre variable sea redeclarado en un BEGIN interno bloque: el
compilador tiene que hacer frente a varias variables que tienen el mismo nombre, surgen
posibilidades desagradables para los errores del programador, y la característica es rara vez
utilizado (con la obvia excepción de los subprogramas). En general, inútil la flexibilidad debe
eliminarse siempre que sea posible. Si no interfiere demasiado en serio con otras consideraciones,
un idioma debe ser conciso. Desafortunadamente, la concisión tiende a interferir fuertemente con
legibilidad (por ejemplo, en APL). También hay un conflicto con la redundancia útil en esa
información repetitiva (la esencia de la redundancia) a menudo requiere más escribir (por
ejemplo, declarar todas las variables). La concisión debería ser probablemente dada la prioridad
más baja entre los diversos problemas de diseño.
Este tema se distingue de la anterior, usabilidad, por una bastante borrosa límite. Hablando en
términos generales, esta subsección se refiere a los aspectos de ingeniería del software del uso del
lenguaje en lugar de la conveniencia para el individuo programador.
Una gran preocupación en ingeniería de software es la grabación de las decisiones hecho durante
el desarrollo del programa. El programa en sí es el mejor lugar para grabar tales decisiones. El
lenguaje de programación debe facilitar una declaración clara de las intenciones del programador.
Esto implica, en particular, que la decisión debería no ser oscurecido debajo del montículo de
detalles requeridos para implementarlo.
Siempre que sea posible, el lenguaje debe permitir a los programadores declarar su quiere y hace
que el compilador realice la implementación. La mayoría de los constructos de alto nivel los
idiomas hacen esto hasta cierto punto. En los casos demasiado frecuentes donde la complejidad
es demasiado grande para el compilador o donde la cuestión de cómo implementar el la decisión
es en sí misma una decisión importante, el lenguaje debe cumplir con la implementación los
detalles deben estar claramente separados de la declaración de la decisión original. UN algunos
idiomas, como LIS (Ichbiah et al., 1973) y Ada, tienen secciones de especificación e
implementación en sus programas. El procedimiento la noción proporciona esta separación para el
código ejecutable. Ideas sobre cómo lograr la separación de cosas como las estructuras de datos
está mucho menos desarrollada.
Además de expresar claramente las decisiones, también es deseable poder localizar el efecto de
los cambios de decisión. Tales cambios son inevitables; no debería será necesario volver a escribir
todo el programa cuando se requiera un cambio. Esta idea conduce directamente a la noción de
abstracción, que es el concepto que un programador puede hacer uso de una noción particular
(por ejemplo, el tipo de datos "lista") sin necesidad de conocer los detalles de su implementación.
Esto simplifica enormemente el uso de tales segmentos de programa predefinidos, y es un factor
clave en el control de complejidad de los programas. Si se agrega la restricción de que el usuario
no solo no lo hace necesita saber los detalles de implementación de la abstracción, pero de hecho
no tiene manera para usar dicho conocimiento, la implementación de la abstracción puede ser
cambiado sin alteraciones a los programas que lo usan.
Los idiomas existentes lo soportan hasta cierto punto; el procedimiento nuevamente es el básico
herramienta. Desafortunadamente, se necesitan herramientas más poderosas para respaldar,
digamos, una "lista" abstracción correctamente Esta ha sido un área activa de investigación (Liskov
y ZiIles, 1974; Horning, 1976; Shaw, 1976; SIGPLAN, 1976), y los desarrollos en él pueden se espera
que sea importante para el diseño del lenguaje.
La cuestión del apoyo a la abstracción conduce también a la cuestión del apoyo para diversas
técnicas de construcción de programas, como la programación descendente.
Hay algunos problemas para proporcionar dicho soporte. En particular, mientras hay muchos
métodos diferentes, los nuevos todavía están evolucionando, y ningún método es claramente
superior a todos los demás. Si un lenguaje se va a usar con una programación específica
metodología, se debe prestar una atención definida a hacer que el idioma ayude el uso del
método; de lo contrario, el diseñador debe tratar de evitar la aplicación de cualquier técnica
particular.
3-4.5 Compitability
Un idioma debe ser compilable. Menos obviamente, debería ser lo más simple posible para
compilar sujeto a ninguna reducción importante en su eficacia comunicativa y usabilidad. La
escritura del compilador ya es un trabajo complejo, y cualquier aumento en su la complejidad
puede hacer que la tarea sea inmanejable en el sentido de que el el producto será más grande,
más complejo y, por lo tanto, menos confiable.
La generación de código también puede hacerse más difícil si es demasiado complejo las
características del lenguaje se incluyen innecesariamente, especialmente si se usan con poca
frecuencia y / o puede simularse mediante construcciones más simples. El infame ALGOL 60
función de paso de parámetro llamada por nombre (o llamada como si por macro) ejemplifica tal
construcción difícil de compilar. Se necesita la generación de una sección de objeto código [a
menudo llamado "thunk" (lngerman, 1961)] para cada argumento. Este código debe ser
reevaluado en tiempo de ejecución cada vez que el parámetro formal correspondiente sea
referenciado La práctica ha demostrado que los esquemas más simples, como el call-by-reference
y call-by-value, son métodos de paso de parámetros suficientemente potentes.
3-4.6 Eficiencia
"Se cometen más pecados informáticos en nombre de la eficiencia (sin necesidad lograrlo) que por
cualquier otra razón única, incluida la estupidez ciega ".
(Wulf, 1972a).
La eficiencia ha sido el tema más excesivamente sobreenfatizado en la historia del desarrollo del
lenguaje de programación. ¿Hay justificación para producir un programa que se ejecuta veinte
veces más rápido que la competencia, pero falla a la mitad ¿las carreras? La eficiencia es
importante después, no antes, de la confiabilidad.
La eficiencia debe considerarse en el contexto del entorno total, recordando que las máquinas son
cada vez más baratas y los programadores están recibiendo más caro. Por ejemplo, generalmente
no es deseable gastar un esfuerzo para mejorar la eficiencia de un programa en un 10 por ciento
porque el ahorro simplemente no justificar la inversión.
También vale la pena recordar que la eficiencia no es solo velocidad-espacio, E / S el acceso y los
patrones de búsqueda también están involucrados en la eficiencia. Ineficiencia espacial en
particular puede ser tan importante como la ineficiencia del tiempo, y se está volviendo más
importante a la luz de los recientes desarrollos en minicomputadoras y microcomputadoras.
Si bien ha sido exagerado, la eficiencia no puede ser ignorada enteramente. La declaración "ya no
tenemos que preocuparnos por la eficiencia porque el hardware será tan barato que no
necesitamos molestar "es parcialmente cierto, pero solo en parte Diferencias en la eficiencia de 10
o 20 por ciento, o incluso 30 o 40 por ciento, puede ser tolerado Cuando la diferencia es un factor
de 2 o un factor de 10, la la situación ya no es tolerable, por las siguientes razones (Hoare, 1973):
1. Por barato y rápido que sea el hardware, es más barato y más rápido cuando ejecutando un
programa eficiente.
2. La velocidad y el costo de los periféricos no mejoran en ningún lugar como tan rápido como los
de las CPU.
3. Los usuarios no deberían tener que sacrificar la legibilidad y la confiabilidad para obtener su
dinero vale la pena por el hardware mejorado.
La mayor causa individual de ineficiencia es una falta de coincidencia entre hardware y el lenguaje.
A veces esto indica revisiones al idioma, por lo general para soltar características que el hardware
no puede soportar de manera eficiente.
Intentar hacer coincidir un idioma demasiado cerca del hardware puede dar como resultado un
palabrotas. El mejor ejemplo de esto son las restricciones de FORTRAN IV sobre los subíndices de
matriz. Estas restricciones adaptan FORTRAN admirablemente bien al direccionamiento hardware
de IBM 709. pero esto ya no constituye una ventaja, y el las restricciones del subíndice son
ciertamente una de las características más irritantes de
FORTRAN.
Aceptar que la ineficiencia puede ser un problema, ¿cuáles son las posibles curaciones para ¿eso?
El que se presenta invariablemente es una optimización extensa en el compilador (ver Capítulos 12
y 13). La optimización de los compiladores puede ser muy útil, particularmente donde el recurso
escaso es el espacio Además, las características específicas también pueden ayudar
(Por ejemplo, un lenguaje GOTO-Iess requerirá mucho menos esfuerzo de compilación para el
análisis de las rutas de flujo de control).
Un buen compilador de optimización es difícil y requiere mucho tiempo para escribir. Porque
mayor tamaño y mayor complejidad, probablemente será menos confiable que una compilador no
optimizador. Se debe tener gran cuidado para que la semántica de la el lenguaje no cambia por la
optimización. Finalmente, los optimizadores generalmente funcionan mucho más lentamente que
los compiladores más simples, lo que puede ser un factor sorprendentemente significativo incluso
en instalaciones que piensan que pasan la mayor parte de su tiempo en la producción carreras.
Aunque una de las esperanzas originales para los lenguajes de alto nivel era que lo harían ser
independiente de la máquina, esta esperanza no se ha realizado plenamente. Más reciente los
idiomas siguen siendo fuertemente dependientes de la máquina, y la transportabilidad de los
programas sufre como consecuencia.
Tenga en cuenta que el hecho de que un idioma no sea independiente de la máquina no lo hace
necesariamente descartar el transporte de programas. A menudo un subconjunto del lenguaje
será independiente de la máquina (por ejemplo, un subconjunto que no incluya punto flotante)
aritmética). Incluso en un idioma que es muy dependiente de la máquina (por ejemplo, uno que
incluye aritmética de coma flotante o un tipo de datos "palabra"), a menudo es posible evite las
dependencias de la máquina lo suficiente como para que los programas específicos sean
transportables.
PASCAL, de hecho, vale la pena estudiarlo como un ejemplo de cómo hacer un lenguaje casi
independiente de la máquina sin dolor. Otros idiomas importantes que se acercan a PASCAL en
independencia de la máquina son FORTRAN y COBOL; sin embargo, la independencia de su
máquina se ha producido como resultado de esfuerzos en la estandarización en lugar de a través
del diseño del lenguaje original.
Problemas similares surgen en relación con el juego de caracteres. El bit exacto los patrones que
representan un personaje dado generalmente no son relevantes, pero la comparación la secuencia
de los caracteres varía lo suficiente de máquina a máquina para hacer independencia de la
máquina muy difícil. Por lo general, es seguro asumir que
1. Los dígitos están en orden.
Tenga en cuenta que "en orden" significa 'a' <'b' pero no prohíbe la existencia de x tal que 'a' <x
<'b'. Los "caracteres de control" no imprimibles también causan problemas, especialmente para
E / S.
3-4.8 Simplicidad
La simplicidad es una pregunta importante en el diseño de cualquier idioma. Está claro que la
mayoría los usuarios prefieren un lenguaje simple. De hecho, un lenguaje que no sea simple lo
hará generalmente fracasan en muchos otros aspectos, como lo demuestra la falta de
abrumadores soporte para PL / I y ALGOL 68. Si bien la falta de simplicidad puede dar como
resultado lenguaje no aceptado, la simplicidad en sí misma no implica un buen lenguaje. BASIC es
ciertamente un lenguaje simple, pero no está especialmente bien diseñado y la mayoría
Deben evitarse las versiones, si es posible, para cualquier tipo de desarrollo de software serio.
Sobre la base de la historia de los lenguajes de programación, una o dos declaraciones se puede
hacer sobre la mejor manera de lograr la simplicidad (Wirth, 1974). Sencillez no se logra por falta
de estructura; el resultado de esto es el caos. La simplicidad es no se logra a través de la
generalidad del límite; el resultado de esto es un lenguaje que es imposible de dominar o
implementar por completo.
Como muchos otros aspectos que se han considerado anteriormente, la uniformidad puede ser
útil o inútil según cómo se aplique. Un ejemplo de utilidad uniformidad es la noción en ALGOL de
la expresión; en cualquier lugar que necesites valor aritmético, puede usar cualquier elíptica. En
general, la uniformidad inútil es hacer que una construcción se comporte de la misma manera en
dos contextos que son lo suficientemente diferente que el programador no espera un
comportamiento idéntico y no tiene uso para ello. Un posible ejemplo de uniformidad inútil es la
fusión de las ideas de "expresión" y "declaración" en ALGOL 68. Con el único y algo
El punto 3 se ilustra por el hecho de que todavía vivimos con el IBM 709, como endurecido en
FORTRAN.
3-4.10 Ortogonalidad
La ortogonalidad es otra filosofía de diseño, prominente en gran parte debido a su uso extensivo
en ALGOL 68. La idea básica de la ortogonalidad es tener varios conceptos generales, cada uno de
los cuales funciona por sí mismo sin conocimiento del estructura interna de los demás. Buenos
ejemplos de esto de ALGOL 68 son valiosos acceder (por ejemplo, variables simples, accesos a la
matriz) y operadores aritméticos (p. "+"). Las primitivas que acceden al valor producen un valor;
no les importa cómo es usado. Los operadores aritméticos combinan dos valores; no les importa
cómo se obtienen valores
a: = a / a [l]
tener resultados dependientes de la implementación. Aquí hay un caso donde un poco menos de
ortogonalidad (es decir, un poco más de "diagonalidad") parece ser necesario. La estructura del
operador que funciona tan bien para operandos escaladores simples simplemente no se extiende
limpiamente a matrices. Tenga en cuenta que esto no excluye a todos los operadores de matriz;
asignación y la comparación sigue siendo útil y directa.
la filosofía básica de la generalidad es que "si nosotros permitimos esto, entonces vamos a
permitir todo similares así." De manera similar a la ortogonalidad, esta noción es una ayuda para la
simplicidad, pero no un sustituto para la simplicidad. Si lleva demasiado lejos, generalidad puede
producir características raramente usadas, propensos a errores que pueden ser difíciles de
implementar. La implementación de cadenas en ALGOL 68 proporciona un ejemplo. Una adecuada
implementación de cadenas permite longitud variable, sin límites.
Los diseñadores de ALGOL 68 decidieron generalizar ALGOL 68 permite los límites de matrices
«flexibles» para cambiar en cualquier momento y hace cadenas sólo un caso especial de esto.
Resulta que, flexibles y arreglos de discos son la pesadilla de un implementador de uso marginal
excepto para proporcionar cuerdas. Un método alternativo es definir "cadena" como una primitiva
de algunos ejecutores del subconjunto han hecho de ALGOL 68. Otro caso especial de generalidad
versus especialización vale la pena mencionar. Muchas idiomas tienen funciones incorporadas que
toman cualquier número de argumentos, mientras que muy rara vez es que funciones el usuario
pueden hacer esto. Solamente funciones de E/S y algunas funciones especiales como MAX y MIN
realmente necesitan un número variable de argumentos. En la mayoría de los casos, los usuarios
no necesitan listas de argumentos de longitud variable para sus propios procedimientos si el
lenguaje ya proporciona estos casos especiales.
Minimality es la noción que la lengua debe contener el mínimo absoluto de construcciones que
posiblemente puede sobrevivir. Esto es generalmente una buena filosofía, pero no si lleva
demasiado lejos. Una máquina de Turing es muy mínima, pero usted no quiere programar uno. Es
importante distinguir entre un conjunto mínimo de constructos, que es el conjunto más pequeño
posible y un conjunto utilizable mínima, que es el más pequeño conjunto que todavía satisface el
requisito básico de la usabilidad razonablemente bien.
La breve discusión a seguir ilustra esta idea bajo dos epígrafes: evitar construcciones que son
demasiado primitivas para encajar bien en el lenguaje y evitar construcciones que son de muy alto
nivel caber bien. Una lengua que razonablemente bien se retira de los pasivos del hardware no
debe contener uno o dos constructos que traen de nuevo. Por ejemplo, para evitar introducir el
hardware "rama" (es decir, el GOTO), ofrecen un buen conjunto de primitivas de control de alto
nivel. Para evitar la reintroducción de ese constructo problemático, el puntero sin restricciones,
tampoco puso algunas restricciones como restringirlo para apuntar sólo a los objetos de un tipo
específico o acabar con ella completamente mediante la aplicación de algo como recursiva-datos
de Hoare tipos (Hoare, Dahl y Dijkstra, 1972). Para evitar la reintroducción de la "palabra" en una
situación donde una palabra contiene, digamos, un número de 12 bits y bits de la 4 bandera,
organizar la estrategia de asignación de almacenamiento de su compilador para que
Esta sección discute, en diversos niveles y desde varios puntos de vista, algunos de los detalles que
van a un idioma. Algunas áreas del diseño del lenguaje se han investigado bien que es posible
establecer directrices muy concretas; Esto se hace siempre que sea posible. Sin embargo, muchos
temas aún no están en tal s.tage, e intenta sólo para dar la discusión general sobre tales asuntos.
El diseñador de lenguaje leyendo que esta sección debería reconocer que la mayor parte de esta
sección presenta opiniones de consenso en lugar de hechos. Diseñadores deben, por todos los
medios, sienta libres de seguir consejos diferentes o a pulsar hacia fuera en sus propias (véase, sin
embargo, Sec. 3-3 y Hoare, 1973, sobre los peligros de demasiada inventiva).
Esta sección pretende proporcionar una base para aquellos que tengan la intención de golpear
hacia fuera, y una base para aquellos que necesitamos armar un idioma y hacer no desean innovar
extensivamente. La sección está organizada sobre la base de los distintos tipos de estructuras de
que un programa de exposiciones, y se pone énfasis en cómo estas estructuras están influenciadas
por el lenguaje de programación en uso. En este contexto se discuten las implicaciones para el
diseño del lenguaje. Las estructuras se ordenan esencialmente en una secuencia de abajo hacia
arriba, trabajando desde el nivel más bajo al más alto nivel
3-5.1 Microestructura
Lo que aquí se llama microestructura básicamente cubre las cuestiones del diseño del lenguaje
que afectan el aspecto de la lengua pero no cambian su semántica. El principio más importante de
la microestructura de la lengua es que el significado de una construcción, como un operador,
debería ser obvio desde su aparición. En otras palabras, los símbolos de la lengua deben ser
fácilmente reconocido por lo que son y lo que hacen. , El aspecto más visible y más bajo nivel de la
microestructura es el conjunto de caracteres utilizado. Demasiado grande un conjunto de
caracteres puede provocar desconcierto de los usuarios sobre el significado de los jeroglíficos
desconocidos en un programa.
Demasiado pequeño un juego de caracteres a menudo resulta en sintaxis algo tensas, como
testigo el uso excesivo de los paréntesis en PL / I. El conjunto de caracteres debería ser tan
estándar como sea posible para evitar el cambio de programas o el lenguaje en sí mismo cuando
se mueve entre máquinas. En general, el mejor juego de caracteres aparece que el ASCII de 7 bits
conjunto. Esta es la versión en Inglés de la ISO internacional Standard Character Set y es el
estándar de facto para la mayoría de la industria. Le falta quizás una docena caracteres útiles, pero
en general es conveniente para la mayoría de lenguajes de programación.
Generalmente es una buena idea utilizar un símbolo para un único propósito, especialmente si es
un operador. Es lamentable, desde el punto de vista de análisis y comprobación de errores, que
más programación remanente de idiomas de las matemáticas la Convención de usar"-"para
negación unaria y resta. Si el diseñador se siente experimentar, tal vez"NEG" podría ser utilizado
para nega ción. Cuando una adecuada combinación de caracteres no alfanuméricos no es
evidente, se debe elegir una palabra clave para un símbolo. Ciertos principios generales pueden
establecerse para la elección de dichas palabras clave. Palabras clave debe ser pronunciable. Es
más fácil recordar una sílaba que una secuencia de letras sin relación, impronunciable. Palabras
clave generalmente debe ser elegido por lo que no es probable que duplicar identificadores
definidos por el usuario. Incluso si hay alguna forma de distinguir los dos, la posibilidad de
confusión es alta. Al elegir las palabras clave, trate de usar los verbos y Preposiciones;
programadores tienden a usar los sustantivos y adjetivos como nombres de variables. Palabras
clave debe deletreado la manera que el usuario les esperaría. Si tiene que utilizar "Tamaño" como
operador, no escribe "SIZ". Tales deletreos peculiar causará más problemas que ahorran. Si es
posible, no tiene dos palabras con ortografía muy similar (por ejemplo, "PROGEND" y "PROCEND").
Abreviar palabras clave sólo cuando la abreviatura es absolutamente evidente. Esto ahorra escribir
considerable y se utiliza con frecuencia. Sin duda está justificado abreviar "Procedimiento" a
"PROC" o "Precursor" a "PRED"; abreviar "Externo" a la "EX" o "REAL" a "RE" no es. Abreviar
"Entero" a "INT" es probablemente justificado. A veces será necesario tener un par de palabras
clave sucesión de algo. Una práctica popular en los últimos años, en particular por ALGOL 68
(Lindsey y van der Meulen, 1972; Van Wijngaarden et aI., 1975), ha sido invertir la ortografía de la
palabra clave principal para obtener la clave final. Esto trabaja parte del tiempo; pero muchos de
los resultados son horribles. Es probablemente mejor usar sólo los pares siguientes, que están
ganando gran aceptación:
Si tienes que terminar una construcción que comienza con "SELECT", usar "ENDSELECT," no
"TCELES." Otra alternativa, que es más difícil de diseño, es tratar de encontrar una palabra clave
que "cabe" como palabra clave final (ejemplo:... LAZO... REPEAT"). También es posible utilizar un
par de palabras clave (ejemplo: "lazo... END LOOP") en lugar de una sola palabra clave, si no grave
confusión se introduce en otra parte. También, tratar de evitar el uso excesivo de una palabra
clave; la PLII "Final" es un bruto violador de esto. Un asunto adicional que es importante con
respecto a palabras clave es cómo decirles aparte de identificadores definidos por el usuario.
Existen tres enfoques distintos:
Generalmente, alternativa 1 obras mejor-es simple y, teniendo en cuenta una cuidadosa selección
de palabras clave, rara vez causa problemas. Alternativa 2, que utiliza en PL / I, signitlcantly
complica el analizador. Probablemente fue adoptado en PL / porque el número de palabras clave
en algunas implementaciones era tan grande que ningún usuario podría razonablemente ser
esperaba para evitarlos todos. Idiomas de un tamaño más realista no tiene esos problemas.
Alternativa 3 es utilizado en las implementaciones de ALGOL 60 y ALGOL 68. Consiste en escribir
extra y hace que el programa no se puede leer. La aplicación solo más acertada del ALGOL 60
(Burroughs uno) utiliza la alternativa 1. Identificadores definidos por el usuario merecen alguna
discusión. El conjunto básico generalmente es letras y dígitos, excepto el primer carácter debe ser
una letra. Si dispone de letras mayúsculas y minúsculas, debe considerar equivalente. Hay pocas
cosas más confuso que tener "A" y "a" actuar como variables independientes. Se ha convertido en
costumbre de agregar algunos caracteres adicionales a la lista de "letras", para mayor comodidad.
Probablemente el más importante es el carácter de subrayado '_', que permite, en efecto, el uso
de "en blanco ~" dentro de identificadores. Esto proporciona una gran mejora en la legibilidad.
Con técnicas modernas de manejo de cadenas no es justificación para limitar la longitud de
identificadores, ya sea explícitamente ("máximo 6 caracteres") o implícitamente ("sólo los 6
primeros son significativos"). Este enfoque no requiere almacenamiento ilimitado, ya que algunos
identificadores serán más de 15 caracteres (Alejandro, 1972). Un "límite de silencio", dicen, 255
caracteres nunca ser superados o incluso se acercaba, especialmente si los identificadores no
pueden dividirse en los límites de la línea. Otro aspecto de la microestructura que merece especial
atención es el diseño de la Convención de comentario. Debe ser breve (no "comentario"), y debe
ser fácil de escribir (no "I *"). Como Hoare (Hoare, 1973), probablemente el mejor tipo de
Convención de comentario es que es bastante común en montadores pero infrecuentes en
idiomas de alto nivel: un símbolo particular comienza un comentario, que luego se extiende hasta
el extremo de esa línea. Esto elimina los comentarios "runaway" de PL / I y este Convenio resulta
para ser sorprendentemente cómodo de usar. Una cuestión importante es la elección de un
símbolo de comienzo. Mientras que varios símbolos se han utilizado para este tipo de
comentarios, el requisito de mecanografiar fácil puede eliminar muchos de ellos. Además, estos
símbolos un carácter son a menudo útiles como operadores. Idealmente, tal símbolo debe: 1. ser
un símbolo de dos caracteres, preferiblemente ambas el mismo carácter. 2. ser un símbolo
raramente, si siempre, utilizado como un operador, con ningún significado obvio como tal. 3. se
compone de caracteres situados en el mismo lugar en todos los teclados.
El símbolo de "lo" es un ejemplo de una buena opción. Sobre el aspecto del programa, la mayoría
idiomas han seguido plomo de ALGOL en declarar que el espacio en blanco es significativo sólo en
eso (excepto en cadenas) separa dos símbolos y no puede ocurrir dentro de un símbolo. Si el
sistema tiene un carácter de "tabulación", es útil tratar la misma manera. También es razonable
para el tratamiento de fin de línea del mismo modo, excepto que también puede terminar
Comentarios. Es más deseable exigir construcciones en ciertas columnas. Una última cuestión
relativa a la apariencia de la lengua: a menos que se planea utilizar un formateador de texto
automático de fuente como parte del compilador, es una buena idea tener algunos medios de
causar un pase a la siguiente página del listado. Si el sistema tiene un carácter forma-alimentación
disponible, es un asunto sencillo para arreglar para que el escáner del compilador ignorar
caracteres tales como tokens. Cuando la fuente es la salida, el carácter de alimentación de forma
generaría el salto requerido a la siguiente página.
Recientemente se ha vuelto evidente que es muy útil para poder dar nombres a constantes. Esto
evita el uso de "números mágicos" en el cuerpo del código. En este programa de manera
legibilidad está mejorada y es mucho más fácil cambiar el valor de, digamos, un tamaño de la
tabla. Algunas idiomas (véase Clark y Horning, 1971) han incluso adoptado la posición que, a
excepción de 0, 1 y 2, todos constantes numéricas que ocurre en el código deben ser nombres
dados por las declaraciones constantes. Por consiguiente, "números mágicos" puede no haber en
cualquier lugar excepto en tales declaraciones. Otro aspecto de la declaración de constantes es
que es útil para poder escribir una constante para cualquier tipo de datos en el programa.
Por ejemplo, debe ser posible escribir una constante matricial. Tales constantes en gran parte
eliminaría la necesidad para inicializar arreglos de discos, como más inicializadas matrices son
realmente constantes de matriz, impedirían que el programador de destruir accidentalmente el
contenido de dicha constante. Asimismo, es útil, esencialmente como una forma de abreviatura,
para ser capaces de dar nombres a los tipos y luego usar esos nombres en la declaración de
variables o en la construcción de tipos más complejos. Estas declaraciones de tipo pueden ahorrar
una gran cantidad de la escritura al tiempo que también mejora la legibilidad. La sintaxis de las
declaraciones de constante y tipo puede ser similar, como en el siguiente:
Las similitudes entre los dos pueden traer un pensamiento tentador: ¿por qué no generalizar tanto
para una instalación sin parámetros macro? La razón básica para evitar esto es que las constantes
y tipos son, en general, lo único que se declararía. Por lo tanto, un constructo tal había sería una
invitación abierta para programación complicado "inteligente". Si el diseñador no decide permitir
la inicialización de variables, la sintaxis de declaraciones de variables se vuelve bastante simple.
Una sintaxis muy teadable es el utilizado por PASCAL:
V AR a, b, c : integer
Nota que esta sintaxis es realmente conveniente sólo cuando toda la información sobre, digamos,
arreglo de discos de límites se localiza en la especificación del tipo. Legibilidad de esta forma
disminuye rápidamente si la lista de nombres de variables es desordenada para arriba con otras
cosas como ini tialization. Ahora pasemos toa discusión de tipos de datos en lenguajes de
programación. La referencia básica para los tipos de datos moderno es obra de Hoare (Hoare, Dahl
y Dijkstra 1972). El trabajo de Wirth en PASCAL (Jensen y Wirth, 1975) es digno de mirar como una
aplicación práctica de muchas de las ideas de Hoare. Puesto que el material sobre este tema se
basa pesadamente en estos dos documentos, no se hace mención de estas referencias. Hehner
(1975) y Tennent (1975) presentan también algunas ideas interesantes.
La base de todas las nociones de estructura de datos es el tipo de datos. ¿Qué es un tipo de datos?
Aunque ha habido considerables diferencias de opinión sobre esto, la opinión más frecuente es
que un tipo de datos es un conjunto de valores y un conjunto de operaciones en estos valores. Por
ejemplo, "entero" podría ser el conjunto de valores "..., - 2, - 1,0,1,2,... "y el conjunto de las
operaciones" +, -, *, /, +-. " Tenga en cuenta que la presencia de las operaciones como parte de la
definición implica también que los miembros del conjunto de valores no pueden ser elegidos
arbitrariamente; debe haber cierta coherencia entre ellos para que las mismas operaciones se
aplican a todos. Existen tres enfoques distintos tipos de lenguajes de programación.
La primera es que ninguno en absoluto, característico del lenguaje ensamblador y algunos idiomas
de "grado medio". La conveniencia de tipo esté bien establecida; así no se dará más atención a su
ausencia. Los dos enfoques restantes se denominan "duros" y "suave" escribiendo. En ambos
enfoques un valor tiene un tipo asociado. La distinción fundamental entre los dos enfoques es que
en un lenguaje con tipos blandos cualquier variable puede contener cualquier valor (como
SNOBOL y APL), mientras que en un lenguaje duro escribió un tipo específico está asociado con
cada variable y la variable puede contener sólo valores pertenecientes a ese tipo! dejó de valores.
En general, ahora se acepta que escribir suave hace un lenguaje un poco más pequeña y algo más
conciso, duro escribiendo es muy superior desde el punto de vista de error en tiempo de
compilación comprobación (Wirth, 1974) y no presenta ningún inconveniente notable . Discusión
posterior asumirá duro escribir.
Aunque no hay diseñadores de dos lenguaje acuerdan sobre exactamente cómo la amplia gama de
tipos de datos debe subdividirse, tipos generalmente pueden agruparse en tres categorías:
simples, compuestos y complejos. Estos términos son relativos y los límites entre ellos son difusos.
Los tipos de datos simples son en el nivel relativamente primitivo de una lengua; se utilizan para
construir tipos más complejos y se proporcionan generalmente más o menos directamente por la
máquina sobre la cual se implementa el compilador. Un asunto de interés general en tipos simples
es la cuestión de la ordenación; ¿que i-s, se consideran los valores de un tipo en una secuencia
específica? Básicamente determina si las comparaciones como "<" son válidos para todo simples
tipos y también pueden influir en el bucle de contado de la lengua. En algunos ~ ases, tales como
números, ordenar es obviamente necesario. Para algunas otras situaciones, la naturaleza precisa
del ordenamiento es menos evidente.
Tenga en cuenta que estas declaraciones muestra constituyen las declaraciones de los
valores"rojo," "verde", etc., así como los identificadores "color" y "job_status." Las operaciones
válidas sobre los valores de esos tipos son asignación y comparaciones igual y no igual. Las
constantes de este tipo son simplemente los nombres dados. Obviamente, dentro de la máquina
los valores de un tipo de enumeración se representan como números enteros pequeños, pero esto
no es visible para el programador; "rojo" es no un valor de tipo "integer". No está claro si los tipos
de enumeración deben ser ordenados o no; tal vez el programador debe ser capaces de pedir un
tipo de enumeración específica a ser ordenado.
Sin ellos, el programador se conduce con frecuencia a simularlas mediante números enteros, un
proceso que funciona pero es propensa a errores. Cualquier idioma para trabajo serio de software
debe tener tipos de enumeración. Probablemente la forma más común de un tipo de datos es el
número. Es realmente muy poco lo que puede decirse acerca de números de punto flotante, otros
que son, en este momento, dependiente de la máquina en la implementación del lenguaje casi
todos. Por lo tanto, la discusión se limita en gran medida a números enteros. Mientras que el tipo
simple "integer" es bastante claro, hay dos notables modificaciones que se pueden hacer. La
primera modificación consiste en la idea de la "subrange," un tipo que tiene, según su valor, un
subconjunto de los enteros. Ejemplos de esta modificación son los siguientes:
TYPE cents = 0 TO 99
TYPE day_oLmonth = 1 TO 31
TYPE inning = 1 TO 9
Esto significa que una definición formal debe considerar sub valores de rango a convertir
automáticamente a valores enteros siempre que sea necesario. Probablemente la mejor solución a
esto es decir que un tipo subrango es realmente "entero" en disfraz, y que el respeto sólo en que
no es idéntico a "entero" es que una variable de subrango puede almacenar sólo un subconjunto
de todo el"entero" sistema de valor. También puede ser útil tener sub rangos de otros tipos
de"integer" (p. ej., subranges del "carácter"). Sería mejor decir que cualquier tipo pedido puede
tener gamas sub definidas. La otra modificación notable "entero" es uno que también es aplicable
a punto flotante.
La idea, que es originalmente debido a Hoare (1973), es que uno debe ser capaz de asociar
unidades (por ejemplo, kilogramos, ticks de reloj, metros por segundo) con variables numéricas.
Esto es esencialmente una forma de proporcionar redundancia más útil. El compilador puede
comprobar que las operaciones realizadas sobre valores numéricos eran de hecho válidas. Por
ejemplo, agregar kilogramos para las garrapatas del reloj es sin duda error de alguien; debe
asignarse un valor obtenido multiplicando los metros por segundo por segundos sólo en una
variable que contiene metros. Un centro completo para especificar este tipo de cosas sería algo
complejo (considerar, por ejemplo, conversiones de pies a metros, el uso de Newton como un
sinónimo para kilogrammeters por segundo cuadrado), pero la idea es novela y es compatible con
Ada. Después de los tipos de datos numéricos, al "carácter" tipos probablemente son lo más
frecuentemente usados.
Es lamentable que hardware actual no admite cadenas de longitud variable verdaderamente bien.
El resultado es una multiplicidad de enfoques a los personajes: 1. "Carácter" es un tipo simple,
manteniendo un carácter. Cadenas se implementan como conjuntos de caracteres. 2. secuencias
pueden variar en longitud, pero deben tener una longitud máxima especificada para fines de
asignación de almacenamiento de información. 3. secuencias pueden variar arbitrariamente en la
longitud (eficiencia sanciones son aceptadas por usabilidad). Un cuarto enfoque, el uso de cadenas
de longitud fija, no serán considerados. La primera aproximación esencialmente puentea el
problema. Las propiedades de la matriz son ahora la clave para la utilidad de secuencias. Por
desgracia, las matrices son absolutamente inadecuados para la implementación de cadenas; uno
puede aceptar la cadena pobre manejo que resulta, como en PASCAL, o bien intentar estirar las
propiedades de la matriz y quedar generalmente con resultados no deseados, como ALGOL 68.
Desde las medidas reales (Alejandro, 1972) muestran que la inmensa mayoría de cadenas de bits
son realmente un poco larga, la generalización parece haber sido innecesario. Para la mayoría de
las idiomas, booleano es suficiente. Aunque Boolean podría definirse como un tipo de datos, el
enfoque más sencillo es simplemente para predefinir, como PASCAL, de tipo booleano = (false,
true) si se dispone de tipos de enumeración, este enfoque satisface todas las exigencias y no
necesidades más complejidades en el compilador.
Ahora que hemos terminado con los tipos simples, el siguiente tema de discusión es el tipo
compuesto. Estos son tipos construidos en formas bastante sencillas de tipos más simples. La
variedad más obvia de tipo compuesto es la matriz. Dos aspectos merecen comentario. En primer
lugar, debe considerarse a extender subíndices a los tipos de datos que no sean números enteros.
General números floating-point son obviamente inadecuados como subíndices, tipos de
enumeración y caracteres solo pueden a menudo provechosamente emplearse como subíndices.
El enfoque adoptado en PASCAL de hecho aprovecha de esto. Las dimensiones de una matriz están
dado en términos de los tipos de sus subíndices:
(véase la definición del color anterior). La segunda cuestión relativa a matrices trata de arreglos
dinámicos, cuyos tamaños están determinados en la entrada del bloque en lugar de en tiempo de
compilación. Arreglos dinámicos añadir a la complejidad de tiempo de compilación y, por esta
razón, han quedado fuera algunos idiomas. Sin embargo, pueden ser muy útiles en aplicaciones
donde tamaño de la matriz depende de datos de entrada. El aumento de complejidad es
compensado a menudo por la utilidad de matrices dinámicas, y por lo tanto en muchos casos los
arreglos dinámicos deben incluirse en un idioma. El otro tipo de datos compuesto es el registro,
conocido a veces, equivocadamente, como la "estructura." Para muchas aplicaciones es sólo tan
fundamental como la matriz. La falta de registros es probablemente un factor importante en el
rechazo de ALGOL 60 hy la comunidad informática.
Un adorno bastante útil del registro es el registro de la variante de etiquetado. Esto permite que
las situaciones frecuentes que la estructura de parte del registro depende de los datos en una
parte anterior del mismo:
El disco tiene algunos campos fijos (por ejemplo, "given_names") seguidos por un campo etiqueta
que selecciona qué variante del resto del disco está en uso. El campo etiqueta se llama "sexo" y es
del tipo de enumeración "(hombre, mujer)." El campo etiqueta es seguido por las variantes, que
en este caso son "married_name" y "maiden_name" o "lasLname." Con tal disposición, el
compilador puede establecer controles de tiempo de ejecución para asegurarse de que cualquier
referencia, por ejemplo a "maiden_name," se hace referencia a un registro cuyo campo "sexo" es
"hembra". PASCAL y Ada soportan esas instalaciones. Relleno de COBOL tiene la interesante
noción de ser capaz de utilizar"" como un campo de nombre en un registro. Esencialmente, esta
característica permite al programador un campo anónimo del tamaño dado. Esto puede ser útil si
se procesa un archivo de registros de varios programas, algunos de los cuales ignoran ciertos
campos. Un tipo de datos compuestos menos obvio es una forma simple de conjunto.
Mientras que los sistemas en general son parte de un sujeto había cubierto más tarde por
"complejos" tipos, una forma restringida de conjunto merece mención, ya que se puede
implementar simplemente y eficientemente pero sigue siendo bastante útil. Si el número de
posibles elementos de un conjunto es muy pequeño, es posible implementar el sistema asignando
simplemente un campo de bits. Se asigna un bit para cada posible elemento. El bit es 1 o 0 para
indicar si el elemento está en el conjunto. Con este esquema, conjunto de manipulaciones se
realizan fácilmente por instrucciones lógica de la máquina. Por ejemplo, un conjunto sobre un tipo
de enumeración
(ver definición de "color" antes) puede ser especialmente útil. El tipo de datos compuesto final
que se considerará es el puntero. El puntero se conoce como un compuesto tipo en contraposición
a un tipo simple ya que es más sabiamente utilizada en conjunto con y no independiente de otro
tipo de datos. De hecho, es bastante amplio acuerdo que indisciplinado de punteros es, en todo
caso, incluso peor que indisciplinados uso del GOTO. Hay dos soluciones de diseño de lenguaje de
programación importantes para esto. La primera solución es recursiva los tipos de datos de Hoare
(Hoare, Dahl y Dijkstra, 1972), que eliminan el uso explícito de un puntero en conjunto. La idea
básica detrás de los tipos de datos recursivos es que en lugar de tener, decir, punto de un campo
de un registro a otro registro, el segundo registro es conceptualmente un campo de la primera. Un
tipo de datos de reCUfSlve es uno en el cual ocurre el nombre del tipo se define en su propia
definición.
Mientras que este tipo de datos ~ pueden implementarse mediante punteros o tipos de datos
recursivos, tal implementación ~ ons son muy difíciles de cambiar si se descubre que necesitan
ajuste (por ejemplo, si la aplicación es muy rápida, generalmente resulta que el espacio de
almacenamiento es el real cuello de botella). La solución a largo plazo parece ser la idea de la
abstracción de datos. La noción básica de la abstracción de datos es que debe ser posible definir
una "interfaz" para un tipo de datos. Esta interfaz especifica las distintas operaciones qué valores
del tipo sin mencionar cómo se implementan los valores o las operaciones.
La puesta en práctica sí mismo entonces consiste en especificar cómo se implementan los valores
(en términos de tipos de datos más primitivos) y cómo las operaciones se ponen en ejecución (un
procedimiento para cada operación). El usuario puede usar el tipo de datos sin saber ni
preocuparse por los detalles de la implementación y la aplicación se puede cambiar sin reescribir
programas de usuario. Abstracciones de datos están en las primeras etapas experimentales, y no
hay acuerdo en los detalles. Para aquellos que deseen investigar más a fondo, Horning (1976) es
una introducción más detallada a las ideas presentadas aquí, Liskov y Zilles (1974) da una
excelente demostración de la utilización de abstracciones de datos y Shaw (1976) y el resto de la
expedición SIGPLAN (1976) constituyen un excelente resumen del tema.
La forma más simple es que de las funciones que toman un valor de 1 tipo y devuelve un valor de
otro tipo (por ejemplo, una función "redondo" que "real" y volver "integer"). La alternativa es el
enfoque de ALGOL 68, que tiene un operador especial que tiene un valor de entrada y un tipo de
datos y convierte el valor de entrada para el tipo especificado.
La asignación de almacenamiento para variables en un programa es, en sus detalles, el negocio del
compilador y el run-time ~ ystem. Ciertas políticas generales, sin embargo. son parte del diseño
del lenguaje. Existen básicamente cuatro formas de asignación variable; examinará a su vez. con
comentarios sobre cada uno. La forma más simple y más antigua de asignación es estática,
inmortalizado en FORTRAN y en instalaciones propias de ALGOL 60. Ahora parece que la mayor
utilidad de la asignación estática es su uso no en los procedimientos pero a nivel mundial.
Mayoría de los casos donde se utilizaría el ALGOL 60 de estilo propio puede ser mejor organizada
como un conjunto de variables estáticas con un conjunto de procedimientos para operar con ellos.
Esto hace asignación estática correspondiente a la abstracción de datos y modularidad (véase Sec.
3-5.5). La forma más popular de la asignación, por ALGOL 60, es local, dinámica o automática. La
simplicidad de la utilidad y la aplicación de asignación local hacen que sea la forma de elección de
variables normales dentro de los procedimientos.
Un punto relevante a la aplicación es que mientras que los programadores tienen la libertad de
solicitar asignación de montón almacenamiento de en cualquier momento, que probablemente no
debería también tener la libertad de hacer su desafectación. Esto resultará más ciertamente en
"punteros colgantes." Recuperación de almacenamiento de información que no está accesible
para el programa debe ser la responsabilidad del sistema de tiempo de ejecución, no el
programador. La última cuestión importante sobre estructuras de datos es el alcance de nombres.
El propósito original de restringir el alcance de nombres adentro ALGOL 60 fue en gran medida
facilitar la asignación de almacenamiento de información. Asignación de ámbito de aplicación y
almacenamiento están acoplados ya no tan fuertemente como lo fueron una vez. Sin embargo, las
restricciones en el alcance de nombres todavía están presentes para ayudar en la reducción de la
complejidad de los programas. Con el fin de mantener la complejidad de los programas y ~
egments de programas de alcance humano, es necesario restringir las interacciones entre los
diferentes segmentos.
Acceder a una variable dada es una de las más importantes interacciones entre las diferentes
partes de un programa, y para este ámbito de la razón las restricciones han continuado en
lenguajes de programación. Algunas observaciones deben hacerse, sin embargo, sobre la
naturaleza de las reglas de alcance que se utilizan. Una de las preguntas más fundamentales de la
organización de ámbitos es, ¿cuál es el área básico de las restricciones de alcance? La respuesta
más común es el originado por ALGOL 60·-the BEGIN-END hlock. Esto aparece. sin embargo, al ser
una estructura excesivamente general. Mul.tiple anidar bloques con las variables declaradas en
cada nivel comienzan a exceder la capacidad limitada de la mente humana para el manejo de
nidos. La alternativa base para las restricciones de alcance es el procedimiento. En lugar de
asignación de variables y control de su accesibilidad con bloques arbitrarios, se pueden realizar
dichos controles en el procedimiento de entrada y salida. Este enfoque ha estado en uso durante
mucho tiempo (después de haber sido introducido en FORTRAN) pero sin embargo, cuando se
combina con la asignación de local en lugar de estática, es bastante factible, como atestiguan su
uso moderno en PASe AL.
Puesto que es algo más simple que el control de alcance de BEGIN-END, puede ser recomendado.
Un aspecto algo más restrictivo de control de alcance es el que ha sido mencionado varias veces
en otras partes de esta sección. es decir, garantizar que una variable no es accesible por dos
nombres diferentes. Ha sido sugerido (Tennent, 1975) que el compilador debe, de hecho,
comprobar tales situaciones y considerarlos errores. En general, se trata de una idea con algún
mérito; en la práctica, podría convertirse en algo difícil cuando se consideran construcciones de
punteros. Incluso si no pueden señalar con punteros a las variables nombre, todavía se pueden
tener dos punteros apuntando a la misma pieza de almacenamiento anónimo. Tipos de datos
recursivos podrían aclarar algunos de estos problemas. Otro aspecto de alcance que es una
restricción que muchos compiladores hacer cumplir es el requisito que las entidades (variables,
procedimientos, etc.) deben ser declaradas antes de utilizarlos.
Para las variables y constantes no es demasiado desagradable. Puede ser conveniente establecer
algunas extensiones para permitir, por ejemplo, los tipos de datos recursivos o tipos de datos con
los indicadores que se refieren unos a otros. El mayor problema con la regla de declarar antes de
usar es para procedimientos. Es realmente conveniente para poder llamar a un procedimiento que
ocurre más adelante en el programa, incluso si uno no tiene la clásica situación de mutuamente
los procedimientos recursivos. Es una grave molestia tener que reordenar los procedimientos en
un programa para el compilador y no el programador.
Poco de esfuerzo en el compilador probablemente se justifica para evitar tener que introducir esta
restricción. Un punto final que es una cuestión de alcance, pero no a veces reconocido como tal es
la cuestión de los nombres de campo en un registro. En algunas idiomas, notablemente COBOL,
estos nombres son accesibles desde el exterior sin la calificación adicional. En muchas idiomas
modernas (por ejemplo, PASCAL), "a.x" debe escribirse "a.x" incluso si no hay ninguna otra"x" en
el programa. La elección del enfoque aquí no es evidente; el enfoque de COBOL puede implicar
ciertamente mucho menos escribir, pero el enfoque de PASCAL probablemente mejora la
legibilidad y ciertamente simplifica el compilador y la semántica de la lengua. Ciertamente, si se
toma el enfoque de COBOL, es vital que el compilador busque ambigüedad posible. no debe ser
posible tener dos interpretaciones diferentes del mismo nombre en el mismo lugar por
reorganizar las declaraciones.
ha existido mucha controversia con respecto a la mejor opción para primitivas de estructura de
control. Gran parte del debate se centra en la inclusión o exclusión del GOTO. Casi universalmente
es aceptado que es mejor utilizar construcciones de alto nivel de control en lugar del GOTO sin
restricciones. Es también ampliamente aceptado que un conjunto adecuado de construcciones de
alto nivel de eliminaría la necesidad de y el deseo del GOTO. Lo que no se conviene así en es lo que
debería abarcar tal conjunto.
Es obviamente necesario ser capaz de agrupar varias instrucciones para la ejecución en secuencia.
Hay poca disputa de que la instrucción IF y algún tipo de lazo que termina en una condición
booleana son deseables. Casi como indiscutible es algún tipo de sentencia CASE para opciones
multiway y alguna forma de controlar una variable de índice del bucle FOR. La mayor área de
controversia se centra alrededor de "escape" construye y construye para el manejo de
excepcionales condiciones que a menudo. pero no siempre, son uno y el mismo. Claramente, esta
discusión tiene algunos elementos de mínima frente mínimo útil tema mencionado en la
consideración de la filosofía de diseño en 3-4 segundos. Nadie conflictos que construye un
programa que utiliza el escape pueden también escribirse sin ellos. La pregunta básica es si es
mucho más fácil de leer y escribir programas con tales construcciones disponibles-más
investigación es necesaria en esta área.
El resto de esta subsección discutirá varias construcciones en detalle. La estructura de control más
simple es la combinación de varias declaraciones en una sola sentencia, con declaración de BEGIN-
END de ALGOL. Idiomas sin tal construcción (FORTRAN, BASIC) sufren gravemente de su ausencia.
Teniendo en cuenta su conveniencia, hay tres métodos distintos para la obtención de tal
combinación: soportes explícitos, la sucesión de construcciones y sucesión por indentación.
Soportes explícitos son mejor caracterizados por el par de ALGOL 60 BEGIN-END (aquí considerado
sin la posibilidad de incluir declaraciones dentro de ella). COMENZAR... Es una opción; hay otros.
En particular, PL utiliza el... EXTREMO, que es posiblemente una variedad débil debido a la
similitud y la construcción de bucle, C (Ritchie, 1974) utiliza {...}.
Todos los esquemas explícitos sucesión desafortunadamente tienen un defecto. Tratan la casos
"una declaración de" y "varias declaraciones de" manera diferente (uniformidad de una violación
del""). A menudo es necesario, para la depuración y modificación. para poner varias declaraciones
donde antes sólo uno estaba presente. Cualquier esquema de sucesión de explícito requiere
soportes para insertarse cada vez que esto se hace, que es una molestia importante. Esta
dificultad se resuelve por el método de auto-bracketing-construcciones. en que cualquier control
construcción que contienen algunas declaraciones está organizado para tener puntuación
distintivo sucesión el punto donde se produzcan las declaraciones. Por ejemplo, se puede añadir
una palabra clave que un IF para que sea la sucesión:
or
IF x
THEN
IF Y
THEN
ELSE
pertenece al IF externo en lugar de a la una interna. Este enfoque, que ha estado alrededor
durante bastante tiempo, parece interesante pero contiene algunos posibles peligros. La pérdida
de las palabras clave que es una importante pérdida de redundancia. Esto puede causar problemas
si se añade una nueva línea con un nivel de sangría incorrecta o si requieren de revisiones a una
sección de un programa de cambios generalizados en el nivel de sangría. También, indentación no
es generalmente significativa en lenguajes naturales: programadores no están acostumbrados a
considerar como significativos. Cuando las declaraciones se agrupan, es obviamente necesario
separarlos unos de otros. La Convención casi universalmente aceptada es utilizar el punto y coma
";".
Un tema menor del debate es si el punto y coma debe separar declaraciones ("BEGIN...; ... ; ...
FINAL") o les ("BEGIN...; ... ; ...: FINAL "). Usando como separador es tal vez más elegante y a veces
puede ser más fácil de analizar, pero las medidas reales (Gannon, 1975) muestran que la forma de
terminación-punto y coma es mucho menos propenso a errores. El constructo de control más
importante siguiente es la instrucción IF, que ofrece dos vías de acción. En esta elección, es muy
posible que una de las dos acciones es nula, lo que resulta en un familiar
IF boolean
THEN
statements
FI (the FI together with THEN serves to bracket the statement sequence). However, the second
action is not always null, which leads to the equally familiar
IF boolean
THEN
statements
ELSE
statements
FI
que proporciona para ambas acciones. Mediciones por Alexander (1972) de la frecuencia relativa
de estas dos formas revela que aproximadamente el 35 por ciento de IFs tienen un ELSE. Este
número es demasiado pequeño para justificar que requiere el otro; por lo que la IF debe aceptarse
como teniendo ambas formas. Un tema \\"111(.:h is relevant to several constructs, including the
IF, is the question"what is a Boolean expression?" Hasta la fecha, esta pregunta ha sido respondida
en al menos tres maneras diferentes: 1. "Booleana" es un tipo de datos independientes, con
constantes "TRUE" y "FALSE". Ciertos operadores, como los operadores de comparación,
resultados booleanos. 2.
"Boolean" equivale a "entero." True y false son distinto de cero y cero, respectivamente.
Rendimiento de los operadores de comparación 1 u o 3. "Boolean" equivale a "entero." True y
false son pares e impares, respectivamente (es decir, se distinguen por el bit de orden inferior del
entero). Operadores de comparación rendimiento 1 u O. De estos tres, el primero es preferible. La
dificultad con el segundo es que los programadores "inteligentes" a menudo usa su conocimiento
de cómo se evalúa la expresión booleana para escribir "IF x" donde debe escribir "IF x = 1." La
resultante pérdida de legibilidad es considerable. Objeciones similares se aplican a la tercera
alternativa.
Otro problema con los métodos de segundo y la terceros es que la confusión de "Boolean" y
"entero" interfiere con la comprobación de errores. Una generalización natural de la IF es de una
opción de dos vías a una opción de n-way. A menudo esta elección se hace en base al valor de una
variable de tipo entero. Se han propuesto varias formas de la construcción del caso, y varios temas
son de importancia. Primero, muchos mayores construcciones caso dependen el orden textual de
las alternativas para decidir a qué caso corresponde a que alternativa:
CASE choice-expression
OF
ESAC
statementsl OR
statements2 OR
(Tenga en cuenta que "o" se utiliza como un delimitador, no como el operador "o") Orden textual
lamentablemente abre posibilidades de error si un caso es olvidado o extraviado. Un esquema
mucho mejor es uno en que cada caso se etiqueta con los valores que la causan a elegir:
CASE choice-expression : 1 TO 10
OF
1: statements OR
2,3: statements OR
4 THRU 7: statements OR
10: statements
ESAC
Es generalmente deseable para especificar el rango de valores posibles de la elección. partl\' fllr
una implementación más fácil y en parte para brindar redundancia valiosa. Se ciefinitely i ~ útil ser
capaz de especificar la misma acción para más alld de valor a él capaz de dar un rango de valores
en lugar de escribirlos todos. Aras de la legibilidad es altamente deseable que los valores utilizados
en la lahels se limite a simples constantes.
Tenga en cuenta también que con este esquema no es realmente ninguna razón para restringir el
tipo de elección-expresión para ser "entero"; sería igualmente válida para tomar una decisión
sobre la base de un valor de tipo "carácter" o la enumeración, por ejemplo. Implementación
eficiente debe decidir qué limitaciones deben colocarse sobre el tipo de la opción expressioh. Otra
cuestión que es relevante es la cuestión de la aplicación precisa de tal caso. Por lo menos dos
alternativas sugieren ellos mismos: o bien utilizar el valor de la expresión de elección al índice en
una tabla de direcciones de la acción. o evaluar la expresión de la opción almacenar su valor y
comparar sucesivamente con cada etiqueta de valor.
Cada esquema tiene sus ventajas. Indexación es más rápido, mientras que (si las gamas se pueden
utilizar como etiquetas) control secuencial puede reducir noticeahl\ de requisitos de
almacenamiento '. Puede ser que vale la pena dar al programador una opción o permiten la
coll1piler elegir basado en una fórmula de optimización. Una pregunta natural que surge es ¿qué
pasa si el valor de la expresión de elección hace no correspolld a ninguna de las lahels valor? ¡Esto
definitivamente no debería causar un salto al azar a algún enfermo el código! La estrategia
habitual aquí es incluir una cláusula ELSE se ejecuta si ninguno de las alternativas seleccionadas:
CASE ....
ELSE
statements
También se ha sugerido (Weinberg et aI., 1975), con cierta justificación, que sería útil disponer de
una declaraciones de col1tainll1g cláusula (tal vez un "También") que serían ejecutados si
cualquiera de las alternativas marcadas fue ejecutado. Por ejemplo, esto proporcionaría una
manera simple de manejar asuntos como establecer una bandera de "actuar" o eliminación de un
elemento de datos procesados con éxito. A veces se sugiere que la sintaxis de IF debe combinar (l
con eso de la caja, ya que el si es realmente un caso especial del caso. Un argumento sólido, sin
embargo, es que la inmensa mayoría de las construcciones de elección utilizado IFs (Alejandro,
1972), y que la popularidad de la IF (particularmente con ninguna cláusula ELSE) justifica el sintaxis
especial para mejorar la legibilidad. ESAC
Es una construcción de caso más generalizada que ha sido propuesta por Dijkstra (1975) y
Weinberg et al (1975) de la forma CASE
ESAC
Boolean: statements OR
Boolean: statements OR
De esta forma, se evalúa cada uno de los "booleanos"; Cuando una de ellas es verdadera, se
ejecuta la correspondiente sección de "declaraciones". No está claro lo que debía suceder si dos o
más de los "booleanos" ambos suceden ser verdad: posiblemente debe usarse el primero de ellos
es verdadero. Esto puede proporcionar una forma más general útil de la comprobación secuencial
mencionado anteriormente como una aplicación plausible de lo habitual. Esta forma aparece
potencialmente muy útil, especialmente si está equipado con un otro y una cláusula también.
El papel de Weinberg también tiene algunas otras ideas interesantes en las estructuras de
elección. Otro constructo mayor control es el lazo. Una gran mayoría de sus usos se contabilizan
por el simple caso en el cual terminación ocurre sobre la base de una condición booleana. Uno de
los problemas es donde se debe comprobar la condición de terminación: al principio de cada
iteración, en el extremo, o incluso en el medio. Los tres casos son útiles. Por lejos la mejor solución
es la propuesta por Dahl (en Knuth, 1974) en la que el cheque es potencialmente en el medio:
LOOP
statements
UNTIL Boolean:
statements
REPEAT
Ninguno de los grupos de Estados puede ser null. Esta construcción abarca las tres formas.
También, en general, elimina la necesidad de tener construcciones especiales que terminan la
ejecución del bucle o reiniciar la iteración (escrito a menudo "salida" y "Ciclo," respectivamente).
Una cuestión menor en bucles de condición booleana es terminar la repetición en la condición
booleana ser verdadero ("hasta") o ser falso ("tiempo").
La opción tradicional es mientras. La escuela mientras que afirma que hasta el que centra la
atención en la terminación del bucle mientras que el tiempo centra donde pertenece, a saber, la
naturaleza de la repetición. La escuela hasta que afirma que el tiempo centra en la mecánica de la
repetición mientras que el hasta centra donde pertenece, es decir, con la condición (booleana)
que el bucle está trabajando. La otra forma notable de lazo es el iterativo para lazo, que altera el
valor de una variable de índice de algún modo predefinido en cada iteración. Aunque el bucle FOR
podría construirse por el usuario desde un bucle de Boolean-condición ordinario, este proceso de
construcción es complicado y propenso a errores, y por lo tanto es una buena idea tener un built-
in para el lazo.
Para lograr legibilidad razonable y fiabilidad, un bucle FOR tiene algunas restricciones bastante
extensas. Modificación de la variable de índice no se debe permitir dentro del bucle; el compilador
debe hacer cumplir esta restricción. Los valores utilizados para definir los parámetros de la
alteración de la variable de índice deben ser evaluados en la entrada al bucle y no deberían ser
alterados después de eso. No está claro a lo que el valor de la variable de índice debe estar en
salida del loop; una solución sencilla es decir que el mismo constituye la declaración de la variable
índice y que es accesible sólo dentro del bucle la variable de índice. Esto también alivia el
programador del fastidio de tener que declarar la variable de índice. El problema que queda es la
naturaleza de la variación de la variable índice.
DO
END
(Nótese el uso de "De" donde algunas formas de utilizan el operador de asignación; "Desde" es
probablemente más clara). Es deseable excluir números de punto flotante, sin embargo; las
peculiaridades de la aritmética floating-point complican demasiado las cosas y así interfieran en la
legibilidad. En idiomas con el "conjunto" como un tipo de datos básico. puede ser conveniente
tener una para que simplemente las secuencias a través de los miembros de un conjunto
proporcionado por el programador:
FOR x IN a_set
DO
END
Aquí la variable Índice x simplemente asume a su vez el valor de cada miembro del conjunto. ¿En
qué orden debe asumir la variable de índice los distintos valores? Quizá la solución menos ofensiva
es decir que el orden es definido por la aplicación y no debe ser dependía en programas. Aunque
el apPlroach de PASCAL para el bucle FOR se puede recomendar, diseñadores que deseen
experimentar tienen varias opciones. El bucle para 109 diseño de lenguaje de programación
basada en sistema discutido previamente es útil en una lengua que maneja conjuntos bien. Otra
propuesta, que ha sido experimentado por Gannon (1975), es un bucle FOR de la forma:
FOR x IN arrayname
DO
END
donde en cada iteración, la variable x del índice se refiere a un elemento de la matriz (es decir,
cualquier referencia a x se refiere a ese elemento de la matriz). Este bucle tiene la ventaja decidió
que anidado bucles no son necesarios para trabajar con matrices multidimensionales. Su
limitación es los bucles no son siempre para el manejo de la matriz. El procedimiento es otro
constructo de control fundamentales. El procedimiento es una de las herramientas más
importantes para romper grandes problemas en pequeños trozos, y esto tiene implicaciones para
su diseño. En particular, la interfaz de llamada de procedimiento debe ser eficiente y debe ser
posible comprobar los tipos de parámetros y resultados completamente.
Uno de los aspectos menos estandarizadas de procedimientos es el asunto del parámetro que se
pasa, aunque ahora parece que dos tipos de parámetros se utilizan más a menudo. El primero es el
parámetro de paso por valor, donde se evalúa una expresión y el resultado está a su disposición
como parámetro. El otro tipo útil de parámetro es el parámetro de pasada como variable (también
llamado paso por referencia), en el que una variable se pasa en modificarse. Esto puede
implementarse pasando un puntero o copiando en el valor inicial y copia el valor final.
Una menor importancia de la nota es que la sintaxis de paso por valor y paso como variable debe
ser distinta, por lo que el programador sabe qué variables pueden ser modificados por un
procedimiento. Tal vez debería decir: procedure_name (x, y, z AR V) pasar x y y como valores y z
como una variable. La utilidad de procedimientos propios que pasan como parámetros es
discutible para algunos tipos de idiomas. Si esta característica es rara vez se utiliza el diseñador de
lenguaje probablemente justificado en omitir, ya que puede ser simulado con no demasiada
dificultad, es difícil de implementar y hace que sea difícil para completar comprobación de errores
de tiempo de ejecución. Los mismos comentarios se aplican a los parámetros de paso-como-if-hy-
macrosubstitution de ALGOL 60.
El otro lado de la cuestión del paso de parámetro es the·matter de devolver un valor desde un
procedimiento. Algunas idiomas hacen una distinción entre las funciones, que devuelven valores y
procedimientos, que no. Tal distinción es probablemente más apropiada cuando; como en una
versión temprana de PASCAL, no se permiten funciones con efectos secundarios (es decir..
modificar parámetros o variables globales). Sin duda corresponde bien a la idea intuitiva del
programador de "hacer algo" versus "que rinde un valor." Sin embargo, cabe señalar que es muy
difícil hacer cumplir una regla de no efectos secundarios.
Un diseñador que no quiere devolver desde procedimientos excepto por "que fluye del extremo"
probablemente terminará por adopción 2 alternativas; mayoría de los otros diseñadores
probablemente optarán por la comodidad y la limpieza de la alternativa 1. Como una última
cuestión de la sintaxis debemos abordar la cuestión de lo que debería ser una llamada a un
procedimiento. ALGOL y sus descendientes simplemente usan el nombre del procedimiento como
una declaración. Esto parece el enfoque más sencillo para situaciones que impliquen un valor
devuelto. Por desgracia, hace llamadas de procedimiento puro ve como accesos variable, que es
probablemente indeseable, ya que los procedimientos no devuelven valores. Por esta razón,
algunos diseñadores de la lengua han introducido una palabra clave especial (por ejemplo, llamar)
para delinear claramente tqe dos tipos de invocaciones de procedimiento.
Ninguna consideración de procedimientos debe pasar por alto la cuestión de si debería aplicarse
como subrutinas o macros. Aunque es habitual la ejecución de la subrutina, se ha sugerido que el
programador debe ser capaz de decidir qué método utilizar, independientemente de los
contenidos del procedimiento sí mismo. Al diseñar un lenguaje para la implementación de
software de sistema, u otras tareas donde la velocidad de ejecución puede ser crítico, esta
flexibilidad puede ser beneficiosa. Sin embargo, cabe señalar que hay problemas en asegurar que
el procedimiento considera el medio ambiente de cualquier manera.
Se han realizado algunos estudios (Wulf, 1972b) de la cuestión más general de especificar la
interfaz a un procedimiento (es decir, convenciones de llamada) de una manera
languageindependent. Este simplificar la vinculación entre diferentes lenguas, permiten
optimización de secuencias de acoplamiento para situaciones críticas y simplificar el manejo de
cosas tales como listas de parámetros de longitud variable. La cuestión es, sin embargo, es
bastante complejo porque las dependencias de la máquina del arrastramiento; sin embargo ha
aparecido ninguna solución excelente. Las construcciones de control más controvertidas
actualmente son las construcciones de "escape", que permiten salidas desde el concepto de "solo
entrada, solo salida" de las estructuras de control. El retorno mencionado anteriormente con
respecto a procedimientos es un escape, y algunas lenguas no lo tienen. Generalmente, el retorno
es suficientemente útil y su funcionamiento es suficientemente claro que pocas personas se
oponían a él.
Otro muy restringido pero construcción de escape muy valiosa es la instrucción de parada, que se
utiliza para terminar la ejecución del programa. PARADA es inestimable para hacer frente a
situaciones de error; debería estar disponible en cualquier idioma que funciona bajo un sistema
operativo (es decir, que puede asociar una acción significativa parada). De las construcciones más
controversiales, prohably el mejor es de Zuhn situationdriven, publicitado por Knuth (1974). Un
ejemplo es aproximadamente como sigue (con algunas alteraciones en la sintaxis original):
Se ha hecho algún trabajo en particular por Brinch Hansen (1973, 1974, 1975). en estructuras de
control para la gestión de procesos paralelos. Esta área está madurando y se está convirtiendo en
cada vez más importante tener estas características en un lenguaje, al menos para algunas áreas
de trabajo tales como software de comunicaciones. Un área importante de las estructuras de
control que no ha sido completamente investigado es la cuestión del manejo de excepciones.
¿Cómo funciona un construir estructuras de control para hacer frente a informar y tratar las
condiciones de error, ya sea detectada por el hardware, los controles de tiempo de ejecución o el
programa de usuario? Existe un número de preguntas abiertas, y sin duda se realizará más
investigación en esta área. Un aspecto menor de estructuras de control es la pregunta: ¿donde
comienza el programa cxecution?
Es un enfoque bueno y legible tomado en PL / I en el que la ejecución comienza con una llamada a
un procedimiento llamado "principal". Esto también proporciona un medio elegante de pasar
parámetros a un programa como parámetros "principal." C (Ritchie, 1974) proporciona un tipo de
construcción similar. Una estructura útil que no ha aceptado ampliamente es el coroutine.
Coroutines puede considerarse como una generalización de los procedimientos o una simulación
especializada, altamente eficiente de los procesos de paralIel. La idea es que un coroutine activo
puede pasar control a otro coroutine activo. Cuando devuelve el control a un coroutine
anteriormente invocado, este coroutine lleva en donde se quedó, reasumir la ejecución
inmediatamente después de la construcción de control de cambios como resultado de la última
transferencia de control.
Coroutines son particularmente útiles para las situaciones donde una parte de un programa
"alimenta" otra parte un flujo de datos; muchos programas se pueden caracterizar de esta
manera. Una manera fácil de implementar un programa es mediante procesos paralelos;
Lamentablemente, esto es prohibitivamente ineficiente en la mayoría de los sistemas. Coroutines
pueden hacerse casi tan eficientes como llamadas a procedimientos. No está claro si el número de
coroutines activadas debe ser constante en tiempo de ejecución (es decir, un coroutine es una
clase especial de procedimiento) o variable en tiempo de ejecución (es decir, un coroutine es una
clase especial de invocación de procedimiento).
Parece que la primera alternativa maneja más req ui remen ts. Dos construcciones muy útiles, que
no pueden pertenecer estrictamente bajo "estructuras de control" pero que no claramente
pertenecen en cualquier otra cosa, son las afirmaciones y las relaciones de invariancia. Estas
construcciones basicalIy ofrecer al programador una oportunidad para (booleanas) condiciones
que deben cumplirse en un punto en el programa (afirmaciones) o por todas partes en el
programa (relaciones de invariancia) del estado. Estas nociones no se han aplicado a lenguajes
utilizados para la producción de programación principalmente debido a la dificultad en probar
grandes programas correctos. Otra vez, esto puede cambiar como producto de la investigación en
esta área y sin duda el desarrollo de EUCLID (Lampson et aI., 1977) es un paso en esta dirección.
Para más información en esta área vale la pena leer el informe sobre Euclides.
Aunque las afirmaciones y las relaciones de invariancia se tratan simplemente como otra forma de
comentario, son valiosos. También es posible (Clark 1971) para que el compilador genere código
para comprobar en tiempo de ejecución que las condiciones son verdaderas. Esta característica
puede ayudar a verificación considerablemente. Por último, existe la posibilidad de poder aplicar
las técnicas de prueba de teorema automatizadas a tales afirmaciones; Esto simplificaría
enormemente escribir programas correctos.
Un problema con este enfoque es que los lenguajes de programación actuales no suelen ser en un
alto suficiente nivel para expresar afirmaciones complejas fácilmente. Una serie de construcciones
de control ha sido propo ~ ed para operaciones no deterministas y "retroceso" (desentrañar los
efectos del código anterior para volver a intentarlo), como se discute en Prenner et al (1972) y
Bobrow y Rafael (1974). Estas construcciones son particularmente aplicables en inteligencia
artificial. Intencionalmente hemos ignorado la construcción ir porque la experiencia actual indica
que con un decente sistema de control de construcciones, el GOTO es simplemente innecesario
(Wulf, 1972a). Un común pensamiento entre los partidarios del GOTO es: "quizás necesito; algo
podría venir ". La respuesta parece ser: "Nada nunca lo hace." No puede enfatizarse demasiado
fuertemente que esta declaración se aplica sólo si se proporciona un buen conjunto de
construcciones de no ir. Muchos idiomas son algo deficientes en esta materia. Las siguientes
construcciones de control son claros fracasos o intentos primitivos haciendo lo que ha hecho
mejor y más simplemente: 1. variables de la etiqueta. 2. modificación de la instrucción dinámica
(por ejemplo, ALTER de COBOL). 3. el excesivamente restrictivas FORTRAN-loop. El diseñador
aconseja bien a ignorar a estos.
"Estructura de compilación" es el término usado aquí para cubrir ciertos aspectos de una lengua
que atan muy fuertemente con el proceso de compilación. Los temas principales de interés son las
directivas para el compilador, las operaciones dentro de la lengua que se realizan en tiempo de
compilación en lugar de en tiempo de ejecución y la cuestión de la compilación separada de
diferentes "módulos" de un programa. A menudo es deseable dar el compilador ciertos elementos
de información que no puede expresarse fácilmente en la lengua sí mismo. El enfoque más común
a esto es tener una forma especial de comentario (el término "pragmática" del ALGOL 68 parece
ser la palabra más adecuada para él) que no tiene ninguna importancia a la lengua sí mismo pero
que contiene directivas para el compilador, en lugar de texto comentario normal.
Un método para distinguir pragmats de comentario normal es que comience con el delimitador de
comentario normal, que es seguido por una secuencia de caracteres. Esta secuencia debe ser
extremadamente improbable en el texto del comentario. Por ejemplo:
/ / This is a comment.
El contenido real de una pragmática dependen, por supuesto, compilador; una pragmática puede
contener ajustes de opciones, consejos de optimización, las especificaciones de formato de lista y
así sucesivamente. Otra, quizá mejor, manera de manejar pragmats es definirlos en la estructura
de la frase de la lengua para que el analizador puede analizarles y construir árboles para ellos si es
necesario. Idiomas varían grandemente en la cantidad de manipulación posible en tiempo de
compilación. Es notable que rara vez se utiliza la mayoría de las instalaciones para operaciones de
tiempo de compilación; uno o dos constructos específicos parecen llenar necesidades más.
El más notable tal facilidad es la capacidad de r ~ quest que el compilador insertar el contenido de
otro archivo de código fuente en un punto específico en su secuencia de entrada (PL / I «%
incluyen» instalación). Una lengua debe tener esta característica o un servicio equivalente. Una
pragmática podría ser utilizado para especificar la inclusión.
El otro gran tiempo de compilación es compilación condicional ~ ahility ignorar selectivas partes de
la entrada de texto en base a condiciones de compilación tllne. Si bien hay varias maneras de
hacer esto, el método utilizado hy ALGOL 68 (Van Wijngaarden et al., 1975) es muy notable, ya
que no requiere la introducción de otra construcción en el lenguaje. El ALGOL 68 es simplemente
para comprobar las expresiones de selección de IFs y casos y ee si pueden evaluadas en
compilación tllne. Si puede, sólo uno de la declaración de alternati\e secuencias necesitan ser
compilados (nota que es todavía accesible para verificación de error los otros).
Si el lenguaje contiene una de las más complejas técnicas de abstracción de datos, esta técnica
puede utilizarse como base para la definición de "módulo". ADA es compatible con muchas de
estas ideas y un debate de estas características se da en segundos 3-8. Es conveniente especificar
las posibles interacciones entre módulos en lugar de adoptar una política de "todo vale"; algunos
trabajos se ha realizado sobre este (DeRemer, 1976,) ' especialmente como estos conceptos se
refieren a la ejecución de las instalaciones de la abstracción de datos.