LOGO en el contexto de los lenguajes de inteligencia artificial

Claudio Gutiérrez


Contribución presentada al V Congreso Internacional de LOGO en 1991.

Introducción

El lenguaje LOGO fue creado en el "Massachusetts Institute of Technology" (MIT) por el investigador en inteligencia artificial y renombrado teórico de la educación Seymour Papert. Pertenece a la familia de lenguajes simbólicos, y por ello se diferencia sustancialmente de los más conocidos lenguajes de programación, como Basic, Pascal, Cobol o Fortran. No obstante, tiene también capacidades matemáticas suficientes como para haber sido postulado como la mejor introducción a estas disciplinas por el mismo Papert. (PAPERT 80) En particular, LOGO es un pariente cercano de otros lenguajes simbólicos diseñados específicamente para sustentar la investigación en inteligencia artificial. Más concretamente, LOGO es una versión simplificada del lenguaje LISP (del inglés "List Processing"), uno de los más evolucionados lenguajes de inteligencia artificial, adaptada para tener capacidades gráficas y ser más asequible a los niños.

No deja de ser paradójico, y emocionante, que el lenguaje de computación que usan los niños de las escuelas rurales más remotas de Costa Rica, sea un miembro de la familia de los lenguajes de inteligencia artificial, que son los lenguajes de programación más poderosos que existen. El tópico es suficientemente sugerente como para que lo exploremos con alguna profundidad, con la misma curiosidad que demostraríamos al descubrir que alguno de nuestros amigos está emparentado con miembros de la realeza reinante. Un título alternativo para esta charla, pues, podría haber sido "La Ilustre Parentela del Lenguaje LOGO". En todo caso, y vana curiosidad aparte, resulta interesante para personas que trabajamos rutinariamente con LOGO, estar enterados de su posición en el contexto general de los lenguajes de programación y familiarizarnos con algunas de las coincidencias y divergencias de los lenguajes más cercanos a él.

Para comenzar nuestra tarea, tratemos de ubicar primero a toda la familia de los lenguajes de inteligencia artificial dentro del género total de los lenguajes de programación. Nada mejor para ello que citar a Marvin Minsky, en su estupendo artículo sobre las capacidades intelectuales de las computadoras. (MINSKY 82) En ese artículo Minsky se pregunta si, como creen muchas personas, las computadoras pueden solamente hacer lo que se les ordena. Como forma de contestar esta pregunta, reconoce, en primer lugar, que los más antiguos programas computacionales eran ciclos de instrucciones del estilo "haga esto; haga lo otro; haga esto y lo otro, y siga haciéndolo hasta que aquello pase". Enseguida procede a dar una clasificación aproximada de los lenguajes de programación, y nos dice que esos primeros programas estaban escritos en lenguajes del tipo hacer ahora. Esos lenguajes obligan al programador "a imaginar todos los detalles acerca de cómo el programa va a pasar de un estado a otro, en cada momento". Es difícil concebir como un programa así "podría hacer algo que su programador no hubiera pensado".

Pero los investigadores desarrollaron otras maneras de programar. "Por ejemplo, el GPS creado por Newell, Shaw y Simon permite describir procesos en términos de declaraciones como 'si la diferencia entre el estado que tiene y el que desea es de clase D, entonces trate de cambiar la diferencia mediante el método M'. Llamaremos a esto programación de hacer cuando." Los programas escritos en esta clase de lenguajes deben decir lo que necesita suceder en cada estado, pero el programador no tiene que saber por adelantado cuándo va ocurrir cada cosa. Es difícil decir en este caso que todo lo que hace la computadora lo previó el programador. En efecto, la situación es semejante a la educación que recibe un ser humano: el maestro dota al alumno de una serie de recursos, pero cómo los llegue a aplicar es algo que no puede preverse. (GUTIÉRREZ Y CASTRO 87 )

