Precedentes (I)
El diseño de lenguajes es un problema complejo
En los lenguajes de programación hay muchos errores de diseño
Se han considerado ideas erróneas
FORTRAN
uso del GOTO
declaración implícita de variables
COBOL
Sentencias demasiado largas
Farragoso de escribir y leer
Uso del GOTO
APL
si puede significar algo, lo significa
LISP
una sintaxis simple es más fácil de aprender
Los paréntesis dificultan escribir programas
La lectura es compleja por una sintaxis demasiado homogénea
Precedentes (II)
C
el programador sabe lo que hace
Obtener direcciones de variables locales
Printf no comprueba el tipo y número de los argumentos
C++
se puede extender C al paradigma orientado a objeto (demasiados compromisos de compatibilidad)
Uso intensivo de apuntadores a objetos
Lenguaje demasiado complicado
Un lenguaje se puede utilizar para todo tipo de desarrollo de software
Aun con muchos errores de diseño es importante estudiar los lenguajes de programación existentes ya que aportan muchas ideas
Consideraciones Preliminares
¿Cual es el propósito del lenguaje?
No hay un lenguaje bueno para todo
Aplicación específica
Bases de datos, sistemas expertos, cálculo numérico, programación simbólica, diseño algorítmico, etc.
¿Es necesario diseñar un nuevo lenguaje?
Ya existe un lenguaje apropiado
El nuevo lenguaje se diferencia de los existentes
Se consume demasiado tiempo en el diseño e implementación de un nuevo lenguaje
Es demasiado fácil diseñar un lenguaje incompleto
Lenguaje demasiado especializado
Sacrificar características del lenguaje por un compilador simple.
Otras opciones
Un modulo o librería de funciones
Ampliar un lenguaje de programación
Fuentes de Ideas
Lenguaje natural (COBOL)
Fácil de leer y escribir
Ambiguo
Solo se puede implementar un subconjunto
Lenguajes matemáticos (APL)
No ambiguos
Crípticos
La programación no es matemáticas
Lenguajes de programación
Errores de diseño (pasado)
Hay que valorar cada característica de un lenguaje existente antes de incluirla en el nuevo lenguaje
Pruebas y experimentos
Programar con el lenguaje antes de haber acabado su diseño
Sirve para detectar los problemas del diseño y tomar decisiones difíciles.
Objetivos y Filosofías de Diseño
Comunicación humana
Prevención y detección de errores
Usabilidad
Efectividad
Compilabilidad
Eficiencia
Independencia de la máquina
Simplicidad
Uniformidad
Ortogonalidad
Generalización y especialización
Otras filosofías de diseño
Comunicación humana (I)
Se busca una comunicación eficiente entre el programador y el ordenador
Un buen nivel de comunicación se da cuando los programas son leíbles
No ha de ser necesaria una documentación externa al programa (minimizar)
Es más importante que un programa sea leíble que escribible
Un programa se escribe una vez, pero se lee muchas durante su depuración, documentación y mantenimiento.
Tendencia actual a separar la interfaz de la implementación de un módulo
La sintaxis ha de reflejar la semántica
Reducir las manipulaciones implícitas
Coerciones (coerciones de PL/I o C)
ON de BASIC para eventos o excepciones
Constructores y destructores de C++ (necesarios, pero complican el seguimiento del flujo de ejecución)
Comunicación humana (II)
El lenguaje ha de representar los patrones de pensamiento humanos
No hay que crear una sintaxis pensada exclusivamente para
un modelo de cómputo teórico (l-calculus)
un conjunto de instrucciones de la máquina
facilitar la compilación (forth)
El programador no es un ordenador
Que el compilador entienda una estructura es posible que el programador no
Evitar incluso la posibilidad de escribirlas
Reducir el conocimiento contextual
El programador no funciona con una pila como el programa compilado.
Prevención y detección de errores
El programador comete errores
Hay que prevenir los errores
El programador es su fuente
El programador no sabe lo que hace y el compilador ha de limitar sus acciones (EUCLID, PASCAL)
Hacer imposible cierto tipo de errores
Ejecutar datos -> control de flujo limitado
Errores en el uso de datos -> Tipado fuerte
Apuntadores erróneos -> Gestión de memoria implícita (LISP, PROLOG, ML, etc).
Hay que facilitar su detección, identificación y corrección
Redundancia
Tener que declarar antes de utilizar.
Evitar coerciones inductoras de errores
float a int por su perdida de precisión
Comprobaciones en tiempo de ejecución
Indice de array fuera de limites
Control sobre los apuntadores a NULL
Lenguaje Utilizable y Efectivo
Un lenguaje ha de ser fácil de utilizar
Un lenguaje ha de ser fácil de aprender y recordar
Evitar la necesidad de consultar el manual (C++ no cumple)
Lenguaje simple (C++ no cumple)
Aprendizaje incremental (PROLOG no cumple, LISP si cumple)
El comportamiento del lenguaje ha de ser predecible
el uso de void* de C++ es incomprensible
Efectividad
Los detalles de implementación no han de oscurecer las intenciones del programador
Soportar abstracción
Modularidad: Separar especificación de implementación
Los Efectos de un cambio han de quedar localizados
Evitar los trucos (programas ilegible)
Otras filosofías de diseño
Compilabilidad
Se ha de poder compilar programa en un tiempo reducido
Se ha de poder depurar o aplicar otras herramientas de análisis
Eficiencia: La ejecución ha de ser rápida
Independencia de la máquina
Simplicidad
Uniformidad: lenguaje predecible
Ortogonalidad
Todas las características del lenguaje se han de poder combinar
Generalización y especialización
La generalización dice que algo similar también es correcto, pero es difícil de implementar
Hay que especializar para facilitar la implementación sin perder la utilidad del lenguaje
Diseño Detallado
Microestructura
Estructura de las expresiones
Estructuras de datos
Estructuras de control
Estructura de compilación
Estructura de la entrada/salida
Ejemplos: Lenguajes de Programación y su Diseño
El lenguaje C
Aplicación: programación de sistemas
Programador: experto
Sacar el mayor rendimiento posible del ordenador
PASCAL
Aplicación: docencia
Programador: inexperto
LISP
Aplicación: procesamiento simbólico
Desarrollo rápido
PROLOG
Aplicación: procesamiento simbólico
Programación lógica
Desarrollo rápido
El Lenguaje C (I)
Lenguaje pensado para el desarrollo de sistemas operativos (unix)
Rendimiento/velocidad de ejecución
Programador experto
Código compacto
Redimiento/Velocidad de ejecución
Operaciones básicas simples
Apuntadores
uso para los strings y arrays
Estructuras de datos referenciadas por apuntadores (paso por referencia explícito)
Gestión de memoria explícita
En las primeras versiones las funciones solo retornan datos que puedan ir en un registro del procesador
Optimización explícita
Incremento y decremento
Operaciones con asignación incluida
variables register
El Lenguaje C (II)
En contra de la velocidad
Cálculos en coma flotante con la máxima precisión posible
Programador experto
Amplio uso de coerciones
Implícitas
char -> int -> double
double void*
Explícitas
El programador puede convertir cualquier tipo de datos en cualquier otro.
int -> void*
Flujo de ejecución
break y continue
goto local
long jump
Obtención de direcciones
Se puede obtener la dirección de cualquier variable aunque esta sea local
El Lenguaje C (III)
Prioridades de operadores complejas
Casi cualquier combinación de operadores es aceptada por el compilador
Otras cuestiones
Falta de un método por defecto para la gestión de excepciones
Códigos de error retornados por las funciones de la librería que continuamente ha de verificar el programa
Faltan comentarios de una línea
Palabras clave con diferentes significados según el contexto
static
Semántica ambigua
Incrementos en una expresión
Métodos para la escritura de programas transportables soportados por el programador
Compilación condicional
sizeof
El Lenguaje C (IV)
Sintaxis de los comentarios confundible (A=b/*p)
Código compacto
La mayor parte de las instrucciones se traducen a unas pocas instrucciones de código máquina
Faltan instrucciones de alto nivel
Strings
Tratamiento por apuntadores
Gestión explicita de la memoria
Considerar continuamente si la reserva de memoria es suficiente
No hay copia de datos compuestos
Asignación array a array
PASCAL
Docencia
Estructura muy rígida de los programas
Un buen nivel de abstracción
Transportable
Lenguaje simplificado
Falta de módulos y librerías
Librería básica muy limitada
Escritura farragosa de los programas
Minimización del uso de coerciones
Todo lo que hace el programa se indica explícitamente
Pensado para la resolución de problemas por subdivisión
Definiciones anidables de funciones y procedimientos
Tipado fuerte
LISP
Sintaxis demasiado simple
Difícil escritura y lectura de los programas
Comprobaciones semánticas en tiempo de ejecución
Variables declaradas
Tipos de datos
Enfoque pragmático
Programación imperativa y funcional
Orientado a optimizar el desarrollo frente a la velocidad de ejecución
Gestión de memoria implícita
Uso de listas para todo
Amplia librería de funciones
Diseño por etapas
Funciones repetidas
setq y setf
Parches para mantener compatibilidad
forma de creación de los places
PROLOG
Programación declarativa y lógica
Implementación del cálculo de predicados
Faltan funciones (l-prolog)
Difícil modularidad
Un método de programación demasiado alejado de la programación habitual
Gramática de operadores extensible
Totalmente transportable
Modulariza las búsquedas
Convierte los bucles de una búsqueda en una secuencia lineal de aplicación de predicados
C++
Orientación a objeto de C
Mantener la compatibilidad con C
Uso masivo de apuntadores
Tratamiento de excepciones no soportado por las librerías estándar
Rendimiento
Complica el lenguaje
Static en métodos
Métodos virtuales y no virtuales
Templates
Destructores por gestión de memoria explícita
Apuntadores y referencias
Otros
comentarios de una línea fáciles de escribir y leer (repetición de //)
Implementación parcial de las coerciones
ADA
Lenguaje pensado para grandes proyectos de software
Facilitar el uso de librerías
Interfaz separa de la implementación del módulo
Control exhaustivo sobre la interfaz
Fácil lectura y detección de posibles bugs
Sintaxis de las instrucciones de control de flujo con marcadores claros de inicio y fin (If then else end if)
Etiquetas visibles
Coerciones explicitas
Reducción de los errores
Definición de excepciones para controlar errores de ejecución
Acceso a array fuera de rango
Un case del que no se selecciona ninguna opción
Atributos asociados a las variables:
un array tiene asociado el atributo de su tamaño
Opciones en el Diseño (I)
Sintaxis de control de flujo
Instrucción compuesta
C, PASCAL (for (;;) ; {…}
Instrucciones de control de flujo con claros marcadores de inicio y fin
ADA
Instrucción vacía
Explicita ADA (null)
Implícita C, PASCAL (; o nada)
Lenguaje orientado a
Instrucción: ADA, PASCAL
Expresión: C, funcionales
Marcadores de las estructuras sintácticas
Si: PASCAL, ADA
No: C, C++
Expresiones
Riqueza de operadores C, C++
Pobreza de operadores PASCAL, ADA
Opciones en el Diseño (II)
Sistema de tipos
No tipado: LISP, BASIC
Tipado: C, PASCAL, ADA, etc.
Código genérico
Datos marcados con su tipo en ejecución
Tipos paramétricos
templates
Gestión de memoria
Explicita: C, PASCAL, ADA
Velocidad
Implícita: LISP, PROLOG, Java, funcionales
Simplicidad
Gestión de errores de ejecución
Responsabilidad del programador: C, C++
Responsabilidad del lenguaje ADA
Excepciones sin declarar C++
Excepciones declaradas Java
Opciones en el Diseño (III)
Estructura declaraciones
Rígida: PASCAL, ADA
Poco rígida: C
Libre: C++
Ámbitos
Expresión LISP
Instrucción compuesta C, C++, Java
Función C, C++, PASCAL, ADA
Espacios de nombres
Clases
Paquetes
Módulos
Coerciones
Explicitas: ADA
Pocas implícitas: PASCAL
Muchas implícitas: C, C++