viernes, 30 de enero de 2009

Tareas complejas con HAL

Recordemos que el último paso de ayer, el que nos permitió construir la orden final

dict -d spa-eng bye

consistió en un acto realizado por la mano del hombre, la selección del nombre de diccionario spa-eng dentro de la lista bimembre:

eng-spa English-Spanish Freedict dictionary
spa-eng Spanish-English Freedict dictionary

Hay multitud de situaciones en la que este último reducto de actividad humana puede ser también eliminado y efectuado directamente por HAL. Veamos un ejemplo.

Supongamos que en nuestras búsquedas en diccionarios tenemos conocimiento seguro de la existencia de uno ---porque lo hemos usado otras veces---, pero cuyo nombre no recordamos ahora. Digamos que se trata de un diccionario sobre computación (computing en inglés).

Si procedemos como el día pasado, tendríamos que realizar los siguientes pasos:

  1. Buscar la línea referente al diccionario en cuestión en la lista entera de diccionarios:

    dict -D | grep -i 'computing'

    que devuelve [El uso del signo ¶ es premeditado, indica la existencia de un espacio en el comienzo de la línea. Más tarde veremos la razón de hacerlo visible]:

    ¶foldoc The Free On-line Dictionary of Computing (27 SEP 03)

  2. Ejecutar la orden dict dando a la opción -d el argumento foldoc, por ejemplo:

    dict -d foldoc debian


HAL se sentirá incómodo si seguimos esta estrategia. El segundo paso es perfectamente prescindible. Porque, aunque no lo parezca, no es necesario que intervengamos en ningún momento del proceso de construcción de la orden final.

Acallemos el orgullo herido de HAL y empecemos desde el principio, en la ignorancia del nombre de diccionario que nos interesa.

Antes de nada debemos realizar una reflexión metodológica previa de suma importancia. Es fundamental plantear metódicamente cada tarea que se emprenda de acuerdo a los principios de análisis y síntesis. El paso inicial es siempre el análisis del problema, su descomposición en partes. Cada una de estas partes constituye a su vez un problema que debe resolverse independientemente, en pasos sucesivos. El ataque final consiste en la síntesis de las soluciones parciales, que se efectúa mediante tuberías o dispositivos análogos. Que este tipo de estrategia sea común a muchas de las tareas no triviales que planteamos a HAL tiene su razón de ser en su propia arquitectura. Los creadores originales de HAL eran fanáticos defensores de una máxima: que cada herramienta haga una sola cosa, sencilla, pero bien hecha, y que las herramientas complejas resulten de la composición de herramientas simples. Se las apañaron para que las herramientas simples fuesen lo suficientemente flexibles como para pudiesen engarzarse una con otra y se generasen a partir de ellas herramientas complejas. Las tuberías son el ejemplo más destacado, quizá, de este tipo de planteamiento. Nótese que, en el fondo, este es el principio dominante de todas las ingenierías y de una buena parte del pensamiento humano.

Miremos, pues, con estos ojos, el problema que tenemos delante, la construcción de la orden siguiente:

dict -d XXXX 'computing'

Las XXXX designan lo que no sabemos, el diccionario que queremos consultar, un diccionario sobre computación de cuya existencia estamos seguros, pero cuyo nombre no recordamos.

Sabemos, además, que estas XXXX ---el nombre de un diccionario--- han de ser forzosamente una de las palabras que aparecen en la primera columna de la lista que genera dict -D, dado que cada línea de tal lista tiene el formato característico:

¶NOMBRE-DICCIONARIO DESCRIPCIÓN

[En realidad, en el caso presente no hay un único espacio de separación entre NOMBRE-DICCIONARIO y DESCRIPCIÓN. Pero vamos a pasar por encima de este detalle para no complicar las órdenes que vamos a utilizar más tarde. En todo caso, el resultado va a ser el mismo.]

Podemos obtener con grep una línea de esa lista, realizar, por así decir, un corte longitudinal en la lista. ¿Pero es posible obtener de una línea con un formato tipificado como el anterior alguna de sus columnas, en concreto, únicamente la primera? ¿Es posible realizar cortes transversales en ella? De ser así, habríamos dado un paso adelante en nuestro objetivo. Si lo lográsemos, aún quedaría otro paso que dar, conseguir que el resultado del corte transversal se introdujese en el lugar que corresponde en la orden final, o sea, en lugar de las XXXX.

Resolvamos el problema por partes, justo las que han surgido de nuestro análisis.

La primera es conseguir cortes transversales de líneas con varias columnas o campos (fields). HAL tiene una tijera especializada para realizar esta operación, la orden cut. Funciona de la siguiente manera: se le indica a HAL el carácter que sirve para delimitar cada campo de las correspondientes líneas (con la opción -d de cut) y se le pide que nos devuelva los campos que nos interesan (con la opción -f de cut) [los campos se identifican por su posición en la lista: el primer campo empezando por la izquierda es el número 1; el segundo, el número 2; etc.]:

Por tanto,

cut -d' ' -f1

devuelve la primera columna o campo de la línea o líneas que se le suministren a cut como entrada, donde cada campo está separado del siguiente por un espacio, que, como de costumbre entrecomillamos: ' '.

De igual forma que grep, cut puede tomar sus datos de entrada de cualquier parte, por ejemplo, de un fichero, pero también del propio dispositivo de entrada por defecto ---la cañería principal, ¿se recuerda? Probemos esto último:

cut -d' ' -f2

