Clips OO

Manejadores de Mensajes


Los manejadores son esenciales en OOP porque proporcionan el soporte para la encapsulación de objetos. La única forma con la que los objetos pueden responder a los mensajes es teniendo los adecuados manejadores para recibirlos y llevar a cabo las acciones apropiadas. 

Además de los manejadores predefinidos como print, clips también permite al usuario definir nuevos manejadores.

La clase NUMBER tiene las subclases INTEGER y FLOAT, son clases predefinidas como se vio en la práctica anterior, en la siguiente descripción de la clase INTEGER se puede 

ver que no dispone de ningún manejador de mensajes. Si se desea enviarle un mensaje print, se deberá crear un manejador para recibir y realizar esta acción. 

El manejador es definido para la clase NUMBER en lugar de  para INTEGER porque de esta manera también se pueden manejar los mensajes de los objetos de la clase FLOAT. En lugar de definir el mismo manejador para FLOAT e INTEGER,  es más útil definir un único manejador para la superclase NUMBER. Si un manejador para un mensaje dado no esta definido en la clase de un objeto, clips busca en todos los manejadores en la lista de precedencia. Por lo tanto si “+” no esta definido para INTEGER, CLIPS busca en NUMBER, donde encuentra el manejador a aplicar y devuelve el resultado de la acción.

Al pedir la descripción de la clase INTEGER en CLIPS, aparece el manejador “ +” definido por el usuario.

Si como se ha visto INTEGER es una clase abstracta, ¿cómo es posible que pueda tener instancias como 1, 2,3, etc.? La respuesta es que no se pueden crear directamente instancias de una clase abstracta, pero si se puede hacer uso de las instancias existentes. En el caso de la clase predefinida INTEGER, todos los enteros hasta el máximo permitido son objetos disponibles en clips. Análogamente, cadenas y símbolos son instancias disponibles de las clases abstractas STRING  y SYMBOL.

La variable ?self es una variable especial en la que clips almacena la instancia activa, que es la instancia a la que se envía el mensaje. ?self es una palabra reservada que no puede ser incluida explícitamente como argumento de un manejador, ni tampoco se le puede asignar un valor diferente mediante bind a la variable  ?self.

Ejercicio:
Define un manejador que concatene cadenas, símbolos o ambos.


Los manejadores normalmente se definen para las subclases de USER donde el usuario puede definir instancias de estas clases. Como norma y para mejorar la eficiencia se debe definir el manejador lo más cerca posible de la clase o clases para las que ha sido concebido. De esta manera se mejora la eficiencia ya que clips no tiene que ir buscando a través de las clases el manejador a aplicar.

COOL permite al usuario declarar por anticipado los gestores de mensajes de una clase dentro de la construcción defclass. El propósito de estas declaraciones es sólo para documentación y son ignoradas por CLIPS. No es necesario declarar previamente los gestores de una clase para poder definirlos y asociarlos a una clase.
En cualquier caso, para definir un gestor se debe utilizar el constructor defmessagehandler. Ejemplo:
(defclass rectángulo (is-a USER)
(role concrete)
(slot base (create-accessor read-write) (default 1))
(slot altura (create-accessor read-write) (default 1))
(message-handler calcular-area)) ; declaración, no definición

Sintaxis de defmessage-handler 

Como ya se ha visto, la construcción defmessage-handler se utiliza para especificar la conducta de los objetos de una clase en respuesta a un mensaje particular.



(defmessage-handler <nombre-clase> <nombre-mensaje>
[<tipo-handler>] [<comentario>]
(<parámetro>* [<parámetro-comodín>])
<acción>*)

La definición de un  gestor de mensajes consta de 7 partes:
1.  Un nombre de clase (ya definida) a la que el gestor estará asociado.
2.  El nombre del mensaje al que el gestor responderá.
3.  El tipo de gestor (por defecto se supondrá primary si no se especifica).
4.  Una cadena de comentario (opcional).
5.  Una lista de parámetros que se pasará al gestor durante la ejecución.
6.  Un parámetro comodín (para gestionar múltiples parámetros).
7.  Una secuencia de acciones o expresiones que serán ejecutadas por el gestor.