Un tercer nivel de capacidad se da con lenguajes, como LISP, capaces de usar recursión general: "Estos lenguajes son todavía más expresivos que los de hacer cuando, porque sus programadores no tienen que prever con claridad ni las clases de estados que puedan ocurrir ni cuando habrán de ocurrir; el programa únicamente coacciona la manera en que los estados y las estructuras se van a relacionar unos con otros. Podemos llamarlos lenguajes coactivos." (MINSKY 82) En este caso no se le dice a la computadora qué debe hacer sino a qué constreñimientos debe someterse en lo que hace. En estos lenguajes no hay realmente previsión del programador sobre lo que haga la máquina. El programador se coloca en un nivel abstracto, donde el procedimiento se define de manera general, para toda clase de datos, y la computadora se encarga de realizar los detalles. La situación es semejante a la de un empleado inteligente al que confiamos una misión, especificándola solamente en sus grandes rasgos.

Finalmente, y como una extrapolación hacia el futuro, Minsky nos habla de lenguajes posibles pero todavía no operacionales de hacer algo razonable. Tales lenguajes permitirán eventualmente a las máquinas tener "sentido común", como los seres humanos. Sin embargo, no se perfilan todavía en el horizonte. Los lenguajes de que vamos hablar, a cuya familia pertenece LOGO, son todos de la tercera clase, los llamados lenguajes de inteligencia artificial. Se llaman así porque se usan preferentemente en aplicaciones que producen en las máquinas comportamientos que, si los ejecutaran seres humanos, diríamos que requieren inteligencia.

Podemos distinguir tres grandes estilos o subfamilias en los lenguajes de inteligencia artificial. Todos ellos están formados por lenguajes de tipo coactivo y disponen del poder de la recursión. Pero tienen cada uno de ellos características especiales que les singularizan y les han dado popularidad entre sus practicantes. Voy a tratar de presentar a ustedes un ejemplo de cada estilo, advirtiendo que, aunque cada subfamilia tiene sus lenguajes específicos, los diferentes estilos de programación son, hasta cierto punto reproducibles en todos los grandes lenguajes que los representan por antonomasia, como veremos al final.

Los tres estilos de programación a que quiero referirme son los siguientes: programación funcional, programación relacional, y programación por objetos. El lenguaje más representativo del estilo funcional es LISP; LOGO, por su identificación con LISP, cae de lleno dentro de este estilo. El lenguaje más representativo del estilo relacional es PROLOG. El lenguaje más representativo del estilo de programación por objetos es SMALLTALK, pero existen varios dialectos de LISP que permiten programar en esta forma.

Lenguajes funcionales

Los lenguajes funcionales se caracterizan porque sus programas son expresiones simbólicas que se evalúan y producen un resultado, en el mismo sentido en que la expresión simbólica "(7 + 5)", al ser evaluada, produce "12". Esta característica contrasta notablemente con lo que ocurre con lenguajes del tipo imperativo, como FORTRAN o BASIC, en que un programa se define como una secuencia de comandos a ejecutar. Solo ocasionalmente una expresión en LISP, u otro lenguaje funcional, produce un "efecto", como distinto del "valor" de la expresión evaluada, por ejemplo, la activación de un periférico de la computadora o la asociación de un término con su significado en una tabla de definiciones. En el caso de LOGO, el aspecto de efecto está enfatizado, dado el sesgo del programa, hacia las aplicaciones gráficas. Una nota importante que debe subrayarse es que en los lenguajes funcionales puros, los argumentos de las funciones pueden a su vez ser funciones, las cuales deben ser evaluadas con anterioridad para que la función de mayor nivel pueda obtener sus argumentos efectivos con los cuales deberá evaluarse.

Voy a ilustrar el estilo funcional con un ejemplo del lenguaje funcional por antonomasia, LISP, creado por John McCarthy en los años cincuenta, específicamente para servir a la comunidad de investigadores de inteligencia artificial. (MCCARTHY 60)Al mismo tiempo ilustraré el poderoso recurso programático de la recursión, en la solución de un problema no trivial. Finalmente, este ejemplo nos servirá de ilustración para la tesis de Minsky de que, en los lenguajes coactivos, el programador no puede, ni necesita, prever todas las acciones concretas del programa.