Tras el Enter, procedemos a alimentar a cut:

Hola cut
cut # cut devuelve el segundo campo
Ctrl+D

(como el día pasado, con Ctrl+D salimos del experimento.)

Antes de seguir con la segunda parte del problema ---y para que el lector no se pierda luego--- sinteticemos provisionalmente las piezas con las que ya contamos. Son las siguientes:

  1. dict -D nos devuelve la lista entera de todos los diccionarios.

  2. grep -i 'computing' nos devuelve las líneas que contengan la palabra computing.

  3. cut -d' ' -fn nos devuelve el campo número n de la línea o líneas, donde cada campo esta separado del siguiente por un espacio.


Si unimos mediante una tubería la primera pieza con la segunda ---lo que hicimos el día pasado--- obtenemos una línea de la lista de todos los diccionarios, la referida al diccionario sobre computación. Esta línea (la salida de la tubería citada) se puede convertir a su vez en la entrada de la tercera pieza, precisamente por medio de otra tubería.

dict -D | grep -i 'computing' | cut -d' ' -f1

que nos devuelve el resultado esperado, o no ...:

[Vacío]

¡Fallo de HAL! ¿Cómo es esto posible? La cadena de tuberías es correcta. Todo parece bien diseñado y bien montado.

Si nuestro HAL fuese el HAL de la serie 9000 del film de Kubrik y nosotros el Dave Bowman de la película, HAL respondería, enojado por nuestras sospechas, con aquello de: "Ninguna computadora de la serie 9000 puede fallar. Este incidente sólo es atribuible a error humano." Si hubiésemos sido Dave Bowman no le hubiésemos creído y todo se habría ido al traste. Pero nosotros no somos Dave Bowman, porque también vimos la secuela de 2001, el film 2010, donde se constata que HAL tenía razón. Por tanto, debemos revisar con más cuidado lo que le hemos hecho digerir a nuestro HAL y asumir que el error es únicamente nuestro.

El error tiene que residir en la última tubería, puesto que la primera funciona perfectamente, como hemos visto al principio del artículo. Recordemos lo que nos devolvía esta primera tubería:

¶foldoc The Free On-line Dictionary of Computing (27 SEP 03)

Fijémonos en el signo incial, el ¶ que puse al principio para hacer visible el espacio con el que comienza la línea. El espacio es, justo, el delimitador que indicamos como separador de los distintos campos. Luego, este espacio, en apariencia insignificante para nosotros, es tan importante para HAL como el resto de letras y espacios de la línea. Para él funciona como el primer separador. Por eso nos ha devuelto el vacío, la nada: la palabra de 0 letras, por así decir, que hay antes de él.

Detectado el error, la situación es fácil de resolver si pedimos a HAL que nos devuelva el segundo campo de la línea y no el primero. [Existe otra forma más elegante de tratar la dificultad pero requiere el uso de otra tubería y otra orden y no conviene saturar al lector con tanta materia.]:

dict -D | grep -i 'computing' | cut -d' ' -f2

Que ---ahora, sí--- devuelve el resultado esperado:

foldoc

Diseñada la solución de la primera parte de nuestro tarea, queda la segunda, a saber, conseguir que la salida de la cadena de tuberías que acabamos de preparar se convierta en el argumento de la opción -d de dict.

En este tipo de situaciones no es posible el simple uso de una tubería, puesto que una tubería conecta la salida de una orden con la entrada de otra orden. Pero nuestro problema ahora es hacer que la salida de una orden se convierta, por así decir, en la entrada de un componente interno de la orden, y no de la orden considerada en total. En definitiva, nos vendría de perlas algún mecanismo, algún constructo en la lengua de HAL, que nos permitiese pedirle que sustituyese una orden (command substitution) por su resultado, algo así como un pronombre, si seguimos hablando en términos lingüísticos. Este mecanismo existe y su sintaxis es bien simple:

$(ORDEN)

Un ejemplo fácil:

echo 'El nombre de mi máquina es' $(hostname)

Tras la resolución de esta segunda parte del problema, sólo nos queda sintetizarla con la solución de la primera parte:

HAL, búsca en el diccionario cuyo nombre es éste (=el resultado de buscar 'computing' en la lista de todos los diccionarios y cortar en la línea resultante el nombre suyo) la palabra HAL

O mucho más simple ---la lengua de HAL empieza a ser más simple y comprensible que la nuestra:

dict -d $(dict -D | grep -i 'computing' | cut -d' ' -f2) HAL


A disfrutar de la respuesta.


Resumen:

  • Para resolver tareas con HAL es fundamental seguir una metodología determinada que consiste en analizar el problema, resolver cada parte del problema y sintetizar las soluciones parciales.

  • Aspecto central de la filosofía de diseño de HAL es que cada una de sus herramientas haga sólo una cosa, pero bien hecha.

  • La orden cut permite obtener de una o varias líneas los campos o columnas suyas que se deseen, siempre que cada campo está separado del siguiente por un mismo carácter delimitador.

  • Por medio de tuberías se pueden construir encadenamientos complejos de órdenes.

  • La sustitución de órdenes permite sustituir una orden por su resultado, allí donde tal resultado convenga en el interior de otra orden.

2 comentarios:

  1. Excelente post, esto se esta poniendo bueno. Sigo con mucha atencion tu blog, es excelente. Saludos.

    ResponderEliminar
  2. ¿Qué característica tenían los algoritmos diseñados por Al-Khorezmi?

    ResponderEliminar