El valor devuelto por un gestor de mensajes es el resultado de la evaluación de la última acción.

Dentro de una clase, los gestores para un mensaje particular pueden ser de cuatro tipos diferentes:  [<tipo-handler>]

 primary: Realiza la tarea principal del mensaje.  before: Realiza trabajo auxiliar para un mensaje antes de que se ejecute el gestor primario (usado sólo para efectos laterales; se ignora el valor producido).
 after: Realiza trabajo auxiliar para un mensaje después de que se ejecute el gestor primario (usado sólo para efectos laterales; se ignora el valor producido).
 around: Inicializa un entorno para la ejecución del resto de los gestores.

El gestor primario proporciona la parte de la implementación del mensaje que es más específica al objeto (el definido en la clase más cercana anula los de las clases superiores).
Un mensaje utiliza todos los gestores before o after de todas las clases de un objeto. Esto quiere decir que si se envía un determinado mensaje a una clase C, se ejecuta el gestor primario de C, y todos los gestores before y after de sus superclases.


Partiendo de la clase rectángulo definida anteriormente:

CLIPS>(defmessage-handler rectangulo calcular-area () (* ?self:base ?self:altura)) ;el área (base por altura).

CLIPS>(defmessage-handler rectangulo calcular-area before ()
(printout t “****Empezando a calcular el área***”)) 

CLIPS>(defmessage-handler rectangulo calcular-area after ()
(printout t “****Área calculada***”)) 

CLIPS >(defmessage-handler rectángulo imprimir-área ()
(printout t (send ?self calcular-área) crlf))

El número de parámetros de un gestor de mensajes puede ser fijo o variable, dependiendo de que se proporcione un parámetro comodín,$?<nombre-variable>.

El cuerpo de un gestor de mensajes es una secuencia de expresiones que se ejecutan en el orden definido. Las acciones de los gestores tienen acceso directo a los atributos, mediante : ,son considerados como parte de la encapsulación del objeto.
 ?self:<nombre-slot> Ejemplo:
(defclass auto (is-a USER)
(role concrete)
(slot puertas (default 4))
(multislot seguridad (default cinturones)))

(defmessage-handler auto imprimir-slots ()
 (printout t ?self:puertas " " ?self:seguridad crlf))

La función bind también se puede utilizar para modificar el valor de un atributo:   (bind ?self:<nombre-slot> <valor>*)


Ejemplo:

CLIPS >(defmessage-handler auto actualizar-seguridad ($?valores)
(bind ?self:seguridad $?valores))

CLIPS >(make-instance Renault-19 of auto)
[Renault-19]

CLIPS >(send [Renault-19] actualizar-seguridad Airbag ABS)
(Airbag ABS)

Cuando un objeto recibe un mensaje mediante la función send, CLIPS examina la lista de precedencia de clases para determinar el conjunto de gestores de mensajes aplicables al mensaje.
Un gestor de mensajes es aplicable a un mensaje, si su nombre coincide con el mensaje y el gestor está ligado (asociado) a alguna clase de la lista de precedencia a la cual pertenezca la instancia que recibe el mensaje.
El conjunto de gestores de mensajes aplicables se ordena de acuerdo a su tipo o role (el orden es: around, before, primary, after), y a continuación se ordena por especificidad de clase:
 los gestores around, before y primary se ordenan de más especifico a más general.
 los gestores after .se ordenan de más general a más específico.

El orden de ejecución es como sigue:
1)      los gestores around comienzan a ejecutarse desde el más específico al más general (cada gestor around debe permitir explícitamente la ejecución a los otros gestores).
2)      Se ejecutan los gestores before (uno tras otro) de más específico a más general.
3)      Se ejecutan los gestores primary, desde el más específico al más general.
4)      Los gestores primary finalizan su ejecución, desde el más general al más específico.
5)      los gestores after se ejecutan (uno tras otro) de más general a más específico.
6)      Finaliza la ejecución de los gestores around del más general al más específico.
  

Emparejamiento de patrones con objetos