Había una vez un monasterio en Hanoi que en su patio central tenía tres mástiles. En uno de los mástiles, de color azul, había doce discos, todos de distinto tamaño, uno encima del otro, siempre el más pequeño encima del más grande. Los otros dos mástiles, rojo y verde, estaban vacíos. Los monjes debían pasar cada día un solo disco de un mástil a otro, cumpliendo con la condición de que nunca un disco más grande debía caer sobre uno más pequeño. Los monjes asegurarían su salvación si lograban que todos los discos llegasen a estar en el mástil rojo. Lamentablemente, los monjes murieron antes de concluir su empresa piadosa, pues no encontraron las 4.095 movidas individuales de disco necesarias para resolver el acertijo.

En el gráfico adjunto representamos una versión más simple del problema: se trata de pasar una torre de solamente cinco discos. Si intentamos solucionar este acertijo con un lenguaje de hacer ahora, o incluso con uno de hacer cuando, el problema es dificilísimo de solucionar. En cambio, usando un lenguaje coactivo podemos fácilmente definir, en forma recursiva, los constreñimientos que deben cumplirse para que se dé una solución. La maniobra es equivalente a ver el problema como un proceso de mover torres, en vez de un problema de mover discos. Se nos dirá que las reglas del juego no. permiten mover torres, y es verdad. Pero precisamente se trata aquí de definir un procedimiento para mover torres que no infrinja las reglas.

La versión simplificada del problema, para torres de cinco discos, se expresa con la siguiente invocación en LISP:

que quiere decir "pasar una torre de tamaño 5 tomando como mástil de origen el mástil azul, como mástil de destino el mástil rojo y como mástil de ayuda el mástil verde" (el mástil de ayuda es necesario para poner los discos que hay que apartar provisionalmente para llegar al próximo disco más grande). El valor de la función será, por supuesto, la lista de jugadas legítimas para solucionar el acertijo.

Veamos cómo se razona coactivamente para idear este programa. Nuestro problema puede reducirse a tres problemas más pequeños: pasar una subtorre al mástil de ayuda, pasar el disco mayor al mástil de destino, y volver a pasar la subtorre, esta vez al disco de destino. Todo esto supone, desde luego, que se pueda encontrar una manera de pasar la subtorre sin infringir las reglas. Pero como lo mismo que acabamos de decir podemos decirlo de la subtorre, hasta llegar a una torre de tamaño nulo, es claro que podemos lograr la solución buscada.

He aquí como queda nuestro programa en un lenguaje funcional (COMMON LISP). Damos por supuesto que existe una definición previa de la función "pasar-disco", que simplemente pone los nombres de los mástiles de origen y destino en una doble lista(1). La traducción a LOGO es trivial.

Como el programa es recursivo, la computadora tendrá que llamarlo muchas veces desde dentro del mismo programa, y cada vez usará diferentes mástiles como los mástiles de origen, destino y ayuda. En cada grupo de llamadas cambiará además el tamaño de la torre secundaria que la computadora deberá pasar, la cual irá siendo tanto más pequeña conforme más avance la recursión. El valor reportado por la función será una lista de listas, cada una de las cuales representará la movida de un disco de un mástil a otro.

Lo importante en todo esto es que el problema se nos aclara si pensamos coactivamente, definiendo en abstracto los constreñimientos que tienen que cumplirse, sabiendo que la recursión llegará a un punto en que no necesite hacer nada para resolver el problema más simple. En este caso, ese problema más simple es pasar una torre con cero discos, que por supuesto es muy fácil: no hay que hacer nada en absoluto.

Algo interesante de este programa es que la computadora lo obedece moviendo siempre un solo disco por vez, como lo exigen las reglas, pero esas movidas son inesperadas para el programador, que no tiene idea del orden en que efectivamente se tienen que ir moviendo los discos: lo único expresado en el programa es el constreñimiento recursivo que exige pasar un disco en medio de dos movidas de torre. Lo sorprendente es que, establecidos esos constreñimientos, el programa resuelve el problema sin hacer nunca una jugada innecesaria; además, es capaz de pasar torres de cualquier tamaño. Confirmando el decir de Minsky, los detalles de la ejecución no son establecidos directamente por el programador.

Lenguajes relacionales

