DSN_XP y el genio de Edsger W. Dijkstra

 Humildemente programador



Uno de los actores fundamentales para el pensar filosófico de DSN_XP es Edsger W. Dijkstra de quien se transcribe uno de sus artículos para entendimiento de nuestro planteamiento.

Programador (Codificador)

Como resultado de una larga secuencia de coincidencias, entré oficialmente a la profesión de programador la primera mañana de primavera de 1952 y, por lo que he podido rastrear, fui el primer holandés en hacerlo en mi país. En retrospectiva, lo más sorprendente fue la lentitud con la que, al menos en mi parte del mundo, surgió la profesión de la programación, una lentitud que ahora es difícil de creer. Pero agradezco dos vívidos recuerdos de ese período que establecen esa lentitud más allá de toda duda.

Después de haber programado durante unos tres años, tuve una discusión con A. van Wijngaarden, que entonces era mi jefe en el Centro Matemático de Ámsterdam, una discusión por la que le estaré agradecido mientras viva. El punto era que se suponía que debía estudiar física teórica en la Universidad de Leiden simultáneamente y como encontraba las dos actividades cada vez más difíciles de combinar, tuve que tomar una decisión, o dejar de programar y convertirme en un verdadero y respetable teórico. físico, o llevar mi estudio de física a una finalización formal solamente, con un mínimo de esfuerzo, y llegar a ser ....., ¿sí qué? 

¿Un programador? ¿Pero era esa una profesión respetable? Porque después de todo ¿Qué era la programación? ¿Dónde estaba el sólido cuerpo de conocimientos que podría respaldarlo como una disciplina intelectualmente respetable? 

Recuerdo muy vívidamente cómo envidiaba a mis colegas de hardware, quienes, cuando se les preguntó sobre su competencia profesional, al menos pudieron señalar que sabían todo sobre válvulas de vacío, amplificadores y demás, mientras que yo sentí que, al enfrentarme a esa pregunta, yo se quedaría con las manos vacías. 

Lleno de recelo, llamé a la puerta de la oficina de van Wijngaarden, preguntándole si podía “hablar con él un momento”; cuando salí de su oficina varias horas después, era otra persona. Porque después de haber escuchado mis problemas con paciencia, estuvo de acuerdo en que hasta ese momento no había mucha disciplina de programación, pero luego pasó a explicar en voz baja que las computadoras automáticas llegaron para quedarse. que estábamos al principio y ¿no podría ser yo una de las personas llamadas a hacer de la programación una disciplina respetable en los próximos años? 

Este fue un punto de inflexión en mi vida y completé formalmente mis estudios de física tan rápido como pude. Una moraleja de la historia anterior es, por supuesto, que debemos tener mucho cuidado cuando damos consejos a los más jóvenes; ¡a veces lo siguen!

Otros dos años más tarde, en 1957, me casé y los ritos matrimoniales holandeses requieren que declares tu profesión y yo dije que yo era programador. Pero las autoridades municipales de la ciudad de Ámsterdam no lo aceptaron alegando que no existía tal profesión. Y, lo crea o no, ¡pero bajo el título "profesión" mi acto matrimonial muestra la ridícula entrada "físico teórico"!

Hasta aquí la lentitud con la que vi emerger la profesión de programador en mi propio país. Desde entonces he visto más del mundo y tengo la impresión general de que en otros países, aparte de un posible cambio de fechas, el patrón de crecimiento ha sido muy similar.

Estructuras programáticas

Permítanme intentar capturar la situación de aquellos tiempos con un poco más de detalle, con la esperanza de comprender mejor la situación actual. Mientras continuamos con nuestro análisis, veremos cuántos malentendidos comunes sobre la verdadera naturaleza de la tarea de programación se remontan a ese pasado ahora lejano.

Las primeras computadoras electrónicas automáticas eran todas máquinas únicas de una sola copia y todas se encontraban en un entorno con el emocionante sabor de un laboratorio experimental. Una vez que la visión de la computadora automática estuvo ahí, su realización fue un tremendo desafío para la tecnología electrónica disponible en ese momento y una cosa es cierta: no podemos negar el coraje de los grupos que decidieron intentar construir un equipo tan fantástico. 

En cuanto a piezas de equipo fantásticas, lo fueron: en retrospectiva, uno solo puede sorprenderse de que esas primeras máquinas funcionaran, al menos a veces. El problema abrumador era conseguir y mantener la máquina en funcionamiento. La preocupación por los aspectos físicos de la computación automática todavía se refleja en los nombres de las sociedades científicas más antiguas en el campo,

El codificador

¿Y el pobre programador? Bueno, a decir verdad: apenas se notó.

Por un lado, las primeras máquinas eran tan voluminosas que apenas se podían mover y, además, requerían un mantenimiento tan extenso que era bastante natural que el lugar donde la gente intentaba usar la máquina fuera el mismo laboratorio donde se había desarrollado la máquina.

En segundo lugar, su trabajo un tanto invisible carecía de glamour: se podía mostrar la máquina a los visitantes y eso era varios órdenes de magnitud más espectacular que algunas hojas de codificación. 

Pero lo más importante de todo, el propio programador tenía una visión muy modesta de su propio trabajo: su trabajo se derivaba de toda la importancia de la existencia de esa maravillosa máquina. Como se trataba de una máquina única, sabía muy bien que sus programas solo tenían un significado local y también, como era claramente obvio que esta máquina tendría una vida útil limitada, sabía que, muy poco de su trabajo tendría un valor duradero

Finalmente, existe otra circunstancia que influyó profundamente en la actitud del programador hacia su trabajo: por un lado, además de poco fiable, su máquina solía ser demasiado lenta y su memoria solía ser demasiado pequeña, es decir, se enfrentaba a una "rozadura de zapato", mientras que, por otro lado, su código de pedido generalmente algo extraño se adaptaría a las construcciones más inesperadas. 

Y en aquellos días, muchos programadores inteligentes obtenían una inmensa satisfacción intelectual de los astutos trucos con los que se las ingeniaba para meter lo imposible en las limitaciones de su equipo, sabía que muy poco de su trabajo tendría un valor duradero.

Escuelas de diseño

Dos opiniones sobre la programación datan de esos días. Las menciono ahora, volveré a ellas más tarde. Una opinión era que un programador realmente competente debería tener una mentalidad de rompecabezas y ser muy aficionado a los trucos inteligentes; la otra opinión fue que la programación no era más que optimizar la eficiencia del proceso computacional, en una dirección u otra.

Esta última opinión fue el resultado de la circunstancia frecuente de que, de hecho, el equipo disponible era un dolorosa remordedura de zapato y en aquellos días uno se encontraba a menudo con la ingenua expectativa de que, una vez que se dispusiera de máquinas más potentes, la programación ya no sería un problema, porque entonces la lucha por llevar la máquina al límite ya no sería necesaria y de eso se trataba la programación, ¿no? 

Pero en las décadas siguientes sucedió algo completamente diferente: se dispuso de máquinas más potentes, no solo en un orden de magnitud más potente, incluso varios órdenes de magnitud más potentes. Pero en lugar de encontrarnos en el estado de eterna felicidad de todos los problemas de programación resueltos, ¡nos encontramos hasta el cuello en la crisis del software! ¿Cómo?

Crisis del software debida al hardware

Hay una causa menor: en uno o dos aspectos, la maquinaria moderna es básicamente más difícil de manejar que la vieja. En primer lugar, tenemos las interrupciones de E / S, que ocurren en momentos impredecibles e irreproducibles; en comparación con la vieja máquina secuencial que pretendía ser un autómata completamente determinista, este ha sido un cambio dramático y las canas de muchos programadores de sistemas dan testimonio del hecho de que no debemos hablar a la ligera sobre los problemas lógicos creados por esa característica

En segundo lugar, tenemos máquinas equipadas con almacenes multinivel, que nos presentan problemas de estrategia de gestión que, a pesar de la extensa literatura sobre el tema, siguen siendo bastante esquivos. 

Hasta aquí la complicación añadida debido a los cambios estructurales de las máquinas reales.

Pero llamé a esto una causa menor; la causa principal es ... ¡que las máquinas se han vuelto varios órdenes de magnitud más poderosas! Para decirlo sin rodeos: mientras no habían máquinas, la programación no suponía ningún problema; cuando teníamos algunas computadoras débiles, la programación se convirtió en un problema leve y ahora tenemos computadoras gigantes, la programación se había convertido en un problema igualmente gigantesco

En este sentido la industria electrónica no ha resuelto un solo problema, solo los ha creado, ha creado el problema del uso de sus productos. Para decirlo de otra manera: a medida que el poder de las máquinas disponibles creció en un factor de más de mil, la ambición de la sociedad por aplicar estas máquinas creció en proporción y fue el pobre programador quien encontró su trabajo en este campo explosivo de tensión entre fines y medios