Para que pueda producirse la correspondencia o emparejamiento de patrones en objetos es necesario indicar en la declaración de la clase a partir de la que se crean las instancias la cláusula pattern-match reactive. El comportamiento por defecto es no permitir el emparejamiento, lo cual también se indicaría explícitamente mediante la cláusula “patternmatch non-reactive”. La posibilidad de emparejamiento se puede indicar a nivel de slot indicando las mismas cláusulas.

Ejemplo:

(defclass PERSONA (is-a USER) (role abstract))

(defclass HOMBRE (is-a PERSONA) (role concrete) (pattern-match reactive)
                         (slot nombre (create-accessor read-write))
                        (slot edad (create-accessor read-write) (default 0)
))

(definstances seres_iniciales
            (hombre1 of HOMBRE (nombre juan))
            (hombre2 of HOMBRE (nombre luis))
            (hombre3 of HOMBRE (nombre alfonso))
            (hombre4 of HOMBRE (nombre juan))
)

(defrule nombres_personas
             ?clase <- (object (is-a HOMBRE) (nombre ?cual))
=>
             (printout t "La instancia " (instance-name ?clase) " tiene de nombre " ?cual crlf)
)

El emparejamiento del objeto se hace especificando la clase y los valores de los slot que se quieran, se usa el elemento condicional para emparejamiento de objetos object para indicar que se está haciendo emparejamiento con un objeto. El nombre de la instancia no tiene importancia a la hora de realizar el emparejamiento en este ejemplo. Para acceder al nombre de la instancia se debe usar la función instance-name que toma como argumento el índice o dirección de la instancia y devuelve el nombre de la instancia.

Se puede hacer el emparejamiento en objetos de forma que también se empareje el nombre de la instancia, para esto, se usa en el patrón una restricción de nombre, que se indica mediante la palabra name seguida por el nombre de la instancia (entre corchetes) que se quiere hacer corresponder:

(defrule nombres_personas
            ?clase <- (object (is-a HOMBRE) (nombre ?cual) (name [hombre2])) =>
             (printout t "La instancia " (instance-name ?clase) " tiene de nombre " ?cual crlf)
)

La palabra name es una palabra reservada, no puede haber slots con ese nombre en ninguna clase.

Ejercicio: Modificar la regla en el siguiente programa para que se imprima el mismo resultado pero sin necesidad de obtener el índice de la instancia ni usar la función “instance-name”

(defclass PERSONA (is-a USER) (role abstract))

(defclass HOMBRE (is-a PERSONA) (role concrete) (pattern-match reactive)
                         (slot nombre (create-accessor read-write))
                        (slot edad (create-accessor read-write) (default 0)
))

(definstances seres_iniciales
            (hombre1 of HOMBRE (nombre juan))
            (hombre2 of HOMBRE (nombre luis))
            (hombre3 of HOMBRE (nombre alfonso))
            (hombre4 of HOMBRE (nombre juan))
)

(defrule nombres_personas
             ?clase <- (object (is-a HOMBRE) (nombre ?cual))
=>
             (printout t "La instancia " (instance-name ?clase) " tiene de nombre " ?cual crlf)
)

El sistema de consultas de COOL


Además de las reglas, se puede producir correspondencia de patrones en instancias mediante el denominado sistema de consultas de COOL. Este sistema consta de seis funciones que pueden ser usadas para efectuar la correspondencia de patrones en un conjunto de instancias (instance-set) y realizar las acciones pertinentes.

Conjuntos de instancias


Un conjunto de instancias es una colección ordenada de instancias, por ejemplo:

[hombre1] [hombre2] [hombre3]

es un conjunto de instancias,

[hombre4] [hombre1] [hombre2]

es otro conjunto de instancias,

[hombre4] [hombre4] [hombre4]

es otro más.

En los conjuntos de instancias, las posiciones de los elementos o miembros del conjunto de instancias importa, por lo que los conjuntos de instancias:

[hombre1] [hombre2] [hombre3]

y

[hombre3] [hombre1] [hombre2]

son diferentes.