El segundo estilo de programación de inteligencia artificial es el estilo relacional. El mejor ejemplo es PROLOG, un lenguaje simple y poderoso desarrollado en los años setenta por varios investigadores europeos que aplicaron descubrimientos de otros lógicos. (KOWALSKI 82) Es también un lenguaje recursivo. Se diferencia de LISP y los otros lenguajes funcionales, en que es más explícito en la definición de los constreñimientos, incorporando dentro de la expresión que se evalúa, un lugar para el resultado de la computación (en contraste con la función, cuyo valor es simplemente reportado). Una típica expresión en PROLOG es

que al ser evaluada podría dar como resultado (dependiendo del resto del programa) las ecuaciones

Se dice de PROLOG que es equivalente a la lógica de predicados, usada como lenguaje de programación. En esta perspectiva, se da una considerable simplificación de las tareas del programador. En efecto, para programar debemos normalmente primero especificar nuestro problema usando la lógica. Una vez hecho esto, nuestro trabajo consiste en traducir la especificación al código particular del lenguaje de programación. De ahí la siguiente ecuación:

Pero si especificamos en lógica, y la lógica que usamos es la versión llamada de cláusulas Horn, que tiene la misma sintaxis de PROLOG, de hecho no necesitamos codificar. Sin embargo, se reconoce que los programas especificados en pura lógica, incluso usando solamente cláusulas Horn, no son todo lo eficientes posible: es necesario entonces agregar un toque de decisión de control, como por ejemplo cambiar el orden de las cláusulas o de los elementos de las cláusulas, a fin de lograr una ejecución eficiente. Así pues, se nos ofrece la ecuación:

Sea esto como sea, lo cierto es que PROLOG ofrece dos características muy atractivas para el programador de alto nivel. Por una parte, la ejecución de un programa se identifica con un procedimiento deductivo, lo que permite muy fácilmente dotar de capacidades intelectuales a nuestros programas. Por otra parte, las expresiones relacionales, con ayuda del uso de variables lógicas, permiten a los programas PROLOG ser usados "en varias direcciones", como por ejemplo si un mismo programa nos sirviera para sumar y restar. En el caso de la expresión mencionada arriba, si nuestro programa consiste únicamente de la expresión

es posible evaluar la pregunta

pero también la pregunta muy diferente

con exitosos resultados. Las capacidades deductivas del lenguaje las podemos fácilmente ilustrar si agregamos a nuestro programa las dos cláusulas siguientes:

donde las letras mayúsculas son variables lógicas. Si ahora preguntamos:

donde la palabra con mayúscula es una variable, el sistema responderá con la ecuación:

Todo esto nos hace ver que PROLOG es un lenguaje muy plástico, susceptible de diversas interpretaciones. Puede entenderse:

  1. como un sistema probador de teoremas, o sistema axiomático;
  2. como un lenguaje de programación; y
  3. como un sistema contestador de preguntas.
El punto 3 merece un comentario. Lo que en informática se usa normalmente como sistema contestador de preguntas es una base de datos, generalmente de tipo relacional. Un programa PROLOG que tuviera solamente cláusulas del tipo de

llamadas cláusulas de hechos, podría funcionar como una base de datos ordinaria. Pero al tener también cláusulas del tipo

llamadas cláusulas de reglas, en realidad se constituye en algo muy superior, a saber, una base de conocimientos. La diferencia es que la base de datos solo puede contestar reportando los hechos que tiene almacenados; la base de conocimientos, además, es capaz de contestar reportando esos hechos, y además todo lo que se puede deducir de ellos con ayuda de las reglas.

Programación por objetos

Los lenguajes de programación por objetos, por su parte, tienen un estilo muy particular, ya que tratan de representar a los objetos del mundo con que lidian nuestros programas con entidades computacionales cerradas sobre sí mismas, llamadas también objetos. Estos objetos computacionales son al mismo tiempo datos y procedimientos. En cuanto datos, los objetos se definen por variables privadas que no son inspeccionables desde fuera de ellos, y que conservan sus valores a través del tiempo. En cuanto procedimientos, los objetos se definen como métodos, que solo ellos pueden aplicar como reacción a ciertos mensajes que reciben. La computación se realiza por medio de intercambio de mensajes entre los distintos objetos. Los mensajes suelen consistir en requerimientos para que un objeto aplique uno de sus métodos a sí mismo (por ejemplo para variar el estado de sus variables privadas) o a otros objetos (enviándoles un mensaje); pero puede también suceder que un mensaje consista en un objeto que se envía a otro objeto. Generalmente los objetos se organizan en clases jerárquicamente organizadas, de modo que todos los miembros de una clase comparten ciertos atributos y que los miembros de una clase subalterna heredan atributos de la clase subalternante (por ejemplo, una clase de objetos puede ser "vehículos" y otra "automóviles", y los objetos de la segunda heredar de la primera clase el atributo de servir para transporte o necesitar combustible). (SHRIVER 87)