El aumento de potencia del hardware, junto con el aumento quizás aún más dramático de su confiabilidad, hicieron factibles soluciones con las que el programador no se había atrevido a soñar unos años antes. Y ahora, unos años después, él tuvo que soñar con ellas y, lo que es peor, ¡tuvo que transformar esos sueños en realidad! ¿Es de extrañar que nos encontremos en una crisis de software? No, ciertamente no, y como puede suponer, incluso se predijo con mucha anticipación; pero el problema con los profetas menores, por supuesto, es que sólo cinco años después se sabe realmente que tenían razón.

Luego, a mediados de los sesenta, sucedió algo terrible: aparecieron los ordenadores de la llamada tercera generación. La literatura oficial nos dice que su relación precio / rendimiento ha sido uno de los principales objetivos de diseño. Pero si toma como "rendimiento" el ciclo de trabajo de los diversos componentes de la máquina, poco le impedirá terminar con un diseño en el que la mayor parte de su objetivo de rendimiento se alcance mediante actividades internas de mantenimiento de dudosa necesidad. Y si su definición de precio es el precio a pagar por el hardware, poco le impedirá terminar con un diseño que es terriblemente difícil de programar: por ejemplo, el código de pedido puede ser tal que lo imponga, ya sea al programador o sobre el sistema, decisiones vinculantes tempranas que presentan conflictos que realmente no se pueden resolver.

Cuando se anunciaron estas máquinas y se conocieron sus especificaciones funcionales, algunos de nosotros debieron de ser bastante miserables; al menos yo lo estaba. Era razonable esperar que tales máquinas inundaran la comunidad informática y, por lo tanto, era aún más importante que su diseño fuera lo más sólido posible. 

Pero el diseño contenía defectos tan graves que sentí que de un solo golpe el progreso de la informática se había retrasado al menos diez años: fue entonces cuando tuve la semana más negra de toda mi vida profesional.

Diseño de hardware e impacto en software

Quizás lo más triste ahora es que, incluso después de todos esos años de experiencia frustrante, muchas personas creen honestamente que alguna ley de la naturaleza nos dice que las máquinas tienen que ser así. Silencian sus dudas al observar cuántas de estas máquinas se han vendido y de esa observación derivan la falsa sensación de seguridad de que, después de todo, el diseño no puede haber sido tan malo. Pero si se examina más de cerca, esa línea de defensa tiene la misma fuerza convincente que el argumento de que fumar cigarrillos debe ser saludable porque mucha gente lo hace.

Es en este sentido que lamento que no sea habitual que las revistas científicas del área de la informática publiquen reseñas de computadoras recién anunciadas de la misma manera que revisamos publicaciones científicas: revisar las máquinas sería al menos tan importante. 

Y aquí tengo una confesión que hacer: a principios de los años sesenta escribí una reseña de este tipo con la intención de presentarla al MCCA, pero a pesar de que los pocos compañeros a los que se envió el texto en busca de sus consejos, me urgieron todo para hacerlo, no me atrevía a hacerlo, temiendo que las dificultades para mí o para el consejo editorial fueran demasiado grandes. 

Esta represión fue un acto de cobardía de mi parte por el que me culpo cada vez más. Las dificultades que preveía eran consecuencia de la ausencia de criterios generalmente aceptados y aunque estaba convencido de la validez de los criterios que había elegido aplicar, temía que mi reseña fuera rechazada o descartada por “cuestión de gusto personal”. Sigo pensando que estas revisiones serían extremadamente útiles y anhelo verlas aparecer, ya que su apariencia aceptada sería un signo seguro de madurez de la comunidad informática.

Diseño de software programable

La razón por la que he prestado la atención anterior a la escena del hardware es porque tengo la sensación de que uno de los aspectos más importantes de cualquier herramienta informática es su influencia en los hábitos de pensamiento de quienes intentan utilizarla y porque tengo razones para creer que esa influencia es muchas veces más fuerte de lo que comúnmente se supone. Pasemos ahora nuestra atención a la escena del software.

Aquí la diversidad ha sido tan grande que debo limitarme a unos pocos escalones. Soy dolorosamente consciente de la arbitrariedad de mi elección y le ruego que no saque ninguna conclusión con respecto a mi apreciación de los muchos esfuerzos que quedarán sin mencionar.

Estructura modular en el software

Al principio existía el EDSAC en Cambridge, Inglaterra y creo que es bastante impresionante que desde el principio la noción de una biblioteca de subrutinas haya jugado un papel central en el diseño de esa máquina y en la forma en que debería usarse. Han pasado casi 25 años y la escena de la computación ha cambiado drásticamente, pero la noción de software básico sigue con nosotros y la noción de subrutina cerrada sigue siendo uno de los conceptos clave en la programación.

Deberíamos reconocer las subrutinas cerradas como uno de los mayores inventos de software; ha sobrevivido a tres generaciones de computadoras y sobrevivirá a algunas más, porque se encarga de la implementación de uno de nuestros patrones básicos de abstracción

Lamentablemente, su importancia se ha subestimado en el diseño de las computadoras de tercera generación, en el que el gran número de registros explícitamente nombrados de la unidad aritmética implica una gran sobrecarga en el mecanismo de subrutina. Pero incluso eso no acabó con el concepto de subrutina y solo podemos rezar para que la mutación no sea hereditaria.

El segundo desarrollo importante en la escena del software que me gustaría mencionar es el nacimiento de FORTRAN. En ese momento se trataba de un proyecto de gran temeridad y los responsables del mismo merecen nuestra gran admiración. Sería absolutamente injusto culparlos por deficiencias que solo se hicieron evidentes después de una década de uso extensivo: ¡los grupos con una anticipación exitosa de diez años son bastante raros! En retrospectiva, debemos calificar a FORTRAN como una técnica de codificación exitosa, pero con muy pocas ayudas efectivas para la concepción, ayudas que ahora se necesitan con tanta urgencia que ha llegado el momento de considerarlas obsoletas

Cuanto antes olvidemos que FORTRAN ha existido, mejor, porque como vehículo del pensamiento ya no es adecuado: desperdicia nuestra capacidad intelectual, es demasiado arriesgado y, por lo tanto, demasiado caro de usar. El trágico destino de FORTRAN ha sido su amplia aceptación, encadenando mentalmente a miles y miles de programadores a nuestros errores pasados. Rezo a diario para que más de mis compañeros programadores encuentren la manera de liberarse de la maldición de la compatibilidad.

El tercer proyecto que no me gustaría dejar sin mencionar es LISP, una empresa fascinante de una naturaleza completamente diferente. Con algunos principios muy básicos en su base, ha demostrado una estabilidad notable. Además de eso, LISP ha sido el portador de un número considerable de, en cierto sentido, nuestras aplicaciones informáticas más sofisticadas. LISP se ha descrito en broma como "la forma más inteligente de hacer un mal uso de una computadora". Creo que esa descripción es un gran cumplido porque transmite todo el sabor de la liberación: ha ayudado a algunos de nuestros congéneres más dotados a pensar en pensamientos que antes eran imposibles.

El cuarto proyecto a mencionar es ALGOL 60. Si bien hasta el día de hoy los programadores de FORTRAN todavía tienden a entender su lenguaje de programación en términos de la implementación específica con la que están trabajando —de ahí la prevalencia de volcados octales y hexadecimales—, mientras que la definición de LISP sigue siendo una mezcla curiosa de lo que significa el lenguaje y cómo funciona el mecanismo, el famoso Informe sobre el lenguaje algorítmico ALGOL 60 es el fruto de un esfuerzo genuino por llevar la abstracción un paso vital más allá y definir un lenguaje de programación en una implementación en forma independiente

Abstracción del lenguaje por su sintaxis

Se podría argumentar que a este respecto sus autores han tenido tanto éxito que han creado serias dudas sobre si podría implementarse en absoluto. El informe demostró gloriosamente el poder del método formal BNF, ahora bastante conocido como Backus-Naur-Form, y el poder del inglés cuidadosamente redactado, al menos cuando lo usa alguien tan brillante como Peter Naur. 

Creo que es justo decir que muy pocos documentos tan breves como este han tenido una influencia igualmente profunda en la comunidad informática. La facilidad con la que en años posteriores se han utilizado los nombres ALGOL y ALGOL-like, como marca comercial desprotegida, para prestar algo de su gloria a una serie de proyectos más jóvenes, a veces apenas relacionados, es un elogio algo sorprendente a su prestigio.

La fuerza de BNF como dispositivo de definición es responsable de lo que considero una de las debilidades del lenguaje: una sintaxis demasiado elaborada y no demasiado sistemática podría ahora apiñarse en los confines de muy pocas páginas. Con un dispositivo tan poderoso como BNF, el Informe sobre el lenguaje algorítmico ALGOL 60 debería haber sido mucho más breve. Además de eso, tengo muchas dudas sobre el mecanismo de parámetros de ALGOL 60: le permite al programador tanta libertad combinatoria, que su uso seguro requiere una fuerte disciplina por parte del programador. Además de caro de implementar, parece peligroso de usar.