La creación de los conjuntos de instancias es automática, haciéndose en base a las especificaciones del usuario que se reflejan en plantillas de conjuntos de instancias.

Las instancias que aparecen en el conjunto de instancias corresponden a las que el usuario ha indicado en las restricciones de clase que aparecen en las plantillas de conjuntos de instancias.

Por ejemplo, la plantilla:

((?ins1 HOMBRE) (?ins2 MUJER))

define un conjunto de instancias formado por dos instancias, la primera de las cuales debe ser de la clase HOMBRE y la segunda de la clase MUJER.

Las variables ?ins1 e ?ins2 se denominan variables de miembros de conjuntos de instancias y se asocian a nombres de instancias en el proceso de creación de los conjuntos. Por todo esto se puede concluir que una plantilla de conjunto de instancias está formada por pares variable – restricciones de clase. Las restricciones de clase pueden constar de una o más nombres de clases, también sería válido:

((?ins1 HOMBRE MUJER) (?ins2 MUJER))

Si se tiene la siguiente descripción de clases e instancias:

(defclass PERSONA (is-a USER)
            (role abstract)
             (slot sexo (access read-only)
                        (storage shared))
            (slot age (type NUMBER)
                         (visibility public)))

(defmessage-handler PERSONA put-edad (?valor)
            (dynamic-put edad ?valor))


(defclass HEMBRA (is-a PERSONA)
            (role abstract)
             (slot sexo (source composite)
                        (default hembra)))

(defclass VARON (is-a PERSONA)
            (role abstract)
             (slot sexo (source composite)
                        (default varon)))

(defclass CHICA (is-a HEMBRA)
            (role concrete)
            (slot edad (source composite)
                        (default 4)
                        (range 0 17)))

(defclass MUJER (is-a HEMBRA)
            (role concrete)
            (slot edad (source composite)
                        (default 25)
                        (range 18 100)))

(defclass CHICO (is-a VARON)
            (role concrete)
            (slot edad (source composite)
                        (default 4)
                        (range 0 17)))

(defclass HOMBRE (is-a VARON)
            (role concrete)
            (slot edad (source composite)
                        (default 25)
                        (range 18 100)))

(definstances GENTE
            (hombre1 of HOMBRE (edad 18))
            (hombre2 of HOMBRE (edad 60))
            (mujer1 of MUJER (edad 18))
            (mujer2 of MUJER (edad 60))
            (mujer3 of MUJER)
            (chico1 of CHICO (edad 8))
            (chico2 of CHICO)
            (chico3 of CHICO)
            (chico4 of CHICO)
            (chica1 of CHICA (edad 8))
            (chica2 of CHICA))

En este caso, la definición de una plantilla de conjunto de instancias como la que sigue:

((?hombre_chico CHICO HOMBRE) (?mujer_chica CHICA MUJER))

que también podría haberse escrito:

((?hombre_chico VARON) (?mujer_chica HEMBRA))

daría lugar a la generación de treinta conjuntos de instancias:

1.      [chico1] [chica1]
2.      [chico1] [chica2]
3.      [chico1] [mujer1] 4. [chico1] [mujer2]
5.      [chico1] [mujer3]
6.      [chico2] [chica1]
7.      [chico2] [chica2]
8.      [chico2] [mujer1]
9.      [chico2] [mujer2]
10.  [chico2] [mujer3]
11.  [chico3] [chica1]
12.  [chico3] [chica2]
13.  [chico3] [mujer1] 14. [chico3] [mujer2]
15.  [chico3] [mujer3]
16.  [chico4] [chica1]
17.  [chico4] [chica2]
18.  [chico4] [mujer1] 19. [chico4] [mujer2]
20.  [chico4] [mujer3]
21.  [hombre1] [chica1]
22.  [hombre1] [chica2]
23.  [hombre1] [mujer1] 24. [hombre1] [mujer2]
25.  [hombre1] [mujer3]
26.  [hombre2] [chica1]
27.  [hombre2] [chica2]
28.  [hombre2] [mujer1] 29. [hombre2] [mujer2]
30. [hombre2] [mujer3]

Definición de consultas