SMALLTALK fue desarrollado por el Learning Research Group del Centro de Investigación de la Compañía Xerox, en Palo Alto, California, al comienzo de los años setenta. Sus ideas principales se deben a Alan Kay. Las primeras realizaciones del lenguaje las produjo Dan Ingalls (primera persona que programó ventanas superpuestas en ambientes de cómputo). Otra persona que colaboró de manera importante en el proyecto fue Adele Golberg, del mismo laboratorio. (KAEHLER 86)

El desarrollo de SMALLTALK fue guiado por la experiencia con el sistema FLEX, creado por el mismo Alan Kay a fines de la década de los sesenta, y por ideas centrales del lenguaje SIMULA, desarrollado a la mitad de los sesenta por Ole-Johan Dahl y Kristen Nygaard del Centro Noruego de Cómputo en Oslo. El esfuerzo paralelo realizado en MIT para la creación de LOGO tuvo también innegable influencia. Su motivación principal fue proporcionar a los niños un ambiente de computación gráfico y concreto. Sin embargo, el lenguaje ha resultado ser de uso general, para toda clase de aplicaciones.

Aunque existan lenguajes específicos para este estilo objetivo de programación, es interesante subrayar que el mismo estilo de programación puede lograrse con un lenguaje funcional como LISP, en varios de sus dialectos. En particular, admiro la tersura con que este tipo de programación puede realizarse en uno de los dialectos más puros y elegantes de esta gran familia: el lenguaje SCHEME, otro producto del MIT. Me voy a permitir presentar un ejemplo bastante elaborado de programación por objetos, usando este terso lenguaje.

Se trata de crear un sistema de cuentas corrientes en un banco. Será necesario poder abrir cuentas a nombre de diferentes personas, emitir chequeras y libretas de depósito para cada cuenta, y realizar operaciones de giro y de depósito. El sistema deberá mantener, como es lógico, una contabilidad separada para cada cuenta, pero deberá ser capaz de recibir depósitos por medio de diversas libretas de depósito de la misma cuenta, así como aceptar cheques de distintas chequeras de la misma cuenta, todo ello sin confundirse. Además, cada cuentacorrentista deberá poder consultar el saldo de su cuenta en cualquier momento. El problema no es trivial, ya que su solución nos permitirá realizar todas las operaciones que normalmente asociamos con las cuentas corrientes. El programa en SCHEME que realiza todo eso, en el estilo de la programación por objetos, es el siguiente:

La palabra "lambda" en LISP es semejante a la primitiva "para" en LOGO, en el sentido de que identifica un procedimiento. Difiere de ella, sin embargo, en cuanto a que en LOGO no se puede usar "para" dentro de un procedimiento(2); mientras que en SCHEME la posibilidad de usar "lambda" dentro de la definición de otro procedimiento, con cualquier nivel de anidamiento, resulta crucial para producir la programación por objetos.

Explicaremos el programa por medio de su uso. Ante todo, el programa permitirá crear una cuenta corriente a mi nombre. Eso lo hago asignando a la variable "cuenta-claudio" un objeto que voy a crear con el procedimiento "haga-cuenta". Cuando uno abre una cuenta debe hacer un depósito inicial, de ahí que el procedimiento tenga un parámetro, "saldo":

De aquí en adelante, podemos contar con que "cuenta-claudio" es una cuenta corriente a mi nombre, que mantendrá un saldo determinado. La cuenta es un objeto de la clase "cuenta", que hereda todos los atributos (capacidades) de esa clase; pero que al mismo tiempo difiere de todos los otros objetos de la misma clase por el valor de su variable privada, a saber, "saldo". Así por ejemplo, la Fundación Omar Dengo puede abrir otra cuenta, con