Finalmente, aunque el tema no es agradable, debo mencionar PL / 1, un lenguaje de programación para el cual la documentación definitoria es de un tamaño y complejidad aterradora. Usar PL / 1 debe ser como volar un avión con 7000 botones, interruptores y manijas para manipular en la cabina. No veo absolutamente cómo podemos mantener nuestros programas en crecimiento firmemente dentro de nuestro control intelectual cuando, por su puro barroquismo, el lenguaje de programación —¡nuestra herramienta básica, claro! - ya escapa a nuestro control intelectual. Y si tengo que describir la influencia que PL / 1 puede tener en sus usuarios, la metáfora más cercana que me viene a la mente es la de una droga. 

Recuerdo de un simposio sobre lenguaje de programación de nivel superior una conferencia que dio en defensa de PL / 1 un hombre que se describió a sí mismo como uno de sus devotos usuarios. Pero dentro de una conferencia de una hora en alabanza de PL / 1. se las arregló para solicitar la adición de unas cincuenta nuevas "características", suponiendo poco que la principal fuente de sus problemas podría muy bien ser que ya contenía demasiadas "características". El hablante mostraba todos los deprimentes síntomas de la adicción, reducido como estaba al estado de estancamiento mental en el que solo podía pedir más, más, más ... Cuando se ha llamado a FORTRAN un trastorno infantil, PL / 1 completo, con sus características de crecimiento de un tumor peligroso, podrían convertirse en una enfermedad fatal.

Demasiado para el pasado. Pero no tiene sentido cometer errores a menos que a partir de entonces podamos aprender de ellos. De hecho, creo que hemos aprendido tanto, que dentro de unos años la programación puede ser una actividad muy diferente de lo que ha sido hasta ahora, tan diferente que es mejor que nos preparemos para el impacto

El futuro de la programación

Déjeme esbozarle uno de los futuros posibles. A primera vista, esta visión de la programación quizás ya en un futuro próximo puede parecerle absolutamente fantástica. Permítanme, por tanto, añadir también las consideraciones que podrían llevarnos a la conclusión de que esta visión podría ser una posibilidad muy real.

La visión es que, mucho antes de que la década de los setenta se complete, seremos capaces de diseñar e implementar el tipo de sistemas que ahora están agotando nuestra capacidad de programación, a expensas de solo un pequeño porcentaje en años-hombre de lo que cuestan. nosotros ahora y que además de eso, estos sistemas estarán prácticamente libres de errores

Estas dos mejoras van de la mano. En este último aspecto, el software parece ser diferente de muchos otros productos, donde, por regla general, una calidad superior implica un precio más elevado. Aquellos que quieran un software realmente confiable descubrirán que deben encontrar la manera de evitar la mayoría de los errores y, como resultado, el proceso de programación será más barato. Si desea programadores más efectivos, descubrirá que no deben perder el tiempo depurando, para empezar, no deben introducir los errores.

Un cambio tan drástico en tan poco tiempo sería una revolución y para todas las personas que basan sus expectativas de futuro en una suave extrapolación del pasado reciente, apelando a algunas leyes no escritas de inercia social y cultural, la posibilidad de que este se producirá un cambio drástico que debe parecer insignificante. ¡Pero todos sabemos que a veces se producen revoluciones! ¿Y cuáles son las posibilidades de este?

Parece haber tres condiciones principales que deben cumplirse. El mundo en general debe reconocer la necesidad del cambio; en segundo lugar, la necesidad económica debe ser suficientemente fuerte; y, en tercer lugar, el cambio debe ser técnicamente factible. Permítanme discutir estas tres condiciones en el orden anterior.

Con respecto al reconocimiento de la necesidad de una mayor confiabilidad del software, ya no espero ningún desacuerdo. Hace solo unos años esto era diferente: hablar de una crisis de software era una blasfemia. El punto de inflexión fue la Conferencia sobre Ingeniería de Software en Garmisch, octubre de 1968, una conferencia que causó sensación al producirse la primera admisión abierta de la crisis del software

Y a estas alturas se reconoce generalmente que el diseño de cualquier gran sistema sofisticado va a ser un trabajo muy difícil y cada vez que uno se encuentra con personas responsables de tales empresas, los encuentra muy preocupados por el tema de la confiabilidad y con razón. En resumen, nuestra primera condición parece cumplida.

Ahora por la necesidad económica. Hoy en día, a menudo se encuentra la opinión de que en los años sesenta la programación ha sido una profesión sobre pagada y que en los próximos años se espera que bajen los sueldos de los programadores. 

Por lo general, esta opinión se expresa en relación con la recesión, pero podría ser un síntoma de algo diferente y bastante saludable, a saber. que quizás los programadores de la última década no hayan hecho un trabajo tan bueno como deberían haberlo hecho

La sociedad está insatisfecha con el desempeño de los programadores y de sus productos. Pero hay otro factor de mucho mayor peso. En la situación actual es bastante habitual que para un sistema específico, el precio a pagar por el desarrollo del software sea del mismo orden de magnitud que el precio del hardware necesario y la sociedad más o menos lo acepta. 

Pero los fabricantes de hardware nos dicen que en la próxima década se puede esperar que los precios del hardware caigan en un factor de diez. 

Si el desarrollo de software continuara siendo el mismo proceso torpe y costoso que ahora, las cosas se desequilibrarían por completo. No se puede esperar que la sociedad acepte esto y, por lo tanto, nosotros debemos aprender a programar un orden de magnitud de manera más eficaz. Para decirlo de otra manera: mientras las máquinas sean el elemento más importante del presupuesto, la profesión de programación podría salirse con la suya con sus técnicas torpes, pero ese paraguas se doblará rápidamente. En resumen, también nuestra segunda condición parece cumplida.

Y ahora la tercera condición: ¿es técnicamente factible? Creo que podría y le daré seis argumentos en apoyo de esa opinión.
  • Un estudio de la estructura de los programas había revelado que los programas —incluso programas alternativos para la misma tarea y con el mismo contenido matemático— pueden diferir enormemente en su manejabilidad intelectual. 
  • Se han descubierto una serie de reglas, cuya violación dañará gravemente o destruirá totalmente la capacidad de gestión intelectual del programa. 
Estas reglas son de dos tipos. Los del primer tipo se imponen fácilmente de forma mecánica, a saber. mediante un lenguaje de programación adecuadamente elegido. Algunos ejemplos son la exclusión de sentencias goto y de procedimientos con más de un parámetro de salida. Para los del segundo tipo, al menos —pero eso puede deberse a una falta de competencia de mi parte— no veo forma de imponerlos mecánicamente, ya que parece necesitar algún tipo de demostrador automático de teoremas para el que no tengo pruebas de existencia. Por lo tanto, por el momento y quizás para siempre, las reglas del segundo tipo se presentan como elementos de disciplina requeridos por el programador

Algunas de las reglas que tengo en mente son tan claras que se pueden enseñar y que nunca es necesario discutir si un programa determinado las viola o no. Algunos ejemplos son los requisitos de que ningún bucle debe escribirse sin proporcionar una prueba de terminación o sin indicar la relación cuya invariancia no será destruida por la ejecución de la declaración repetible.

Sugiero ahora que nos limitemos al diseño e implementación de programas intelectualmente manejables. Si alguien teme que esta restricción sea tan severa que no podamos vivir con ella, puedo tranquilizarlo: la clase de programas intelectualmente manejables es todavía lo suficientemente rica como para contener muchos programas muy realistas para cualquier problema capaz de solución algorítmica

No hay que olvidar que no es nuestro negocio el hacer programas, nuestro negocio es diseñar clases de cálculos que se mostrarán con un comportamiento deseado. La sugerencia de limitarnos a programas intelectualmente manejables es la base de los dos primeros de mis seis argumentos anunciados.

El argumento uno es que, dado que el programador solo necesita considerar programas manejables intelectualmente, las alternativas entre las que está eligiendo son muchas, mucho más fáciles de afrontar.

El segundo argumento es que, tan pronto como hemos decidido restringirnos al subconjunto de los programas intelectualmente manejables, hemos logrado, de una vez por todas, una reducción drástica del espacio de soluciones a considerar. Y este argumento es distinto del argumento uno.

El tercer argumento se basa en el enfoque constructivo del problema de la corrección del programa. Hoy en día una técnica habitual es hacer un programa y luego probarlo. Pero: la prueba del programa puede ser una forma muy efectiva de mostrar la presencia de errores, pero es desesperadamente inadecuada para mostrar su ausencia. La única forma eficaz de elevar significativamente el nivel de confianza de un programa es dar una prueba convincente de su corrección

