Metaprogramación Ingeniería Informática Departamento de Lenguajes y Ciencias de la Computación Universidad de Málaga Contenido 1. Clasificación de términos 2. Inspección de estructuras Metaprogramación 2 Clasificación de términos Clasificación de términos Prolog Los términos Prolog se clasifican en: átomo constante entero número real término variable estructura Metaprogramación 4 Metapredicados de clasificación Para cada subclase de término, Prolog predefine un metapredicado unario que nos dice si su argumento pertenece a esa subclase: atom(+T) – T es integer(+T) – T es float(+T) – T es number(+T) – T es atomic(+T) – T es var(+T) – T es compound(+T)– T es un átomo un entero un flotante un número entero o flotante una constante una variable libre una estructura Todos son tests y tienen éxito si sólo si su argumento pertenece a la subclase a la que se refieren Metaprogramación 5 Ejemplos de clasificación de términos ?- atom(vacio). Yes ?- integer(-3). Yes ?- var(X). Yes ?- compound([a,b|Xs]). Yes ?- compound(-3.14). No ?- integer([1]). No Metaprogramación 6 Clasificación de las variables instanciadas Se clasifican respecto al término al que están instanciadas: ?- X = 2, var(X). No ?- X = 2, integer(X). Yes ?- var(X), X = 2. X = 2 ?- var([X]). No ?- compound([X]). Yes Metaprogramación 7 El metapredicado nonvar/1 Para facilitar metapredicado: el trabajo con variables se define el nonvar(+T) – T no es una variable libre Ejemplos: ?- nonvar(X). No ?- X = a, nonvar(X). Yes ?- nonvar(vacio). Yes Metaprogramación 8 Aplicaciones de la clasificación de términos En general, estos metapredicados sirven para escribir predicados cuyo comportamiento dependa del tipo de término que reciben En particular, pueden emplearse para: comprobar precondiciones diagnosticar errores de tipo extender usos posibles mejorar la eficiencia distinguir casos según el tipo Veremos algunos ejemplos Metaprogramación 9 Ejemplo: el predicado suma/3 Consideremos el predicado suma/3: suma(X,Y,Z) :Z is X + Y. El siguiente objetivo da lugar a un error de ejecución: ?- suma(2,a,Z). ERROR: is/2: Arithmetic: `a/0' is not a function ^ Exception: (8) _G256 is 2+a ? ¿Cómo podríamos evitarlo? Metaprogramación 10 Comprobar precondiciones Podemos comprobar el tipo de los argumentos: suma_precond(X,Y,Z) :% precondición number(X), number(Y), % precondición Z is X + Y. ?- suma_pre(2,a,Z). No Evitamos el error, pero no se distingue de un fracaso: ?- suma_pre(2,3,7). No ¿Cómo podemos informar al usuario del error de tipo? Metaprogramación 11 Diagnosticar errores de tipo (I) tipo(+T,?Tipo) - el término T es de tipo Tipo tipo(T,atomo) :- atom(T). tipo(T,entero) :- integer(T). tipo(T,flotante) :- float(T). tipo(T,variable) :- var(T). tipo(T,estructura) :- compound(T). Ejemplos: ?- tipo(2,X). X = entero ?- tipo([a,b,c],X). X = estructura Metaprogramación 12 Diagnosticar errores de tipo (II) La suma sólo se realiza si todos los tipos son correctos: suma_tipada(X,Y,Z) :- % tipos correctos tipo(X,TX), esta(TX,[entero, flotante]), tipo(Y,TY), esta(TY,[entero, flotante]), tipo(Z,TZ), esta(TZ,[entero,flotante,variable]), Z is X+Y. Los otros casos detectan y diagnostican errores de tipo Metaprogramación 13 Diagnosticar errores de tipo (III) Una cláusula por cada parámetro: suma_tipada(X,_,_) :- % error de tipo en X tipo(X,TX), no_esta(TX, [entero, flotante]), write('suma(X,Y,Z):’), write(‘X debe ser numerico y es '), write(TX), nl. Ejercicio: completa la definición comprobando los tipos de Y y Z Metaprogramación 14 Diagnosticar errores de tipo (y IV) ?- suma_tipada(2,3,X). X = 5 ?- suma_tipada(2,3,7). No ?- suma_tipada(2,3,x). suma(X,Y,Z): Z debe ser num o var y es atomo ?- suma_tipada(A,B,5). suma(X,Y,Z): X debe ser numerico y es variable suma(X,Y,Z): Y debe ser numerico y es variable El último objetivo muestra que estamos perdiendo usos. ¿Cómo podríamos recuperar usos alternativos? Metaprogramación 15 Extender usos posibles (I) Una cláusula por cada uno de los usos posibles: suma_ex(X,Y,Z) :% usos: (+,+,+) y (+,+,-) number(X), number(Y), tipo(Z,TZ), esta(TZ, [entero, flotante, variable]) Z is X+Y. Metaprogramación 16 Extender usos posibles (y II) suma_ex(X,Y,Z) :number(X), var(Y), number(Z), Y is Z-X. suma_ex(X,Y,Z) :var(X), number(Y), number(Z), X is Z-Y. % uso: (+,-,+) % uso: (-,+,+) Ejercicio: completa los otros usos de suma_ex/3 Metaprogramación 17 Mejorar la eficiencia (I) abuelo(A,N) - A es abuelo (paterno o materno) de N abuelo(A,N) :padre(A,P), progenitor(P,N). Esta definición es muy ineficiente en el uso (-,+): ?- abuelo(A,heidi). ¿Cómo podemos mejorar la eficiencia? Metaprogramación 18 Mejorar la eficiencia (y II) Definimos cláusulas específicas según el uso: abuelo(A,N) :% uso: (-,+) var(A), atom(N), progenitor(P,N), padre(A,P). abuelo(A,N) :% uso: (+,+), (+,-), (-,-) tipo(A,TA), tipo(N,TN), (TA,TN) \== (variable, constante), padre(A,P), progenitor(P,N). Metaprogramación 19 Distinguir casos según el tipo (I) aplana(+Xss,?Ys) Xss lista de átomos y de listas de átomos arbitrariamente anidadas, Ys lista de los átomos que aparecen en Xss Ejemplo: ?- aplana([a,[b,c],[[d,[e]],[],f],g],A). A = [a,b,c,d,e,f,g]; No Metaprogramación 20 Distinguir casos según el tipo (II) aplana([],[]). % caso base aplana([X|Xss],Ys) :- % caso recursivo … Lo que haya que hacer depende del tipo de X X es un átomo X es una lista Metaprogramación 21 Distinguir casos según el tipo (y III) aplana([],[]). % caso base aplana([X|Xss],[X|Ys]) :- % caso recursivo atom(X), X \== [], aplana(Xss,Ys). aplana([Xs|Xss],Ys) :% caso recursivo es_lista(Xs), aplana(Xs,As), aplana(Xss,Bs), concatena(As,Bs,Ys). es_lista([]). es_lista([_|_]). Metaprogramación 22 Ejercicios 1. Define un predicado super_suma/3 que combine todas las técnicas (tipado, diagnóstico de errores, usos múltiples) 2. Define un predicado longitud/2 que funcione correctamente en todos los usos posibles. 3. Define una versión recursiva de cola de aplana/2. Compara el coste con la versión original. 4. Define un predicado aplanax/2 que aplane una lista de términos arbitrarios Metaprogramación 23 Inspección de estructuras Inspección de estructuras (I) Los siguientes objetivos deberían tener éxito: ?- suma_tipada(2+3*5,1,Z). suma(X,Y,Z): X debe ser numérico y es estructura ?- suma_tipada(3,2*5,Z). suma(X,Y,Z): Y debe ser numérico y es estructura ?- suma_tipada(2*3,4*5,Z). suma(X,Y,Z): X debe ser numérico y es estructura suma(X,Y,Z): Y debe ser numérico y es estructura Debemos aceptar la estructura como tipo de X, Y y Z Metaprogramación 25 Inspección de estructuras (II) suma_estruc(X,Y,Z) :- % uso (+,+,?) tipo(X,TX), esta(TX,[entero, flotante, estructura]), tipo(Y,TY), esta(TY,[entero, flotante, estructura]), tipo(Z,TZ), esta(TZ,[entero,flotante,estructura,variable]), Z is X+Y. ¿Es correcto? Metaprogramación 26 Inspección de estructuras (III) ¡No! ?- suma_estruc(1+2*3,4,Z). Z = 11 ?- suma_estruc(3+2*a,6,Z). ERROR: is/2: Arithmetic: `a/0' is not a function ^ Exception: (8) _G328 is 3+2*a+6 ? una estructura no es siempre una expresión aritmética válida compound/1 no es suficiente ¿Qué necesitamos? Metaprogramación 27 Inspección de estructuras (y IV) Debemos inspeccionar X, Y y Z recursivamente + ?- suma_estruc(1+2*3,4,Z). Z = 11 1 * 2 3 ?- suma_estruc(1+2*a,4,Z). suma(X,Y,Z): X no es una expresión aritmética + 1 * 2 Metaprogramación a 28 Metapredicados de inspección de estructuras Los metapredicados de inspección de estructuras permiten: descomponer una estructura en sus componentes componer una estructura a partir de sus componentes Prolog predefine 3 metapredicados de inspección: =../2 functor/3 arg/3 Metaprogramación 29 El metapredicado =../2 X =.. Y X es cualquier término Prolog Y es una lista cuya cabeza es el átomo del functor principal de X y cuyo resto está formado por los argumentos de X X =.. Y se lee X univ Y Soporta los usos (+,+), (-,+) y (+,-) Cuidado: el uso (-,-) genera un error: ?- A =.. B. ERROR: Arguments are not sufficiently instantiated Metaprogramación 30 Comprobando términos con =../2 En el uso (+,+) se comporta como un test ?- f(a, X, g(b,Y)) =.. [f, a, X , g(b,Y) ]. Yes argumentos átomo del functor principal ?- [a,b,c] =.. [‘.’, a, [b,c]]. Yes ? 2+3*5-1 =.. [‘-’,2+3*5,1]. Yes ?- a =.. [a]. Yes Metaprogramación 31 Descomponiendo términos con =../2 En el uso (+,-) se comporta como un generador único Se utiliza para descomponer un término en sus componentes ?Xs ?Xs ?Xs ?Xs ?Xs punto(2,3) =.. Xs. = [punto, 2, 3] [A,f(X),Y] =.. Xs. = ['.', A, [f(X), Y]] ; sin(X)*cos(X) + 3.14 =.. Xs. = [+, sin(X)*cos(X), 3.14] ; 6 =.. Xs. = [6] [] =.. Xs. = [[]] Metaprogramación 32 Componiendo términos con =../2 En el uso (-,+) se comporta como un generador único Se utiliza para componer un término a partir de sus componentes ?- T =.. ['+',a+b+c,d]. T = a+b+c+d ?- T =.. [arco,ej1,i,q3,q5]. T = arco(ej1, i, q3, q5) ?- T =.. ['.', p,[a,k]]. T = [p, a, k] ?- T =.. [fin]. T = fin Metaprogramación 33 Metaprogramación con =../2 El metapredicado =../2 nos permite definir predicados que procesen términos arbitrarios (tal como hacen algunos predicados predefinidos, e.g. write/1, =/1, @</2,…) Típicamente, en los predicados que procesan términos arbitrarios distinguimos: casos base: constantes y variables caso recursivo: estructura a menudo emplearemos recursión mutua Esta capacidad nos servirá para implementar en Prolog el polimorfismo estructural Metaprogramación 34 Escalando figuras geométricas (I) Representamos figuras geométricas planas por los términos: Figura =:: | | | cuadrado(lado) rectangulo(anchura, altura) triangulo(lado1, lado2, lado3) circulo(radio) donde los argumentos de las distintas figuras son números que indican sus dimensiones Ejemplos: Metaprogramación rectangulo(2,6) circulo(3.14159) 35 Escalando figuras geométricas (II) Define un predicado escala(+F,+K,?KF) que multiplique cada dimensión de la figura F por el factor K, obteniendo la figura escalada KF. Ejemplo: ?- escala(rectangulo(3,5), 2, R). R = rectangulo(6,10) ?- escala(circulo(6.5),0.5,C). C = circulo(3.25) En principio, necesitamos 4 reglas, una por cada tipo de figura ¿Es posible escribir un predicado escala/3 genérico? Metaprogramación 36 Escalando figuras geométricas (y III) escala(Fig,K,KFig) :Fig =.. [Tipo|Ds], multiplica(K,Ds,KDs), KFig =.. [Tipo|KDs]. % descomponer figura % componer figura multiplica(_,[],[]). multiplica(K,[D|Ds],[KD|KDs]) :KD is K*D, multiplica(K,Ds,KDs). Funciona para cualquiera de las figuras anteriores Ejercicio: ¿Qué tipo de términos pueden usarse con escala/3? Metaprogramación 37 Detectando términos básicos es_basico(+T) – T es un término básico (i.e. no tiene variables) es_basico(T) :atomic(T). es_basico(T) :compound(T), T = [_|Args], args_basicos(Args). args_basicos([]). args_basicos([A|As]) :es_basico(A), args_basicos(As). Metaprogramación % caso base % caso recursivo % argumentos % recursión mutua % recursión mutua 38 Subtérminos de un término subtérmino(?S,+T) – S es subtérmino del término básico T subtermino(T,T). subtermino(S,T) :compound(T), T = [_|Args], miembro(A,Args), subtermino(S,A). ¿Por qué es importante que T sea básico? Ejercicio: añade el test para comprobar que T es básico Metaprogramación 39 El metapredicado functor/3 functor(T,F,N) T es cualquier término Prolog F es el átomo del functor principal de T F es la aridad del functor principal de T Soporta los usos (+,+,+), (+,-,-) y (-,+,+) Los usos (+,+,-) y (+,-,+) son posibles, pero poco útiles Cuidado: el resto de usos genera un error: ?- functor(T,F,3). ERROR: Arguments are not sufficiently instantiated Metaprogramación 40 Comprobando functores principales En el uso (+,+,+) se comporta como un test ?- functor(t(X,a),t,2). Yes ?- functor(2+3*5-1,’-’,2). Yes ?- functor(a,a,0). Yes ?- functor([x,y],’.’,2). Yes Metaprogramación 41 Obteniendo el functor principal En el uso (+,-,-) se comporta como un generador único Se utiliza para obtener el functor principal de un término ?- functor(punto(a,X),F,N). F = punto N = 2 ?- functor([A,f(X),Y],F,N). F = ‘.’ N = 2 ?- functor([],F,N) F = [] N = 0 Metaprogramación 42 Generando plantillas con functor/3 En el uso (-,+,+) se comporta como un generador único Se utiliza para generar una plantilla de estructura ?- functor(T,punto,2). T = punto(_,_) ?- functor(T,’+’,2). T = _ + _ ?- functor(T,’+’,4). T = ‘+’(_,_,_,_) ?- functor(T,a,0). T = a La utilidad de functor/3 se aprecia al combinarlo con arg/3 Metaprogramación 43 El metapredicado arg/3 arg(+I,?T,?A) I es un entero positivo T es una estructura A unifica con el argumento I-ésimo de la estructura T Los argumentos de numeran a partir de 1, de izquierda a derecha Soporta los usos (+,+,+), (+,+,-) y (+,-,+) Cuidado: el resto de usos genera un error: ?- arg(3,T,A). ERROR: Arguments are not sufficiently instantiated Metaprogramación 44 Accediendo a argumentos por su posición En el uso (+,+,-) se comporta como un generador único Se utiliza para obtener el argumento i-ésimo de una estructura ?- arg(3,arco(i,q2,q0),A). A = q0 ?- arg(1,[a,b,c,d],A). A = a ?- arg(2,[a,b,c,d],A). A = [b, c, d] ?- arg(3,[a,b,c,d],A). % fuera de rango No Metaprogramación 45 Instanciando argumentos por su posición En el uso (+,-,+) se comporta como un generador único Se utiliza para instanciar el argumento i-ésimo de una estructura ?- arg(2,arco(i,Q,q0),q5). Q = q5 ?- arg(1,[X,b,c,d],a). X = a Importante: aunque esté en modo -, el segundo argumento de arg/3 es una estructura Podemos metaprogramar combinando functor/3 y arg/3 Metaprogramación 46 Sustitución de términos (I) sustituye(+X,+Y,+TX,?TY) – sustituye las apariciones de X por Y en TX, obteniendo TY ?- sustituye(a,b,f(a,c),T). T = f(b, c) ?- sustituye(2,-1,[1,2,3,2,5],T). T = [1, -1, 3, -1, 5] ?- sustituye(X,Y,p(X,[A,B,[C,X]],q(2+t(x,X))),T). T = p(Y, [A, B, [C, Y]], q(2+t(x, Y))) Metaprogramación 47 Sustitución de términos (II) sustituye(X,Y,TX,Y) :TX == X. sustituye(X,_,TX,TX) :atomic(TX), TX \== X. sustituye(X,_,TX,TX) :var(TX), TX \== X. sustituye(X,Y,TX,TY) :compound(TX), TX \== X, functor(TX,F,N), % (+,-,-) functor(TY,F,N), % (-,+,+) sust_args(N,X,Y,TX,TY). Metaprogramación 48 Sustitución de términos (y III) Un bucle dirigido por el contador para aplicar la sustitución a cada argumento: sust_args(0,_,_,_,_). sust_args(I,X,Y,TX,TY) :I > 0, arg(I,TX,TXi), % (+,+,-) arg(I,TY,TYi), % (+,+,-) sustituye(X,Y,TXi,TYi), % rec. mutua I1 is I-1, sust_args(I1,X,Y,TX,TY). Metaprogramación 49 Ejercicios (I) Define los siguientes metapredicados: 1. reemplaza(+T,+I,+X,?TX) reemplaza el argumento I-ésimo de T por X, obteniendo TX ?- reemplaza(2,f(a,b,c),w,T). T = f(a,w,c). 2. atomos(T,A) A es un átomo del término T ?- atomos(f(a,g(b,1)),A). A = a ; A = b ; No Metaprogramación 50 Ejercicios (y II) 3. lista_atomos(T,As) A es la lista de átomos del término T ?- lista_atomos(f(a,g(b,1)),As). As = [a, b] ; No Metaprogramación 51