y el objeto cuenta-fod dispondrá de su propio saldo que no tiene nada que ver con el mío. Literalmente "cuenta-fod" ni siquiera conoce la existencia del saldo de "cuenta-claudio", y "cuenta-claudio" ignora totalmente la existencia del saldo de "cuenta-fod".

¿En qué consiste el objeto "cuenta-claudio"? Como explicamos arriba, los objetos se definen por estados internos —variables privadas— y métodos —procedimientos privados—. En el caso de "cuenta-claudio", el estado interno es "saldo", con un valor inicial de 100.000. El método que define el objeto es el siguiente:

Nótese que el objeto que hemos almacenado en "cuenta-claudio" es un procedimiento, y en consecuencia, podrá ser activado, una o muchas veces, en el futuro; por cierto que es un procedimiento con un solo argumento, a saber, "mensaje", el cual deberá dársele cada vez que invoquemos el objeto (activemos el procedimiento). Y esto es precisamente la siguiente cosa que vamos a experimentar. Vamos a invocar el objeto "cuenta-claudio" para producir una chequera de mi cuenta corriente:

A partir de este momento, la variable "chequera-claudio" contiene un objeto de la clase "chequera" que me permitirá retirar fondos de mi cuenta. Nótese que en la definición de "chequera-claudio" se usa el procedimiento "cuenta-claudio" al cual le enviamos el mensaje "retiro". Vemos aquí un ejemplo de intercambio de mensajes entre objetos. Cuando "cuenta-claudio" recibe ese mensaje produce, de conformidad con su programa interno, el siguiente objeto que reporta como valor:

Tal objeto, que es un procedimiento (nótese que comienza con la palabra lambda), formará ahora el contenido de la variable "chequera-claudio". El procecimiento tendrá acceso a la variable privada "saldo" que esconde el valor actual del saldo de la cuenta corriente. Tal procedimiento podrá activarse en su oportunidad, para producir los resultados buscados con la operación de la chequera. Usemos ahora la chequera que hemos creado:

En ese momento se supone que yo he recibido el dinero correspondiente, y la computadora imprime el saldo que queda en mi cuenta después de este retiro, a saber, 99.000.

Más tarde, mi esposa me pide que le dé una chequera sobre mi cuenta, para usarla en los gastos de la casa. La produzco con la siguiente invocación:

Marlene va de compras, y en cierto momento considera oportuno averiguar el saldo actual de mi cuenta. Lo obtiene con la siguiente invocación:

que le responde imprimiendo el número 99.000. Nótese que dos chequeras diferentes, pero de la misma cuenta, dan resultados perfectamente congruentes con la realidad del movimiento de la cuenta.

Mientras tanto, la FOD considera oportuno emitir una libreta de depósitos sobre su cuenta, lo que hace con la invocación:

Esta invocación creará un objeto de la clase "libreta de depósito" cuyo contenido será el procedimiento

que tiene acceso al valor de la variable privada "saldo" de la respectiva cuenta corriente.

Ahora se puede hacer un depósito adicional en la cuenta, por 5.000.000, con la invocación:

lo que le producirá la impresión del saldo correcto, 10.000.000. Por supuesto, no habrá confusión con el saldo de "cuenta-claudio".

Lo más sorprendente de este programa —además de que funcione del todo— es su brevedad, ya que no ocupa más que una pantalla de texto. Lo cual es índice revelador del poder del estilo de programación por objetos.

Conclusión

Hemos presentado una visión muy sucinta de los tres estilos de programación en inteligencia artificial. Nos resta subrayar un hecho sumamente interesante, a saber, que —por lo menos en principio— es posible programar usando cualquiera de los tres estilos desde cualquiera de los tres lenguajes principales que epitomizan cada estilo: LISP, PROLOG, SMALLTALK. Puedo hablar de primera mano en el caso de LISP, pues he desarrollado un lenguaje escrito en LISP, llamado GOAL, que hace todo lo que hace PROLOG (y un poco más) en el estilo relacional. Además, acabamos de ver cómo un dialecto de LISP, SCHEME, se presta directa y elegantemente a la programación por objetos. Conozco de oídas intentos de programar desde PROLOG o SMALLTALK en los estilos diferentes al propio del lenguaje. La idea general, que cubre todos los cruces entre estilos y lenguajes, es la siguiente.