Pero no se debe hacer primero el programa y luego probar que es correcto, porque entonces el requisito de proporcionar la prueba solo aumentaría la carga del pobre programador. Al contrario: el programador debe dejar que la prueba de corrección y el programa crezcan de la mano. El argumento tres se basa esencialmente en la siguiente observación
Si uno se pregunta primero cuál sería la estructura de una prueba convincente y, habiendo encontrado esto, luego construye un programa que satisface los requisitos de esta prueba, entonces estas preocupaciones de corrección resultan ser una guía heurística muy efectiva. Por definición, este enfoque solo es aplicable cuando nos limitamos a programas intelectualmente manejables, pero nos proporciona medios efectivos para encontrar uno satisfactorio entre ellos.
El argumento cuatro tiene que ver con la forma en que la cantidad de esfuerzo intelectual necesario para diseñar un programa depende de la duración del programa. Se ha sugerido que existe algún tipo de ley de la naturaleza que nos dice que la cantidad de esfuerzo intelectual necesario aumenta con el cuadrado de la duración del programa. Pero, gracias a Dios, nadie ha podido probar esta ley. Y esto se debe a que no tiene por qué ser verdad. 
Todos sabemos que la única herramienta mental por medio de la cual un razonamiento muy finito puede cubrir una miríada de casos se llama "abstracción"; como resultado, la explotación efectiva de sus poderes de abstracción debe considerarse como una de las actividades más vitales de un programador competente. 
A este respecto, podría ser conveniente señalar que el propósito de abstraer no es para ser vago, pero para crear un nuevo nivel semántico en el que uno puede ser absolutamente preciso. Por supuesto, he tratado de encontrar una causa fundamental que impida que nuestros mecanismos de abstracción sean suficientemente efectivos. 

Pero no importa cuánto lo intenté, no encontré tal causa. Como resultado, tiendo a suponer —hasta ahora no refutado por la experiencia— de que mediante la aplicación adecuada de nuestros poderes de abstracción, el esfuerzo intelectual necesario para concebir o comprender un programa no necesita crecer más que proporcional a la longitud del programa

Pero un subproducto de estas investigaciones puede tener una importancia práctica mucho mayor y es, de hecho, la base de mi cuarto argumento. El subproducto fue la identificación de una serie de patrones de abstracción que juegan un papel vital en todo el proceso de composición de programas. Ahora se sabe lo suficiente sobre estos patrones de abstracción como para dedicarle una conferencia sobre cada uno de ellos. 

Lo que la familiaridad y el conocimiento consciente de estos patrones de abstracción implican se me ocurrió cuando me di cuenta de que, si hubieran sido de conocimiento común hace quince años, el paso de BNF a compiladores dirigidos por sintaxis, por ejemplo, podría haber tomado unos minutos en lugar de unos años. 

Por tanto, presento nuestro conocimiento reciente de los patrones de abstracción vitales como el cuarto argumento.

Ahora para el quinto argumento. Tiene que ver con la influencia de la herramienta que estamos tratando de usar sobre nuestros propios hábitos de pensamiento. Observo una tradición cultural, que con toda probabilidad tiene sus raíces en el renacimiento, para ignorar esta influencia, para considerar a la mente humana como el amo supremo y autónomo de sus artefactos. 

El mindset de la programación

Pero si empiezo a analizar los hábitos de pensamiento de mí mismo y de mis semejantes, llego, me guste o no, a una conclusión completamente diferente, a saber. que las herramientas que estamos tratando de usar y el lenguaje o la notación que estamos usando para expresar o registrar nuestros pensamientos, son los factores principales que determinan lo que podemos pensar o expresar

El análisis de la influencia que los lenguajes de programación tienen en los hábitos de pensamiento de sus usuarios y el reconocimiento de que, a estas alturas, la capacidad intelectual es, con mucho, nuestro recurso más escaso, juntos nos brindan una nueva colección de criterios para comparar los méritos relativos de varios lenguajes de programación. 
El programador competente es plenamente consciente del tamaño estrictamente limitado de su propio cráneo; por eso se acerca a la tarea de programar con total humildad y entre otras cosas evita trucos ingeniosos como la peste. 
En el caso de un conocido lenguaje de programación conversacional me han dicho desde varios lados que, en cuanto una comunidad de programación está equipada con un terminal para ello, ocurre un fenómeno específico que incluso tiene un nombre bien establecido: se llama “ las frases ingeniosas ”. 

Toma una de dos formas diferentes: un programador coloca un programa de una línea en el escritorio de otro y dice con orgullo lo que hace y agrega la pregunta "¿Puedes codificar esto con menos símbolos?" —¡Como si esto tuviera alguna relevancia conceptual! - o simplemente pregunta "¡Adivina qué hace!".
De esta observación debemos concluir que este lenguaje como herramienta es una invitación abierta a trucos ingeniosos; y aunque exactamente esta puede ser la explicación de parte de su atractivo, a saber. para aquellos a quienes les gusta mostrar lo inteligentes que son, lo siento, pero debo considerar esto como una de las cosas más condenatorias que se pueden decir sobre un lenguaje de programación. 
Otra lección que deberíamos haber aprendido del pasado reciente es que el desarrollo de lenguajes de programación "más ricos" o "más potentes" fue un error en el sentido de que estas monstruosidades barrocas, estos conglomerados de idiosincrasias, son realmente inmanejables, tanto mecánica como mentalmente. 

Veo un gran futuro para los lenguajes de programación muy sistemáticos y muy modestos. Cuando digo "modesto", me refiero a que, por ejemplo, no solo la "cláusula for" de ALGOL 60, pero incluso el "bucle DO" de FORTRAN puede resultar descartado por ser demasiado barroco. 

He realizado un pequeño experimento de programación con voluntarios muy experimentados, pero apareció algo bastante inesperado y no intencionado. 
Ninguno de mis voluntarios encontró la solución más obvia y elegante. Tras un análisis más detallado, esto resultó tener una fuente común: su noción de repetición estaba tan estrechamente conectada con la idea de una variable controlada asociada que debía intensificarse, que estaban mentalmente bloqueados para no ver lo obvio. Sus soluciones eran menos eficientes, innecesariamente difíciles de entender, y les llevó mucho tiempo encontrarlas. 
Fue una experiencia reveladora, pero también impactante para mí. Finalmente, en un aspecto, uno espera que los lenguajes de programación del mañana difieran mucho de los que estamos acostumbrados ahora: en mucha mayor medida que hasta ahora deberían invitarnos a reflejar en la estructura de lo que escribimos todas las abstracciones necesarias para afrontar conceptualmente la complejidad de lo que estamos diseñando. Hasta aquí la mayor adecuación de nuestras herramientas futuras, que fue la base del quinto argumento.

Como un aparte me gustaría insertar una advertencia a quienes identifican la dificultad de la tarea de programación con la lucha contra las deficiencias de nuestras herramientas actuales, porque podrían concluir que, una vez que nuestras herramientas sean mucho más adecuadas, la programación ya no será suficiente, será un problema. 

La programación seguirá siendo muy difícil, porque una vez que nos hayamos liberado de la incomodidad circunstancial, nos encontraremos libres para abordar los problemas que ahora están mucho más allá de nuestra capacidad de programación.

Puedes discutir con mi sexto argumento, porque no es tan fácil recolectar evidencia experimental para su apoyo, un hecho que no me impedirá creer en su validez. Hasta ahora no he mencionado la palabra “jerarquía”, pero creo que es justo decir que este es un concepto clave para todos los sistemas que incorporan una solución bien factorizada. Incluso podría dar un paso más y convertirlo en un artículo de fe, a saber. que los únicos problemas que realmente podemos resolver de manera satisfactoria son aquellos que finalmente admiten una solución bien factorizada

A primera vista, esta visión de las limitaciones humanas puede parecerles una visión bastante deprimente de nuestra situación, pero yo no lo siento así, ¡al contrario! La mejor forma de aprender a vivir con nuestras limitaciones es conocerlas. En el momento en que seamos lo suficientemente modestos como para probar solo soluciones factorizadas, Debido a que los otros esfuerzos escapan a nuestro control intelectual, haremos todo lo posible para evitar que todas esas interfaces perjudiquen nuestra capacidad para factorizar el sistema de una manera útil. 

Y no puedo dejar de esperar que esto conduzca repetidamente al descubrimiento de que, después de todo, se puede factorizar un problema inicialmente imposible de resolver. Cualquiera que haya visto cómo la mayoría de los problemas de la fase de compilación llamada "generación de código" se pueden rastrear hasta las propiedades divertidas del código de pedido, sabrá un ejemplo simple del tipo de cosas que tengo en mente. La aplicabilidad más amplia de soluciones bien factorizadas es mi sexto y último argumento a favor de la viabilidad técnica de la revolución que podría tener lugar en la década actual

#AskYourSelf

En principio, dejo que usted decida por sí mismo cuánto peso le va a dar a mis consideraciones, sabiendo muy bien que no puedo obligar a nadie más a compartir mis creencias. Como cada revolución seria, provocará una oposición violenta y uno puede preguntarse dónde esperar las fuerzas conservadoras que intentan contrarrestar tal desarrollo. 

No los espero principalmente en las grandes empresas, ni siquiera en el negocio de las computadoras; Los espero más bien en las instituciones educativas que brindan capacitación hoy en día y en esos grupos conservadores de usuarios de computadoras que piensan que sus viejos programas son tan importantes que no creen que valga la pena reescribirlos y mejorarlos

A este respecto, es triste observar que en muchos campus universitarios la elección de la instalación informática central ha estado determinada con demasiada frecuencia por las demandas de unas pocas aplicaciones establecidas pero costosas, sin tener en cuenta la cuestión de cuántos miles de "pequeños usuarios" que estén dispuestos a escribir sus propios programas iban a sufrir esta elección. 