Una consulta es una expresión booleana definida por el usuario que se aplica a un conjunto de instancias para determinar si este conjunto de instancias cumple las restricciones establecidas. Si la evaluación de esta expresión para un conjunto de instancias es cualquier cosa que no sea FALSE, se dice que el conjunto de instancias satisface la consulta.

Dentro de una consulta, los slot de los elementos de un conjunto de instancias pueden ser accedidos sin necesidad de pasar un mensaje al objeto correspondiente, sino separando simplemente el nombre de la variable de instancia del nombre de slot:

<nombre-variable-de-conjunto-de-instancias>:<nombre-slot>



Acciones distribuidas


Una acción distribuida es una expresión definida por el usuario que se evalúa para cada conjunto de instancias que satisface una consulta. Para ejecutar más de una acción, se debe usar la función  progn para agruparlas.

Las variables de miembro de conjunto de instancias sólo son válidas dentro de la función de consulta.

Funciones de consulta


Existen seis funciones. Para un conjunto de instancias dado, las seis funciones iterarán sobre estas instancias en el mismo orden.

1.- Función any-instancep


Esta función aplica una consulta a cada conjunto de instancias que empareja con la plantilla. Si un conjunto de instancias satisface la consulta, la función termina (se busca sólo que haya algún conjunto de instancias que cumpla la consulta) y se devuelve el valor TRUE. De otra forma, se devuelve FALSE.

La sintaxis es:

(any-instancep <plantilla-de-conjunto-de-instancias> <consulta>)

Ejemplo:

(any-instancep ((?hombre HOMBRE)) (> ?hombre:edad 30))

2.- Función find-instance


Esta función aplica una consulta a cada conjunto de instancias que cumple con la especificación de la plantilla. Si un conjunto de instancias satisface la consulta, entonces la función termina y se devuelve el conjunto de instancias en  un campo multivalor. Si no se satisface la consulta, se devuelve un campo multivalor de longitud cero.

Ejemplo: Para encontrar el primer par de hombre y mujer con la misma edad:

(find-instance ((?h HOMBRE) (?m MUJER)) (=  ?h:edad  ?m:edad))

3.- Función find-all-instances


Esta función aplica una consulta a cada conjunto de instancias que empareja con la plantilla. Cada conjunto de instancias que satisface la consulta se guarda en un campo multivalor, por lo que al final se devuelve un campo multivalor en el que aparecen todos los nombres de todas las instancias de todos los conjuntos de instancias que satisfacen la consulta.

Por ejemplo, para encontrar todos los pares hombre-mujer que tienen la misma edad:

(find-all-instances ((?h HOMBRE) (?m MUJER)) (=   ?h:edad   ?m:edad))

4.- Función do-for-instance


La función aplica una consulta a cada conjunto de instancias que emparejan con la plantilla. Si un conjunto de instancias satisface la consulta, la acción distribuida especificada se ejecuta y la función termina. El valor de retorno es la evaluación de la acción. Si ningún conjunto de instancias satisface la consulta, el valor de retorno es FALSE.

Por ejemplo, para imprimir por pantalla la tripleta de gente que tiene la misma edad.

(do-for-instance ((?p1 PERSONA) (?p2 PERSONA))
            (and (=   ?p1:edad   ?p2:edad   ?p3:edad)
                        (neq ?p1  ?p2)
                        (neq ?p1  ?p3)
                        (neq ?p2  ?p3))
            (printout t ?p1 “ “ ?p2  “ “ ?p3  crlf))

¿Por qué se usan las llamadas a “neq”?

5.- Función do-for-all-instances


En este caso, la acción se ejecuta tantas veces como conjuntos de instancias satisfagan la consulta. El valor devuelto es la evaluación de la acción para el último conjunto de instancias que satisface la consulta. Si ningún conjunto de instancias satisface la consulta, el valor devuelto es FALSE.

6.- Función delayed-do-for-all-instances


En este caso se emplea un almacenamiento intermedio de los conjuntos de instancias, empezando a ejecutarse las acciones cuando todos los conjuntos que satisfacen la consulta están localizados.