LISP es un lenguaje funcional. En consecuencia, sus unidades de acción son funciones. PROLOG, como lenguaje relacional, tiene unidades de acción que son relaciones. SMALLTALK, como lenguaje objetivo, tiene como sus unidades de acción objetos. Una estrategia razonable para integrar los estilos dentro de uno de los lenguajes consiste en identificar una de sus unidades de acción con la invocación al estilo diverso deseado.

Más concretamente: estando en ambiente LISP debo poder invocar una función que se llame —digamos— PROLOG, dándole como argumento el problema que quiero resolver en estilo relacional; el valor reportado por la función sería la misma fórmula de la invocación pero con todas sus variables lógicas sustituidas por los respectivos valores encontrados por el intérprete relacional.

Estando en ambiente PROLOG, debo poder ejecutar una relación que se llame —digamos— LISP, uno de cuyos argumentos sea una expresión funcional y otro sea el valor de la misma, que produciría el intérprete funcional.

Estando en un ambiente de programación por objetos, debiera poder dar un mensaje a un objeto llamado LISP, o PROLOG, que consista precisamente en una invocación que debe ser ejecutada por el respectivo intérprete.

LISP no tiene problema en trabajar con objetos, y varios dialectos del lenguaje han incorporado ya la programación por objetos, llamados sabores en varias de las implantaciones (en ZETA LISP, por ejemplo, el objeto más corriente es llamado "vainilla", por alguna extraña razón).

En cuanto a la posibilidad de llamar a SMALLTALK desde PROLOG, puede hacerse de manera análoga a la llamada a LISP desde un ambiente funcional.

De más está decir que todas estas llamadas de un estilo a otro pueden multiplicarse recursivamente, dando un resultado bastante alambicado que me gusta identificar —por razones obvias— como "club sandwich". Por experiencia de mi propia investigación en inteligencia artificial con el lenguaje GOAL, sé que esta forma de programar es extraordinariamente poderosa.

En un artículo reciente, Sherry Turkle y Seymour Papert examinan la tendencia hacia lo que ellos llaman pluralismo epistemológico como un rasgo deseable de la cultura computacional. La manera de combinar los distintos estilos de programación que hemos explicado aquí se inspira en el mismo espíritu. De un modo especial, estos autores resaltan la importancia de la programación por objetos, la cual les parece eminentemente compatible con la orientación de muchas personas que favorecen un acercamiento concreto a la computación. Tal estilo tiene la ventaja, para los usos en que estamos interesados —en palabras de Turkle y Papert— de emplear como unidad de pensamiento "la creación y modificación de agentes dentro de un programa para el cual las metáforas naturales son biológicas y sociales en vez de algebraicas. Tales agentes son capaces de interactuar entre sí como los actores de una obra de teatro" o, podríamos agregar, de un juego de niños. (TURKLE 90)

Copyright © 2000 Claudio Gutiérrez

Nota 1: La razón de la doble lista (una lista dentro de otra) estriba en que la lista interior representa una jugada particular, siendo un par ordenado cuyo primer miembro es el nombre del mástil de origen y el segundo el del destino. La lista exterior, por su parte, es solo un envoltorio provisional que se va unificando con los otros factores de la operación de concatenación para formar una gran "bolsa", a saber la lista reportada como resultado final. Esta lista resultante contendrá todos los pares ordenados que representan las jugadas en secuencia correcta. La función "concatenar" admite en el ejemplo tres argumentos, que deben ser también listas de listas de jugadas. Las listas vacías reportadas por la función en las puntas de la recursión no afectan el resultado, naturalmente. Nota agregada en 2002 por prurito de claridad.

Nota 2: Esta restricción de LOGO hace que sea imposible programar por objetos dentro de su ambiente.

Nota 3: Nótese que LOGO no puede hacer algo equivalente: al no permitirse el uso de "para" dentro de la definición de un procedimiento, no es posible que el valor reportado por un procedimiento de LOGO sea otro procedimiento.