Con demasiada frecuencia, por ejemplo, la física de altas energías parece haber chantajeado a la comunidad científica con el precio del equipo experimental restante. La respuesta más fácil, por supuesto, es una negación rotunda de la viabilidad técnica, pero me temo que se necesitan argumentos bastante sólidos para ello. 
Desafortunadamente, no se puede obtener ninguna tranquilidad con la observación de que el techo intelectual del programador promedio de hoy impedirá que se produzca la revolución:
También puede haber impedimentos políticos. Incluso si sabemos cómo educar al programador profesional del mañana, no es seguro que la sociedad en la que vivimos nos lo permita
El primer efecto de enseñar una metodología —más que difundir conocimientos— es el de potenciar las capacidades de los ya capaces, magnificando así la diferencia de inteligencia. En una sociedad en la que el sistema educativo se utiliza como instrumento para el establecimiento de una cultura homogeneizada, en la que se impide que la crema llegue a lo más alto, la formación de programadores competentes podría resultar políticamente imparable.

El movimiento mental TopDown

Déjame concluir. Las computadoras automáticas han estado con nosotros durante un cuarto de siglo. Han tenido un gran impacto en nuestra sociedad en su capacidad de herramientas, pero en esa capacidad su influencia no será más que una onda en la superficie de nuestra cultura, en comparación con la influencia mucho más profunda que tendrán en su capacidad de desafío intelectual sin precedente en la historia cultural de la humanidad. 
Los sistemas jerárquicos parecen tener la propiedad de que "algo" considerado como "una entidad indivisa" en el primer nivel, se considera un objeto compuesto en el siguiente nivel inferior de mayor detalle; como resultado, el grano natural de espacio o tiempo que es aplicable en cada nivel disminuye en un orden de magnitud cuando cambiamos nuestra atención de un nivel al siguiente nivel inferior. 
Entendemos las paredes en términos de ladrillos, los ladrillos en términos de cristales, cristales en términos de moléculas, etc. Como resultado, el número de niveles que se pueden distinguir de manera significativa en un sistema jerárquico es proporcional al logaritmo de la relación entre el grano más grande y el más pequeño, y por lo tanto, a menos que esta relación sea muy grande, no podemos esperar muchos niveles. 
En programación de computadoras, nuestro bloque de construcción básico tiene una granularidad de tiempo asociado de menos de un microsegundo, pero nuestro programa puede tomar horas de tiempo de cálculo. No conozco ninguna otra tecnología que cubra una proporción de 10 elevado a la 10 o más: el ordenador, en virtud de su fantástica velocidad, parece ser el primero en proporcionarnos un entorno en el que los artefactos altamente jerárquicos son posibles y necesarios. 
Este desafío, a saber. el enfrentamiento con la tarea de programación, es tan singular que esta novedosa experiencia puede enseñarnos mucho sobre nosotros mismos. Debería profundizar nuestra comprensión de los procesos de diseño y creación, debería darnos un mejor control sobre la tarea de organizar nuestros pensamientos. Si no fuera así, ¡a mi gusto no deberíamos merecer la computadora en absoluto!
Ya nos ha enseñado algunas lecciones y la que he elegido enfatizar en esta charla es la siguiente.
Haremos un trabajo de programación mucho mejor, siempre que abordemos la tarea con una plena apreciación de su tremenda dificultad, siempre que nos ciñamos a lenguajes de programación modestos y elegantes, siempre que respetemos las limitaciones intrínsecas de la mente humana y abordemos la tarea. como programadores muy humildes.

DSN_XP y la genialidad de John Guttag

 La abstracción como tipo de dato


DSN_XP entre en contacto con John Guttag cuando comenzamos a analizar tanto a la ingeniería que da soporte al proceso de abstracción en el desarrollo de software y a la ingeniería de software / ciencias de la computación en el desarrollo e implementación de dicho software.
Para la arquitectura, la ingeniería es la base fundamental para el desarrollo de las perspectivas de diseño que necesariamente requieren de una estructura modular. 
El concepto de tipo de dato abstracto (TDA, Abstract Data Type), fue propuesto por primera vez hacia 1974 por John Guttag y otros, pero no fue hasta 1975 que por primera vez Barbara Liskov lo propuso para el lenguaje CLU.

El diseño de especificaciones de tipos de datos

Documento de conferencia 
Enero de 1976
John V. Guttag *, Ellis Horowitz * y David R. Musser **

Resumen

Este artículo trata sobre el diseño de tipos de datos en la creación de un sistema de software. El punto principal es explorar un significado para especificar un tipo de dato que es independiente de su eventual implementación. El estilo particular de especificación denominado axiomas algebraicos, se exhibe axiomatizando muchos tipos de datos de uso común.  Como tal, estos ejemplos revelan mucho sobre las complejidades de la especificación del tipo de datos a través de axiomas algebraicos y, además, un estándar con el que se pueden comparar formas alternativas. Más allá del uso de esta técnica de especificación para probar la exactitud de las implementaciones y en la ejecución interpretativa de un diseño de sistema grande antes de que comience la implementación real.

Términos de indexación: 

Tipo de datos, especificación, axiomas algebraicos, software diseño, programación recursiva, corrección del programa.

Introducción

El proceso de creación de un sistema de software es generalmente considerado como un proceso de cuatro etapas: requisitos, diseño, codificación y pruebas. Para algunas de estas etapas, las herramientas, las técnicas o ambas han sido desarrolladas para mejorar significativamente el proceso. 

Recientemente ha habido una creciente preocupación por el desarrollo de ayudas para la etapa de diseño. El diseño es esencialmente un proceso creativo, sintético y una herramienta completamente automatizada es muy poco probable que lo sea. 
Lo que se sugiere es seguir una "metodología" o un estilo de trabajo, que se supone produce diseños mejorados.
El diseño de arriba hacia abajo es un proceso mediante el cual se transforma una tarea en un programa ejecutable. Este proceso en su forma más pura, requiere refinar cuidadosamente, paso a paso, los requisitos funcionales de un sistema en programas operativos, más las directrices sobre la elección de declaraciones apropiadas y el aplazamiento de decisiones de diseño, se puede encontrar en [Dahl 72].
El propósito de este artículo es explorar una estrategia complementaria de diseño, el diseño de tipos de datos. 
Un sistema completo de software puede contener una variedad de tipos de datos (listas, pilas, árboles, matrices, etc.) y una variedad de operaciones. 
Un procedimiento de diseño útil es tratar aquellas operaciones que actúan principalmente sobre un solo tipo de datos como parte de una unidad y considerar la semántica de estas operaciones como la definición del tipo. 
Esta idea estaba implícita en el lenguaje de programación SIMULA 67 [Dahl 70], en el que la designación sintáctica class denota una colección de tales operaciones. Sin embargo, el concepto de clase aplica este principio a nivel de lenguaje de programación en lugar de en tiempo de diseño

Cada operación de una clase es directamente un programa ejecutable. También es útil considerar una colección de operaciones en tiempo de diseño y luego el proceso de diseño (de tipos de datos) consiste en especificar esas operaciones a cada vez mayor nivel de detalle hasta que se logre una implementación ejecutable, la idea que deseamos explorar aquí es cómo crear una especificación inicial de un tipo de dato.

Una especificación de un tipo de dato (o tipo de dato abstracto) es una definición formal independiente de la representación de cada operación de un tipo de datos. Por lo tanto, el diseño completo de un solo tipo de datos procede dando primero su especificación, seguido de una (eficiente) implementación que esté de acuerdo con la especificación. Esta separación del diseño de tipos de datos en dos fases distintas es muy útil desde un punto de vista organizacional. Cualquier proceso que necesite hacer uso del tipo de datos puede hacerlo examinando solo la especificación.
No es necesario esperar hasta que el tipo esté completamente implementado ni necesario para comprender completamente la implementación.
Hay dos preocupaciones principales al diseñar una técnica para especificación de tipo datos:
  • La primera es idear una notación que permita una definición rigurosa de operaciones pero sigue siendo una representación independiente y 
  • la segunda es aprender a usar esa notación. Ahí hay muchos criterios que se pueden usar para medir el valor de una notación de especificación, pero las dos principales son las siguientes: 
  1. ¿pueden las especificaciones ser construidas sin dificultad indebida? y,
  2.  ¿es la especificación resultante fácil de comprender?. 
Al igual que con la programación, hay potencialmente un gran número de formas de especificar una operación. Una buena especificación de tipo de dato debe proporcionar la información suficiente para definir el tipo, pero no tanto que la elección de sus implementaciones que son basadas en la especificación es limitada. Por tanto, decimos que una especificación de tipo de dato es una abstracción de un concepto cuya implementación final es solo una instancia.

En este artículo, nuestra intención es explorar una técnica de especificación particular [Guttag 75], [Zilles 75], [Goguen 75], al exhibir especificaciones para una serie de tipos de datos de uso común.

Los que hemos elegido son típicos de los que son discutidos en un curso sobre estructuras de datos, ver [Horowitz 76]. Por proporcionar estos ejemplos esperamos convencer al lector de que el estilo de especificación que discutimos aquí es especialmente apropiado para diseñar tipos de datos y que cumpla con los dos criterios previamente fijados. En segundo lugar, esperamos que estas especificaciones de ejemplo proporcionen un estándar por el cual se pueden comparar con otros métodos. 

Nosotros no pretendemos haber proporcionado especificaciones definitivas de los tipos de datos de ejemplo. Tanto nuestra elección de operaciones como la semántica que asociamos con algunas de las operaciones son algo arbitrarias. En el último apartado indicamos cómo se pueden realizar estas especificaciones más utilizadas para probar la exactitud de las implementaciones y para  probar, en tiempo de diseño, grandes sistemas de software. 

Sin embargo, desde que estos temas son bastante largos para discutir, limitamos nuestra presentación aquí para una discusión informal de lectura y escritura del tipo de especificaciones de datos. Los temas restantes solo se insinuarán aquí y que son tratados en [Guttag 76b].

Muchas otras personas han estado trabajando en estas áreas relacionadas y nos hemos beneficiado de sus ideas. Una bibliografía útil de este trabajo se da en [Liskov 75]. Algunas de las particulares axiomatizaciones ya han aparecido en la literatura, en particular pilas, colas y conjuntos, consulte [Liskov 75], [Guttag 76a], [Goguen 751 [Standish 73] y [Spitzen 75].

Las especificaciones

¿Cómo se puede describir un tipo de datos sin restringir indebidamente su eventual forma implementada? 
Un método es definir el objeto utilizando lenguaje natural y la notación matemática. 
Por ejemplo, una pila puede ser definida como una secuencia de objetos (al, ..., an) n>=0, donde solo se permiten inserciones o eliminaciones a su extremo derecho. 
Este tipo de definición no es satisfactorio desde el punto de vista informático en donde es preferible definir constructivamente un tipo de dato por definir las operaciones que crean, acumulan y destruyen instancias del tipo. 
Dado que los diseñadores de software generalmente saben cómo programar, el uso de un lenguaje de programación para la especificación es especialmente deseable.

Las características que elegimos permiten solo lo siguiente:
  1. variables libres
  2. if-then-else expresiones
  3. Expresiones booleanas
  4. recursividad
Además restringimos el uso de procedimientos para aquellos que son de valor único y no tienen efectos secundarios. Tenga en cuenta que muchas características normalmente que se presumen que están presentes en los lenguajes de programación convencionales (como la asignación a variables, una declaración de iteración) no están permitidas en este formalismo. 

Este enfoque puede parecer tan arbitrario como para eliminar la posibilidad de lograr las metas previamente establecidas, pero en realidad tiene varios puntos fuertes por los que se lo recomienda. 
  • Primero, el conjunto restringido produce una representación independiente para suministrar una especificación. 
  • Segundo, las especificaciones resultantes pueden expresar muy claramente los conceptos si el lector se siente cómodo con la lectura de programas recursivos. (Aunque muchos programadores no están tan acostumbrados, una lectura fiel de este documento servirá como tutorial sobre este tema).
  • En tercer lugar, la separación de valores y efectos secundarios aporta claridad y simplifica una especificación. Aunque requerir esta separación puede ser demasiado restrictivo para una implementación, los criterios de eficiencia pueden relajarse en la etapa de especificación
  • Cuarto, las características anteriores pueden ser fácilmente axiomatizadas, que es un primer paso necesario para realizar con éxito pruebas de implementación, ver [Guttag 76b].
Comencemos con un ejemplo muy simple de una pila que está dada en la Figura 2.1. 

Las operaciones que están disponibles para manipular una pila son: 
  1. NEWSTACK, que produce una instancia de una pila vacía; 
  2. PUSH, que inserta un nuevo elemento en la pila y devuelve la pila resultante; 
  3. POP, que elimina el elemento superior y devuelve la pila resultante; 
  4. TOP, que devuelve el elemento superior de la pila; 
  5. ISNEWSTACK, que prueba si una pila está vacía. 

Para cada operación, los tipos de entradas y salidas son enumerados en la sentencia de la declaración. Observe que todas las operaciones son verdaderas funciones que devuelven un valor único y no permiten efectos secundarios. Si las operaciones de pila se implementan mediante procedimientos con efectos secundarios, su efecto se puede especificar fácilmente en términos de las operaciones que se han dado. La extensión del formalismo de esta manera se analiza en la sección 3.

En este punto, introduzcamos las convenciones de notación que se utilizan a lo largo de este documento. 
  • Todos los nombres de las operaciones están escritos en mayúsculas.
  • Los nombres de los tipos comienzan con una letra mayúscula, por ejemplo, Pila. Minúscula
  • Los símbolos se consideran variables libres, como s e i en la figura 2.1. que se toman como de tipo Pila y artículo respectivamente
  • Los nombres de tipo se pueden modificar enumerando "parámetros" dentro de corchetes. Estos parámetros pueden ser nombres de tipos o variables libres cuyo rango es un tipo, por ejemplo , el elemento es una variable e indica que el tipo Pila puede aplicarse a cualquier otro tipo de datos . Las ecuaciones dentro de for all y final son los axiomas que describen la semántica de las operaciones.
Al principio, estos axiomas pueden resultar difíciles de comprender. Una ayuda consiste en interpretar los axiomas como la definición de un conjunto de funciones recursivas.

La pila vacía está representada por la función con entrada sin argumentos NEWSTACK. Luego preguntando por el elemento superior de NEWSTACK se considera una condición excepcional que no dan como resultado un elemento, por lo tanto, lo llamamos INDEFINIDO. La única otra pila que podemos tener debe ser de la forma PUSH (s, i) donde s es cualquier pila e i es el elemento insertado más recientemente. 

Luego por la línea 12 el último elemento insertado es el primero devuelto. Note que no debemos preocuparnos por expresiones de la forma TOP (POP (s)), ya que los axiomas 9 y 10 nos dan reglas para expresar cualquier valor de tipo Pila en términos de NEWSTACK y PUSH.

Desafortunadamente, el ejemplo de la pila es demasiado simple al respecto para ilustrar adecuadamente las complejidades de la especificación de tipo de datos. Un ejemplo algo más rico es el tipo de datos Oueue o lista de primero en entrar, primero en salir. Su especificación se da en la Figura 2.2. Ahí son seis operaciones, cuatro de las cuales producen colas, una devuelve un elemento y otra tiene valor booleano. Los axiomas de la cola son más complejos que los axiomas de una pila en el sentido de que hacen uso de la construcción if-then-else y recursión

Una forma fácil de entender estos axiomas es concebir el conjunto de todas las colas como representado por el conjunto de cadenas que consta de

NEWQ o ADDQ (... ADDQ (ADDQ (NEWQ, it), i2), ..., in), n> l.

El artículo i1 está en la parte delantera y en la parte trasera. Entonces los axiomas pueden ser concebidos concretamente como reglas que muestran cómo cada operación actúa sobre cualquier cadena de este tipo. Por ejemplo, tomando el FRONTQ de la cola vacía es INDEFINIDA. De lo contrario, FRONTQ se aplica a una cola cuyo elemento insertado más recientemente es i, y q representa el resto de la cola. Si q está vacío, entonces i es el resultado correcto, de lo contrario, FRONTQ se aplica recursivamente a q. Una situación similar se mantiene para la operación DELETEQ. Tenga en cuenta que ninguna de las formas de representación de la cola, por ejemplo, como listas enlazadas o en una matriz, es implícito ni excluido por esta definición.


Consideremos una tercera estructura familiar, el árbol binario (Btree) y examinar con más detalle la virtud de considerar todos los valores de la estructura de datos representada por cadenas. Su especificación se da en la Figura 2.3.


Las operaciones incluidas son EMPTYTREE que crea el árbol vacío, MAKE, que une dos árboles con una nueva raíz y operaciones que acceden a los datos en un nodo, el subárbol izquierdo y el subárbol derecho de un nodo y busque un elemento de datos determinado. Tres operaciones que, naturalmente, podríamos preguntarnos si incluir los métodos de recorrido son habituales, preorder, inorder y postorder, que coloque los elementos contenidos en el árbol en una cola [Horowitz 76].

Quizás la razón más poderosa para incluirlos es el hecho de que están expresados ​​de manera tan sucinta por nuestra notación recursiva, por ejemplo, 

INORD(Btree) -* Queue y 
INORD(EMPTYTREE) = NEWQ 
INORD(MAKE(I,d,r)) = APPENDQ(ADDQ(INORD(I),d),INORD(r)) 

Para cada I,r perteneciente a Btree y d perteneciente a item. La elección de qué operaciones incluir en una especificación es arbitraria. Hemos omitido esto operación porque hace un uso significativo de las operaciones de otro tipo de datos: Cola. De todos modos, esto nos da la oportunidad de experimentar con la representación de cadenas. 

Hagamos un ejemplo que comienza con el árbol binario.

T = MAKE(MAKE(EMPTYTREE,B,EMPTYTREE),A,
MAKE(EMPTYTREE,C,EMPTYTREE))

y aplica los axiomas a INORD (T) para obtener 

INORD (T) = APPENDQ (ADDQ (INORD (MAKE (EMPTYTREE, B, EMPTYTREE)), A) INORD (MAKE (EMPTYTREE, C, EMPTYTREE))) 

que por la definición de INORD se convierte en 

APENI ~ Q (ADDQ (APENDQ (ADDQ (NEWQ, B), NEWQ), A), APENDQ (ADDQ (NEWQ, C), NEWQ))

y ahora usando los axiomas para APPENDQ obtenemos

APENDQ (ADDQ (ADDQ (NEWQ, B), A), ADDQ (NEWQ, C)) y nuevamente aplicando APPENDQ 

obtenemos 

ADDQ(APPENDQ(ADDQ(ADDQ(NEWQ,B),A),NEWQ),C) lo cual nos lleva al resultado final  

ADDQ(ADDQ(ADDQ(NEWQ,B),A),C)

En este punto, el lector ha visto tres ejemplos y estamos en una mejor posición para argumentar las virtudes de la notación de especificación.

El número de axiomas está directamente relacionado con el número de operaciones del tipo que se describe. La restricción de expresar axiomas usando solo el if-then-else y la recursividad no ha causado ninguna contorsión.

Esto no debería sorprender a los programadores LISP que han encontrado estas suficientes características muchos años de programación. Una crítica que uno generalmente se encuentra es que la recursividad obliga a uno a un código ineficaz, como el evidenciado por la operación FRONTQ que encuentra el elemento frontal de la cola comenzando en el último elemento. A esto respondemos que una especificación no debe verse como una descripción del eventual programa implementado, sino simplemente como un medio para comprender la operación que está por hacer. 

Un segundo comentario supone que los nombres de la operación no están bien elegidos y luego se pregunta ¿Qué tan fácil es es revelar su significado a través de los axiomas?. Esto es difícil de responder, especialmente cuando se trata de imaginar cómo otras técnicas serían justamente bajo esta restricción. Sin embargo, podríamos preguntarle al lector si él puede determinar qué hace la operación MISTERIO donde:

MYSTERY(Queue) --> Queue and
MYSTERY(NEWQ) --> NEWQ
MYSTERY(ADDQ(q,i)) --> APPENDQ(ADDQ(NEWQ,i),MYSTERY(q))

Consideremos otro tipo familiar: String. En la especificación de la Figura 2.4, hemos elegido cinco operaciones primitivas:


NULL, que crea la cadena nula; ADDCHAR, que agrega un carácter a una cadena; CONCAT, que une dos hilos; SUBSTR (s, i, j), que de una cadena s devuelve la subcadena de caracteres j comenzando en el i-ésimo carácter de s; INDICE (s, t), que devuelve el posición de una cadena t como subcadena de una cadena s (0 si t no es una subcadena de s).

Tenga en cuenta que existen varios tipos que componen esta definición además del tipo String; a saber, Carácter, Entero y Booleano. En general, una especificación de tipo de datos siempre define solo un tipo, pero se puede requerir en las operaciones de otros tipos de datos para lograr esto

Otra pregunta que surge de nuevo es ¿Cuándo debería una operación ser parte de la especificación y cuándo no debería serlo?, problema que ya hemos encontrado con árboles binarios. Las operaciones que hemos elegido aquí son básicamente las que se proporcionan en PL / I.

Hasta ahora nos hemos concentrado principalmente en cómo leer los axiomas. Ahora consideremos cómo crearlos. Como esquema general de ataque, comenzamos con un conjunto básico de operaciones fl, ..., fm.  Un subconjunto de estos, digamos 'fl, ..., fk k_ <m, tienen como salida el tipo de datos que es definido. De las k operaciones se elige un subconjunto que llamamos el conjunto de constructores que satisface la propiedad de que todas las instancias de los tipos de datos se pueden representar utilizando solo operaciones de conjuntos de constructores. Entonces, los axiomas que deben escribirse son los que muestran cómo cada operación de conjunto no constructor se comporta en todas las instancias posibles de tipo de datos.

Como un nuevo ejemplo, considere el tipo Set. Las operaciones cuyo rango es de tipo Set son: 

EMPTYSET que tiene el habitual sentido; INSERT y DELSET que ponen un elemento en uno o lo eliminan del conjunto respectivamente. De estas tres operaciones seleccionamos EMPTYSET e INSERT como constructores. Entonces un conjunto arbitrario que contiene n_> 1 elementos viene dado por la expresión

INSERT(...INSERT(EMPTYSET,i 1 ,),...,in).

Una característica muy importante de esta definición es el hecho de que no existe pedido asumido en los artículos. Alternativamente, la especificación podría insistir en que il <i2 <... <in también sea cierto. Una especificación anterior de un conjunto que asumió que se dio un pedido en [Zilles 75].


El tipo de gráfico de la figura 2.6 es interesante en varios aspectos.  La definición matemática de un gráfico es generalmente en términos de dos conjuntos: nodos y aristas. Esto se refleja en los constructores de esta definición que son EMPTYGRAPH, ADDNODE y ADDEDGE. Esta definición permite un gráfico no conectado y para nodos sin incidentes en sus bordes. Una arista está dada por la función REL (i, j) (un constructor del borde del tipo de datos) y no se especifica si los bordes están dirigidos o no.


Observe que tres de las operaciones resultado en conjuntos y la notación de parámetros ha sido naturalmente extendida para distinguir entre conjuntos con diferentes tipos de elementos. ADJAC encuentra todos los nodos adyacentes a algún vértice. NODOUT (g, v) elimina el nodo v y todos los bordes incidentes a v. EDGEOUT elimina un solo borde del gráfico.

El siguiente ejemplo es un tipo de datos de archivo secuencial (Figura 2.7). Las operaciones incluyen READ, WRITE, RESET, ISEOF (fin de archivo check) y SKIP (más allá de un número específico de registros).


Las operaciones secuenciales de archivos no serían, en la práctica, implementados como funciones, sino más bien como procedimientos con efectos secundarios, diga READP (f, r) y WRITEP (f, r). 

Las operaciones que hemos dado pueden utilizarse para especificar los efectos de estos procedimientos:

READP (f, r) significa r <- LEER (f), f ~ SALTAR (f, 1); y WRITEP (f, r) significa f <- WRITE (f, r). 

Nota: Los axiomas implican que si una operación SKIP sigue inmediatamente a una de ESCRIBIR, significa restablecer el archivo a su comienzo, luego saltar más allá de i registros. Además, si se sobrescribe un registro, la parte del archivo más allá se pierde el registro. Para un estudio más detallado de los axiomas, tenga en cuenta que todos los valores de archivos se pueden ver como una de las siguientes formas de cadena:

EMPTYFILE o WRITE (WRITE (, .. (EMPTYFILE, r 1), ..., rn) o 
SKIP (WRITE (WRITE (..., (EMPTYFILE, r 1,), ..., rn), i).

Concluimos este apartado con una presentación de tipo Polinomio. La definición matemática habitual de un polinomio es una expresión de la forma:


donde x es un indeterminado y el ai proviene de algún anillo conmutativo. Si am fO entonces m se llama grado, am el coeficiente principal, y am_lxm-l + ... + alx + a 0 el reductum.

Una especificación de Polinomios como un tipo de datos con once operaciones en la Figura 2.8.


En esta especificación cada polinomio es CERO o construido aplicando ADDTERM a un polinomio. 

Nota: La ausencia de supuestos sobre el orden de los exponentes, distintos coeficientes de cero, etc., que son importantes como decisiones de representación pero no es esencial para la especificación.

La verdadera virtud de esta especificación es que un sistema bastante complejo el objeto se ha definido completamente utilizando solo unas pocas líneas. los programas correspondientes en un lenguaje de programación convencional pueden ser varias veces este tamaño. (Esto será especialmente cierto si algunos de se utilizan los algoritmos "rápidos".)

Procedimientos y tipos finitos

Hasta ahora todos los tipos de datos abstractos que tenemos axiomatizados han sido infinitos. Es relevante observar un paralelo aquí entre la informática y las matemáticas, que los tipos finitos a menudo son más difíciles de definir que los infinitos. Esta sección tiene la intención de hacer frente a las complicaciones añadidas de especificar más tipos de datos realistas. En particular, investigamos cómo especificar un tipo de tamaño finito. Al mismo tiempo relajaremos la restricción que todas las operaciones sean de un solo valor y permitan una notación que se asemeja al uso convencional de procedimientos. Esta notación fue introducida por primera vez en [Guttag 76b].

Ahora será permisible incluir procedimientos en las especificaciones. Un procedimiento P cuyo primer argumento, x, se modifica como un resultado de su ejecución pero no su segundo argumento y, es declarada sintácticamente como P (var x, y). Si P es un procedimiento puro, es decir, no devuelve ningún valor, entonces esto se expresa sintácticamente escribiendo P (var x, y) ->. La definición del procedimiento P se incluirá en la especificación semántica del tipo de datos. Un procedimiento tiene cuerpo y una parte de valor opcional separada por un punto y coma, p. ej.

P (var x, uar y) - x * - F (x, y), y <- G (x); H (x, y).

es una posible definición de P donde F, G, H son funciones y H devuelve un valor, observe que la asignación simultánea a los parámetros ahora es permitido, pero seguimos adhiriéndonos a nuestro enfoque anterior al requerido que cada procedimiento sea expresado por funciones de un solo valor.

En algunos casos, estas últimas operaciones ya no serán accesibles por el usuario del tipo de datos. Las llamamos funciones ocultas e indíquelos colocando una estrella junto a su nombre.

Como ejemplo, damos en la Figura 3.1 la especificación de un cola de tamaño finito. Nótese que en comparación con la cola infinita de la Figura 2.2 se han agregado cuatro nuevas operaciones. ADDQ y DELETEQ ahora se designan como funciones ocultas y en su lugar el usuario aplicará el procedimiento puro ENQ y la función DEQ, los cuales tienen el efecto secundario de alterar su primer argumento. Observe también que hemos aumentado el valor INDEFINIDO operación permitiendo que sea calificado. Esto facilitará la manejo de errores distinguiendo su origen.

Otras direcciones 

En este artículo hemos destacado el arte de la especificación de tipo de datos. Nuestro principal objetivo ha sido explorar una notación que sea especialmente atractiva para definir formalmente un tipo de datos sin tener en cuenta a su implementación. En esta sección queremos indicar brevemente cómo estas especificaciones se pueden utilizar para diseñar software confiable, pero para una discusión completa de especificación reservadas en [Guttag 76b].

El primer uso de una especificación axiomática es como una ayuda al diseñar e implementar el tipo. Se toma la decisión de elegir un forma particular de implementación. Esta implementación estará en términos de otros tipos de datos y asumimos que sus especificaciones ya existen. Para un tipo de datos complejo, este proceso puede continuar a través de varios niveles antes de que una implementación ejecutable sea lograda. La virtud de las especificaciones es que cada etapa se realiza más claro organizando los tipos, valores y operaciones que se pueden usar.


Un segundo uso de estas especificaciones y quizás su mayor importe, es para demostrar que una implementación es correcta.

Establecer la corrección ahora equivale a mostrar que los axiomas originales se satisfacen con la implementación recién desarrollada. A menudo es posible eludir la necesidad de inducción y, en cambio, una sencilla manipulación de la fórmula resulta suficiente. Esta el proceso también se presta con bastante facilidad a la automatización. Este trabajo es descrito en [Guttag 76b].

Otro uso de estas especificaciones es para pruebas tempranas. Eso sería muy deseable si uno pudiera diseñar un sistema de tal manera que podría probarse antes de comprometer a la gente a construirlo.

Dadas las restricciones adecuadas sobre la forma en que las ecuaciones axiomáticas puede tomar, un sistema en el que implementaciones y algebraicos se pueden construir de especificaciones de tipos de datos intercambiables.

En ausencia de una implementación, las operaciones del tipo de datos pueden interpretarse simbólicamente. Por lo tanto, a excepción de una pérdida significativa en eficiencia, la falta de una implementación se puede hacer completamente transparente para el usuario. Curiosamente no es necesario gastar muchos años-hombre desarrollando este sistema. La capacidad es esencialmente disponible en sistemas de manipulación de símbolos basados ​​en LISP como SCRATCHPAD [Griesmer 71], REDUCE [Hearn 71] y MACSYMA [Martín 71]. El uso de REDUCE para este propósito se discute en [Guttag 76b], que también analiza las ideas esenciales de un patrón compilador de coincidencias diseñado especialmente para la compilación de axiomas algebraicos .

Agradecimientos

Varias personas hicieron comentarios valiosos sobre borradores anteriores de este paper, incluidos Ralph London, Mary Shaw, Tim Standish, Dave WIle y los árbitros. Agradecemos a Betty Randall por su experiencia. y paciencia para mecanografiar muchos borradores del documento.

Referencias

[Dahl 70] OJ Oahl, B. Myhrhaug y K. Nygaard, "The SIMULA 67 idioma base común, "Norwegian Computing Center, Oslo, Publicación S-22, 1970.

[Dahl 72] OJ Dahl, EW Dijkstra y CAR Hoare, Strctctred Programación, Londres: académico, 1972, págs. 1-82.

[Goguen 75] JA Goguen, JW Thatcher, EG Wagner y JB Wright, "Tipos de datos abstractos como álgebras iniciales y exactitud de las representaciones de datos, actas, conferencias sobre gráficos por computadora, reconocimiento de patrones y datos Estructura, mayo de 1975.

[Griesmer 71] JH Griesmer y RD Jenks, SCRATCHPAD / Z -Una instalación interactiva para las matemáticas simbólicas " Proc. Segundo Simposio de Simposio y Algebraico Manipulation, ACM, marzo de 1971, págs. 42-58.

[Guttag 75] JV Guttag, "La especificación y aplicación para programación de tipos de datos abstractos, "Ph. D. Thesis, Universidad de Toronto, Departamento de Ciencias de la Computación, 1975, disponible como Informe de investigación de sistemas informáticos CSRG-59.

[Guttag 76a] JV Guttag, "Tipos de datos abstractos y desarrollo de estructuras de datos, "Suplemento del Actas de la Conferencia SIGPLAN / SIGMOD sobre Datos: abstracción, definición y estructura, marzo de 1976, págs. 37-46.

[Guttag 76b] JV Guttag, E. Horowitz y DR Musser, "Tipos de datos abstractos y validación de software", USC Informe técnico del Instituto de Ciencias de la Información, 1976.

[Hearn 71] AC Hearn, "Reduce 2: un sistema y un idioma para manipulación algebraica, " Proc. Segundo Simposio sobre Manipulación simbólica y algebraica, ACM, marzo de 1971, págs. 128-133.

[Horowltz 76] E. Horowitz y S. Sahni, Fundamentos de datos Estructuras, Computer Science Press, junio de 1976.

[Liskov 75] BH Liskov y $. N. Zilles, "Especificación técnicas de abstracción de datos, " Transacciones IEEE en Ingeniería Softusare, Vol. SE-I, No. 1, marzo de 1975, págs. 7-18.

[Martin 71] WA Martin y RJ Fateman, "The MACSYMA sistema, " Proc. Segundo Simposio sobre Simbólicos y Algebraicos Manipulation, ACM, marzo de 1971, págs. 59-75.

[McCarthy 63] J. McCarthy, "Base para una teoría matemática de comlbutation, "en CotTtptLter Programming and Formal Systems, P. ~ Braffort y D. Hirchberg (eds.), North-Holland Publishing Compañía, 1963, págs. 33-70.

[Parnas 72] DL Parnas, "Una técnica para la especificación de módulos de software con ejemplos, " Comm. ACM, VoI. 15, págs. 330-336, mayo de 1972.

----------

Esta investigación fue apoyada en parte por Defense Research Agencia de Proyectos bajo contrato No. DAHC15 72 C 0308 y por el National Science Foundation bajo contrato No. MCS76-06089. Las opiniones expresadas son las de los autores.

* Departamento de Ciencias de la Computación, USC, Los Ángeles, CA 90007
** Instituto de Ciencias de la Información de la USC, 4676 Admiralty Way, Marina del Rey, CA 90291

DSN_XP y Alistair Cockburn en gratitud constante

 Metodología del software

Metodólogo Alistair Cockburn
DSN_XP entra en contacto con las ideas de Alistair mediante su familia de esquemas estratégicos para equipos de desarrollo de software o CRYSTAL CLEAR

Fuente de conocimientos

Metodología centrada en el potencial humano

Alistair se convierte en una fuente de conocimientos importantes para DSN_XP al poder tener como ejemplo a un investigador del software y metodólogo, todo un estudios de varios artefactos que ha ido estudiando y experimentando en su blog personal. 

Advanced Use Case

Alistair también se vuelve un referente como metodólogo en el uso avanzado de los casos de uso para el diseño de software. DSN_XP venía desarrollando una mirada similar desde nuestro método de modelando avanzado basado en casos de uso y el poder de abstracción que nos brindaba UML.

Manifiesto Agile

Alistair es uno de los mentores del manifiesto agile, de hecho, nuestros registros sobre la concepción del manifiesto y las fotografías que adornan nuestro blog fueron liberadas por el propio Alistair a la comunidad.

IC Agile y el modelado de competencias

Alistair promovió un modelo de certificación que respondía a las necesidades de la comunidad internacional en base a un diseño muy interesante sobre formación de sus estudiantes.

Scrum

Alistair abandona al equipo IC Agile y comienza su gira internacional mediante su capacitación  en Scrum

Corazón de la agilidad

El último esfuerzo metodológico realizado por Alistar se denomina el Corazón de la Agilidad y sintetiza todo el proceso observado a lo largo del tiempo y hasta la fecha, tanto del proceso de capacitaciones como el material pedagógico de entrenamiento.