0% encontró este documento útil (0 votos)
77 vistas

Programación Funcional

Este documento resume el modelo de programación funcional. Describe conceptos como tipos de datos, funciones, listas, árboles y evaluación perezosa. Compara la programación funcional con la imperativa y explica conceptos como funciones de orden superior, lambdas y pattern matching. Incluye ejemplos de funciones recursivas y uso de listas en Haskell.

Cargado por

César Ces
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
77 vistas

Programación Funcional

Este documento resume el modelo de programación funcional. Describe conceptos como tipos de datos, funciones, listas, árboles y evaluación perezosa. Compara la programación funcional con la imperativa y explica conceptos como funciones de orden superior, lambdas y pattern matching. Incluye ejemplos de funciones recursivas y uso de listas en Haskell.

Cargado por

César Ces
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 201

Modelo de programación funcional

• Introducción al modelo de programación funcional

• El tipo de datos.

• Funciones.

• Intervalos.

• Operadores.

• Aplicaciones de las listas.

• Árboles.

• Evaluación perezosa
Evaluación de expresiones

Declarativo Imperativo

• Control del programador es nulo en la secuencia • El programador tiene control sobre la


del programa secuencia del programa

• Se enfoca en el qué • Se enfoca en el cómo

• Lista como estructura fundamental • Se fija en como realizar operaciones


ayudándose de patones de control de flujo
• No hay asignación destructiva
• Asignación destructiva
• Solo existen valores y expresiones matemáticas que
generan nuevos valores desde los ya declarados • Presenta efectos colaterales

• El control no es responsabilidad del programador • El control por parte del programador es


excesivo
Ejemplo del cálculo del factorial de un número
• Programación imperativa.
unsigned factorial (unsigned n)
{ int product = 1; // valor inicial
while (n > 1)
{
product *= n--; // acumulador
}
return product; // resultado
}

• Programación funcional.

Factorial : : int -> int


factorial 0 = 1
factorial n = n * factorial (n-1)
Modelo de Von Neumann vs Alonzo Church
•Seleccionar el primer elemento de una lista no vacía:
head [1,2,3,4,5]

Ejercicios con Haskell

• Liga www.haskell.org

• Primer ejercicio “Hola, Haskell”.

• Seleccionar el primer elemento de una lista


head [1,2,3,4,5]

• Eliminar el primer elemento de una lista


tail [1,2,3,4,5]

• Eliminar el primer elemento de una lista


take 3 [1,2,3,4,5]

• Eliminar los n primeros elementos de una lista


drop 3 [1,2,3,4,5]
•Seleccionar el primer elemento de una lista no vacía:
head [1,2,3,4,5]

Ejercicios con Haskell

• Calcular la longitud de la lista


length [1,2,3,4,5]

• Calcular la suma de una lista


sum [1,2,3,4,5]

• Calcular el producto de la lista


product [1,2,3,4,5]

• Concatenar dos listas


[1,2,3,4,5] ++ [6,7]

• Invertir una lista


reverse [1,2,3,4,5]
Funciones
• La función en este tipo de lenguajes puede tomar funciones como
parámetros y devolver funciones como resultado.

• Una función que realiza ambas o alguna de ellas se llama función de


orden superior.
head [1,2,3,4,5]

• applyDoble (+3) 10, el resultado = 16


• applyDoble (++ “Erika”) “Hola”, el resultado “Hola Erika Erika”
• applyDoble (“Erika” ++) “Hola”, el resultado “Erika Erika Hola”

• Las lambdas son funciones anónimas que suelen ser usadas cuando
se necesitan una sola vez, ejemplo: (\x -> x + x) 2. \parámetros
-> el cuerpo de la función entre paréntesis. Pueden recibir
más de un parámetro.
(\x y z -> x * 2 - y + 10 * z)

• Pattern Matching let (_,(a:_)) = (10,”abc”) in a


Funciones

• Función con un único parámetro, ejemplo la función not recibe un


booleano y devuelve otro booleano.
: t not
not :: Bool -> Bool

• Funciones con más de un parámetro, éstas se llaman currificadas.


: t (&&)
(&&) :: Bool -> Bool-> Bool

• Función error predefinida cuyo tipo es string -> a, es útil si se


quiere terminar un programa cuando hay un error.
Intervalos en programación funcional

• Las funciones devuelven siempre el mismo valor


Cuando se le llama con los mismos parámetros.
No modifican ningún estado, no acceden a ninguna variable, ni objeto.

• Diferencia entre declaración y modificación de variables


En programación pura una vez declarada una variable no puede modificar su valor
En programación imperativa es habitual modificar el valor de una variable en
distintos puntos del código
Intervalos en programación funcional

• Las funciones devuelven siempre el mismo valor


Cuando se le llama con los mismos parámetros.
No modifican ningún estado, no acceden a ninguna variable, ni objeto.

• Diferencia entre declaración y modificación de variables


En programación pura una vez declarada una variable no puede modificar su valor
En programación imperativa es habitual modificar el valor de una variable en
distintos puntos del código
Fibonacci

• Fibo 0 = 0.
• Fibo 1 = 1.
• Fibo n = (fibo(n-1) + fibo(n-2))

Fibo 2 = 1 + 0 = 1
Fibo 3 = 1 + 1 = 2
Fibo 4 = 2 + 1 = 3
Fibo 5 = 3 + 2 = 5
Asignación no destructiva

• Se puede evaluar una expresión en el orden que se desee y se


obtiene el mismo resultado.

• masuno x = x + 1
• masuno (3*2) da el mismo resultado (2*3)

• Esta propiedad no se cumple en la mayoría de los lenguajes


imperativos
• n + (n := 1) si empezamos a evaluar de izquierda a derecha 0 + (n := 1) da 0 +1
• si se empieza de izquierda a derecha n + 1 da 1 + 1 = 2
• Si se evalúa la expresión con distintas estrategias se obtienen resultados diferentes es
una asignación “destructiva”
Evaluación perezosa

• El incremento en el rendimiento al evitar cálculos innecesarios, y en tratar


condiciones de error al evaluar expresiones compuestas.

• La capacidad de construir estructuras de datos potencialmente infinitas.

• La capacidad de definir estructuras de control como abstracciones, en


lugar de operaciones primitivas.

• Puede también reducir el consumo de memoria de una aplicación, ya que


los valores se crean sólo cuando se necesitan.

• Lo contrario es la evaluación acaparadora o voraz, que es el método por


defecto de la mayoría de los lenguajes imperativos.

• Un contra es que la evaluación perezosa puede conducir a fragmentar la


memoria
Listas

• Calcula los cuadrados de los números pares entre 1 y 20.


• cpares20 :: [Integer]
• cpares20 = [x ^ 2 | x <- [1..20], x ‘mod‘ 2 == 0]

• Divisores de un número
• divisores :: Integer -> [Integer]
• divisores n = [d | d <- [1..n], n ‘mod‘ d == 0]

• Función de ruffini
• ruffini :: Float -> [Float] -> [Float]
• ruffini a = scanl1 (\r x -> r * a + x)
• ruffini 3 [2,4,3] resultado [2,10,33]

• Función decimal a binaria


• d2b :: Integer -> [Integer]
• d2b = reverse . unfold (==0) (‘mod‘ 2) (‘div‘ 2)
• d2b 13 resultado [1,1,0,1]
Listas

• Calcula los cuadrados de los números pares entre 1 y 20.


• dobla_lista = [2*x |x <- [0 .. 10]]
• print dobla_lista
Listas

• Calcula los cuadrados de los números pares entre 1 y 20.


• dobla_lista = [2*x |x <- [0 .. 10]]
• print dobla_lista

+ +AB

A B
Recorridos del árbol

10 22 15 12 35 52 33

15 22 12 10 35 33 52 +

35 33 52 12 15 22 10
A B
+AB

B A+

A+B
10,22,15,12,35,52,33

15,12,22,35,33,52,10
Programar en funcional es pensar en funciones. Sin embargo, no
sólo es lo anterior, cualquier lenguaje de programación dentro de
este paradigma se caracteriza por un comportamiento acerca de
como evalúa las construcciones.

La Evaluación impaciente evalúa cada expresión en el momento


exacto del tiempo en que ésta es encontrada dentro del código
fuente. La evaluación perezosa, por el contrario, posterga la
evaluación de la expresión hasta que su valor es realmente
demandado por el programa en ejecución.
•Codificación. La evaluación perezosa permite alcanzar un modelo de
programación defensivo basado en aserciones [+] que resulta generalmente más
sinóptico y sencillo de leer y entender. En lugar de comprobar las precondiciones
de una operación a base de un abuso de sentencias y expresiones condicionales
se utilizan expresiones lógicas como prefijo de las operaciones a realizar para que
operen de guarda que dispara la ejecución de la operación a la derecha sólo si se
satisface cierta condición ambiental. En el código (A) del Listado 1 pueden verse
algunos ejemplos de este uso canónico de construcciones.
Recursión. De forma similar al caso anterior, los esquemas de diseño funcional
recursivo también se prestan mucho a hacer uso de este tipo de
construcciones. En el código (B) del Listado 1 aparece un ejemplo de función
de búsqueda recursiva que determina si un elemento x se encuentra dentro
de un vector xs. Tal función consiste en una sola expresión que indica que el
vector contendrá al elemento, si éste se encuentra en la posición de análisis
en curso p o está en alguna posición posterior a este índice, comenzando
desde el 0. Y todo ello siempre que p no supere la longitud del vector. En este
caso, la evaluación perezosa nos servirá para que se pare la recursión cuando
esta última condición sobre p deje de ser cierta o el elemento se haya
encontrado
•Rendimiento. La evaluación perezosa también puede ser relevante en temas de
rendimiento. El código (C) del Listado 1 muestra una función similar a la anterior,
pero en este caso aplicando búsqueda binaria. Básicamente, en cada iteración
recursiva, el vector se parte - de forma imaginaria - en 2 subvectores iguales con
ayuda de dos índices posicionales p y q. Buscar el elemento x consiste en recurrir
con la búsqueda en cada subvector. El proceso terminará cuando se llegue a alguna
situación unitaria donde py q coincidan y pueda comprobarse que en ese punto
reside el elemento buscado. La ventaja de la evaluación perezosa en este escenario
consiste en que todo el proceso de encadenamiento compositivo por operaciones
de disyunción lógica se para automáticamente en cuanto se encuentra la primera
coincidencia ahorrando muchos ciclos de cómputo. Recuerde que el modelo de
ejecución es secuencial y que las operación has no se lanzan en paralelo.
Referencias

• Gustavo Ramiro Rivadera, cuadernos de la facultad n 3, 2008.


• https://javiervelezreyes.com/las-3-evaluaciones-de-la-
programacion-funcional/
Programación Lógica
• Lógica proposicional

• Lógica de predicados

• Inferencia en lógica

• Representación de conocimiento en lógica


Programación Lógica

La programación lógica es una variedad de lo hemos aprendido


de programación declarativa.

Este paradigma se basa en la fórmula "algoritmos = lógica +


control" (la llamada Ecuación Informal de Kowalski), lo que
significa que un algoritmo se crea especificando conocimiento
mediante axiomas (lógica) y el problema se resuelve mediante
un mecanismo de inferencia que actúa sobre el mismo (control).

Entre los lenguajes de programación lógica se destacan Prolog,


Lisp o Erlang.

Los ámbitos de la computación donde más se usa Prolog son


los de la inteligencia artificial y aspectos relacionados con la
misma como el machine learning, procesamiento de lenguaje
natural, construcción de sistemas expertos, etc.
Programación Lógica
Programación Lógica
La lógica de primer orden la realidad implica, objetos y relaciones entre ellos.
Cláusulas de Horn
Ejemplo:
Well formed formula
Modus tollens
El modo que, al negar, niega. Si A implica B, y B no es cierto, entonces A no es cierto

Modus ponens
El modo que, al afirmar, afirma. Si A implica B, y A es cierto, entonces B es cierto
La aridad de un operador matemático o de una función es el número de
argumentos necesarios para que dicho operador o función se pueda
calcular.
-
(A + B) - C
C
+
-+ABC

AB+C- A B
Análisis Lexicográfico
Funciones del analizador lexicográfico
Token y Lexema
Token y Lexema: Ejemplo
Palabras reservadas
Por ejemplo: int valor = 100;

int (palabra reservada) valor (identificador) = (operador) 100 (constante) ; (símbolo)

Regla de Coincidencia Mas Larga


Cuando el analizador léxico lee el código fuente, busca en el código letra por letra y
cuando encuentra un espacio en blanco, un operador, o algún smbolo especial
decide si una palabra esta completada.
Por qué se divide el análisis Léxico del Sintáctico
A % 20

Analizador léxico sencillo


C = 60 + 50 30 = 45 + 80
X1 = ( (Q * 9 ) – ( 3 + Y)) / ((T + R) *9 )
Generación de código: Árbol Sintáctico
=

+ a

3 5
Esquema de un compilador
Análisis: Se lee el programa fuente y se estudia la estructura y el significado del
mismo

Síntesis: se genera el programa objeto

Otros elementos: tabla de símbolos, rutinas de tratamiento de errores, etc.


Fases de análisis
 Análisis léxico

 Análisis sintáctico

 Análisis semántico
Fases de análisis
 Análisis léxico
 Identificar símbolos
 Eliminar separadores
 Eliminar comentarios
 Crear símbolos de entrada al análisis sintáctico (tokens)
 Determinar errores

 Análisis sintáctico

 Análisis semántico
Fases de análisis
 Análisis léxico

 Análisis sintáctico
 Comprobar que las sentencias que componen el texto fuente
son correctas en el lenguaje, creando una representación
interna que corresponde a la sentencia analizada.

 Análisis semántico
Fases de análisis
 Análisis léxico

 Análisis sintáctico

 Análisis semántico
 Se ocupa de analizar si la sentencia tiene algún significado.
Incluye el análisis de tipos, y determina sentencias que
carecen de sentido.
Análisis léxico en Haskell
 Reconocer expresiones regulares, que pueden ser reconocidas
por un autómata finito determinista (AFD).

 Implementación de los estados del AFD


f :: String -> (String, Token)

 Implementación de la transición A a B; la función fA llama a fB


después de leer un carácter y pasarle el resto a fB
Análisis léxico en Haskell
Análisis léxico en Haskell
Ejemplos
combinación de analizadores para conseguir uno más
complejo (parser combinator)

infixl7 &><
(&><) :: ReadS a -> ReadS b -> ReadS (a,b)
p1 &>< p2 = \s -> [ ((x1,x2),s2) | (x1,s1) <- p1
s,
(x2,s2) <- p2
s1 ]

MAIN> (rChar ‘a’ &>< rChar ‘b’) “abcd”


[((‘a’, ‘b’), “cd”)]
Análisis sintáctico en Haskell
En un lenguaje funcional como Haskell, es fácil traducir las reglas
gramaticales directamente a especificación funcional.

exp -> term rest exp = term <*> rest


rest -> + exp rest = token AddOp <*> exp <|> epsilon
| epsilon
Análisis sintáctico en Haskell

El paradigma funcional nos da una expresividad a la hora de representar reglas


gramaticales impensable en el paradigma imperativo.

Ejemplo: función many


many :: Parser a b -> Parser a [b]

exp = term <*> many (token addOp <*> term <@ f4) <@ f5
Análisis sintáctico en Haskell

Lo que hemos visto se refiere a análisis de arriba a abajo.

Realizar análisis de abajo a arriba es más complejo.

Happy es una herramienta que nos facilita la creación de un analizador


abajo a arriba.
Análisis semántico en Haskell

Atributos de los nodos del árbol:


Se usan para asignar un valor parcial a cada nodo del árbol,
para ir calculando, por ejemplo, los valores de una expresión
paso a paso.
Atributo sintetizado:
Para calcularlo, necesitamos calcular antes los atributos de
los sucesores.
Ejemplo: Inferencia de Tipos
Se corresponde a un recorrido de abajo a arriba.
Funciones de orden superior como foldTree son muy
útiles, y nos dan una sencillez y expresividad grandes.
Análisis semántico en Haskell
Análisis semántico en Haskell

Atributos heredados.
 Su valor ya está calculado, arriba o al mismo
nivel en el árbol.
 Se corresponden a un recorrido de arriba a
abajo.
 Se puede representar mediante una función
recursiva (posiblemente de cola), acumulando
los atributos.
 Veamos en el árbol anterior cuáles serían
atributos heredados.
Análisis semántico en Haskell
Analizadores monádicos

 Wadler, en 1995, introdujo el uso de las mónadas para


implementar analizadores.

 Usando el parser combinator &>< que hemos visto,


tenemos tuplas anidadas, engorrosas de manipular.

 La función monádica bind (>>=) junto con el uso de


lambda-abstracciones nos permite una notación más
manejable.

 Además, podemos usar otros combinadores monádicos.


Analizadores monádicos

Ejemplo: secuencia
• Como se ha visto en clase, algo bueno de las mónadas es que permiten
simular secuenciación al estilo imperativo:

aplica :: Analiz a -> String ->[(a, Estado)] MAIN> aplica


aplica (AN p) ent = p ent dosElementos “abcdca”

dosElementos::Analiz String [(“ab”, “cdca”)] ::


dosElementos=do [(String, String)]
a <- elemento
b <- elemento
return[a,b]
Analizadores monádicos

Mediante MonadPlus, podemos implementar el concepto


de alternancia. Mplus toma dos analizadores, y concatena
el resultado de ambos sobre la cadena entrada; mzero falla
siempre.

Instance MonadPlus analiz where


mplus (AN p)(AN q) = AN(\ent -> p ent ++ q ent)
mzero = AN (\ent -> [])
Analizadores monádicos
Tomando (!+) como sinónimo de mplus, podemos
construir lo siguiente: elemento !+
dosElementos, que captura un solo carácter, o
dos.
unoODosElementos = elemento !+ dosElementos
> aplica unoODosElementos "abcdca"
[("a","bcdca"),("ab","cdca")]

Otros filtros
(!>) ::Analiz a -> (a -> Bool) -> Analiz a
k !> p = do
a <- k
if p a then return a else mzero
Analizadores monádicos
Reconocimiento de una letra, o bien de un
número:
letra::AnalizChar
letra=elemento !> isAlpha

digito::AnalizChar
digito=elemento !> isDigit

letraODigito = letra !+ digito.


Analizadores monádicos

Ejemplo: reconocimiento de expresiones:

term ::= constante


| ( term + term )
| ( term / term )
Analizadores monádicos
Ejemplo: reconocimiento de expresiones:

anaConst::AnalizTerm anaDiv::AnalizTerm
anaConst=do anaDiv=do
a <- número _ <- literal ’(’
return(Const a) u <- term
_ <- literal ’/’
anaSum::AnalizTerm v <- term
anaSum=do _ <- literal ’)’
_ <- literal ’(’ return(u:/:v)
u <- term
_ <- literal ’+’ term::AnalizTerm
v <- term term=anaConst !+ anaSum !+ anaDiv
_ <- literal ’)’
return(u:+:v)
Software específico

 Alex

 Happy

 Frown

 Parsec
Alex y Happy
Son descendientes de los programas de Unix LEX y YACC ( Yet
Another Compiler Compiler), los programas generados por Alex
y Happy son en Haskell y como parte de un programa más largo
en Haskell.

Alex es una herramienta para generar analizadores léxicos en


Haskell. Dada una descripción de las unidades léxicas a
reconocer en forma de expresiones regulares Alex, genera un
módulo Haskell que contiene código para analizar texto
efectivamente

Una especificación léxica en Alex se guarda normalmente en un


fichero con una extensión .x . La forma general de un fichero
Alex es la siguiente: alex := [ código ] [ wrapper ] { macros }
token ’:-’ { regla } [ código ]
Regla:
Letras [a..zA..Z]

Analisis lexicográfico

Analisis ok
lexicográfico
Analizador Básico
Se procede con la instrucción Código final
alex wordcount.x {
main :: IO()
Código inicial main do
{ s <- getContents
module Main(main) where let toks = alexScanTokens s
} mapM _putStrLn toks
}
La siguiente línea es

%wrapper “basic”
En este caso Alex proporciona la función
alexScanTokens : : String -> [Token]

Macro definiciones
$letter = [a-zA-Z]
$noletter = [~ $letter \n]

Reglas
token :-
$noletter+ ;
$letter+ {id}
Tipos de parsers en un compilador
Los analizadores de sintaxis siguen reglas de producción definidas a través de
gramáticas libres de contexto. La forma en que las reglas de producción son
implementadas (derivadas) se dividen en dos tipos de parsers: Top-Down y Bottom-Up
Top-down

Cuando el parser comienza a construir su árbol desde el símbolo de


inicio e inmediatamente después trata de transformar el símbolo de
inicio, entonces se le llama top-down parsing y hay dos formas de
lograrlo.

•Parseo recursivo descendente: Es una forma común de


parseo. Es llamado recursivo pues usa procedimientos recursivos
para procesar el código de entrada.

•Backtracking : Significa que si una derivación de la producción


falla, La sintaxis del analizador reinicia el proceso usando
diferentes reglas de la misma producción. Esta técnica puede
procesar una cadena de entrada mas de una vez para determinar
la regla de producción correcta.
Bottom-up
O en español de abajo a arriba, Como el nombre los sugiere, el parseo
de abajo a arriba comienza a parser la entrada desde abajo hacia
arriba y trata de construir el árbol de parseo hasta el símbolo.

Ejemplo:

cadena de entrada: a + b * c

Reglas de producción.

Se comienza de abajo hacia arriba, se lee la cadena entera y se revisa


si las reglas de producción coinciden con la cadena de entrada.
Top-down

Cuando el parser comienza a construir su árbol desde el símbolo de


inicio e inmediatamente después trata de transformar el símbolo de
inicio, entonces se le llama top-down parsing y hay dos formas de
lograrlo.

•Parseo recursivo descendente: Es una forma común de


parseo. Es llamado recursivo pues usa procedimientos recursivos
para procesar el código de entrada.

•Backtracking : Significa que si una derivación de la producción


falla, La sintaxis del analizador reinicia el proceso usando
diferentes reglas de la misma producción. Esta técnica puede
procesar una cadena de entrada mas de una vez para determinar
la regla de producción correcta.
9 5 4 20 10 3

9 5 4 20 10 3

9 5 4
45
45 9 3 10 20
lex “ABC”

“ABC”

“1BC DEF GHI”


“ABC”
“DEF”
“GHI”
likes(mary,food).
likes(mary,wine).
likes(john,wine).
likes(john,mary).

| ? - likes(mary,food).
yes

| ? – likes(john,mary)
Yes

| ? – likes(mary,john)
No

| ? – likes (john,food)
No
male(james1).
male(charles1).
male(charles2).
male(james2).
male(george1).
female(catherine).
female(elizabeth).
female(sophia).
parent(charles1,
james1).
parent(elizabeth,
Es George I el pariente de Charles I? Query: pariente(charles1,
james1). george1).
parent(charles2,
charles1). De quién es Charles pariente? Query: pariente(charles1,X).
parent(catherine,
charles1). Quien son los hijos de Charles I? Query: pariente(X,charles1).
parent(james2,
charles1). Periente (Sophia, Charles1)
parent(sophia,
elizabeth).
parent(george1,
sophia).
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Informalmente, un programa PROLOG consiste en un conjunto de hechos


(afirmaciones simples) y de reglas que afirman “El hecho A es cierto si son
ciertos los hechos B1 y … y Bn”. Esto es, las reglas servirán para deducir nuevos
hechos a partir de otros.

Los hechos son las sentencias más sencillas. Un hecho es una fórmula atómica o
átomo: p(t1, ..., tn) e indica que se verifica la relación (predicado) p sobre los
objetos (términos) t1, ..., tn.

En el caso de la relación padre(pepe, maria). Se asume como un hecho, pues


no está involucrando variables en su definición; al nombre de la relación se le
llama predicado.

Una regla es una sola conclusión seguida por el signo :- el cual hace la vez de SI
condicional, seguida por una o mas relaciones de condición.
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Cuando la verdad de un hecho depende de la verdad de otro hecho o de un


grupo de hechos se usa una regla. Declaran las condiciones para que un
predicado sea cierto, con una implicación que pueden relacionar hechos para
dar los valores de verdad a un predicado (la cabeza se cumple si el cuerpo se
cumple). Funcionan como las fórmulas condicionales habituales en lógica. Una
regla está compuesta por una cabeza y una cuerpo. El cuerpo puede estar
formado por varios hechos y objetivos. Su sintaxis general es:

cabeza :- objetivo1, objetivo2, ..., objetivon

Existen dos tipos de reglas:

Las Conjunciones: Se emplea el operador lógico AND, se utiliza la coma.


Existen dos tipos de reglas:

Las Disyunciones: Se emplea el operador lógico OR, se utiliza el punto y coma.

Regla recursiva
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Ejemplo de reglas:

sucesor(1,2).
sucesor(2,3).
sucesor(3,4).
sucesor(4,5).
sucesor(5,6).
sucesor(6,7).
suma(1,X,R):-sucesor(X,R).
suma(N,X,R):-sucesor(M,N),suma(M,X,R1),sucesor(R1,R).

Aquí podemos apreciar como suma es una regla que depende en gran medida
de la condición sucesor. En la segunda regla de suma se observa que existe mas
de una condición, por tanto estas van separadas por coma; podemos observar
también que se realiza una operación recursiva.
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Definición de términos: átomos, números, variables y estructuras.

Un átomo es:
• Una cadena de caracteres formada por letras mayúsculas,
minúsculas, dígitos y el carácter de guion bajo. Aquí hay algunos
ejemplos: letramin, gran_kahuna_burger, lsita2Musicas y
gutarristaAcustico.
• Una secuencia arbitraria de caracteres entre comillas simples. Por
ejemplo, “Paty", “El lobo", “Cinco_Pesos_Cambio", "& ^% & # @ $ & *"
y "". La secuencia de caracteres entre las comillas simples se
denomina nombre del átomo. Tener en cuenta que se permite utilizar
espacios en dichos átomos; de hecho, una razón común para usar
comillas simples es para que podamos hacer precisamente eso.
• Una cadena de caracteres especiales. Aquí hay algunos ejemplos: @
= y ====> y; y: - son todos átomos. Como se vera, algunos de estos
átomos, como; y: - tienen un significado predefinido.
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Definición de términos: átomos, números, variables y estructuras.

Un número es:
• Los números reales no son particularmente importantes en las
aplicaciones típicas de Prolog. Entonces, aunque la mayoría de las
implementaciones de Prolog admiten números de punto flotante o
flotantes (es decir, representaciones de números reales como
1657.3087 o π).
• Los números enteros (es decir:…, -2, -1, 0, 1, 2, 3,…) son útiles para
tareas como contar los elementos de una lista, su sintaxis de Prolog
es como en otros lenguajes de programación: 23, 1001, 0, -365, etc.
La base de conocimiento se expresa mediante hechos y reglas, que no son otra
cosa que una representación sintáctica concreta de cláusulas de Horn de primer
orden. Por lo tanto el conocimiento queda expresado en lógica de primer orden.

Un programa Lógico por lo tanto no es más que un conjunto de hechos y reglas


que expresan cierto conocimiento mediante lógica de primer orden.

Al contrario de la mayoría de los lenguajes de programación, Prolog es un


lenguaje conversacional, es decir mantiene un diálogo continuo con el
programador desde el inicio hasta el fin de la sesión. A lo largo del cual el usuario
planteará preguntas y el sistema responderá cada una de las preguntas
formuladas.
Prolog le indica el usuario que está esperando a que se le formule una pregunta
mostrando en pantalla el siguiente símbolo.

?-

Tras este símbolo, el programador puede teclear una pregunta (terminada en un


punto) y pulsar el retorno de carro. Con ello, el programador solicita al sistema
Prolog que responda a la pregunta recién formulada. Una vez procesada la
pregunta el sistema Prolog mostrará en pantalla la respuesta correspondiente.

Por ejemplo, si queremos preguntar a Prolog si 5 es igual a 2+3 podemos teclear


la pregunta

?- 5 is 2+3.
Yes

Después de pulsar el retorno de carro, Prolog comprobará que efectivamente 2 y


3 suman 5 y, por lo tanto, responderá afirmativamente (Yes).

Prolog puede dar también respuestas negativas a las preguntas

?- 1 is 1+1.
No
Para responder a las preguntas formuladas, Prolog consulta una base de
conocimiento, al iniciar la sesión, esta base de conocimiento almacena un
conocimiento básico

Obviamente, Prolog no es capaz de responder cualquier pregunta que le


formulemos. Por ejemplo, si le preguntamos a Prolog si el pato Lucas es un pato

?- esPato(lucas). ERROR Undefined predicate `esPato/1’

Prolog nos responderá que no sabe determinar si algo es o no un pato, pues su


base de conocimiento no incluye información acerca de los patos. Formalmente,
lo que ocurre es que el predicado lógico ‘esPato/1’ no está definido (undefined
predicate).
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Definición de términos: átomos, números, variables y estructuras.

Una variable es:


• Una variable es una cadena de letras mayúsculas, minúsculas, dígitos
y guiones bajos que comienza con una letra mayúscula o un guión
bajo. Por ejemplo, X, Y, Variable, _tag, X_526, Lista, Lista24, _inicio,
Fin, _entrada y Salida son todas variables de Prolog. La variable _ (es
decir, un solo carácter de subrayado) es bastante especial. Se llama
variable anónima.
Los tres constructores básicos en Prolog

Hechos, Reglas y Queries.

Definición de términos: átomos, números, variables y estructuras.

Una estructura es:


• Las constantes, los números y las variables son los bloques de
construcción: al unirlos se forman términos complejos. Recordar que
los términos complejos a menudo se denominan estructuras.
• Los términos complejos se construyen a partir de un funtor (función
compleja) seguido de una secuencia de argumentos. Los argumentos
se colocan entre paréntesis ordinarios, separados por comas y
colocados después del functor.
• Tener en cuenta que el funtor debe ir seguido directamente del
paréntesis; no puede haber un espacio entre el functor y el paréntesis
que encierra los argumentos. El funtor debe ser un átomo. Es decir,
las variables no se pueden utilizar como functores. Por otro lado, los
argumentos pueden ser cualquier tipo de término.
Ejercicios:

¿Cuáles de la siguiente lista son átomos, variables y cuales no son ni uno ni otro.

1.- Vincent
2.- MasajedePies
3.- variable23
4.- Variable2000
5.- big_kahuna_Burger.
6.-’big kahuna Burger’
7.- big kahuna Burger
8.- ‘Julio’
9.- _Julio
10.- ‘_Julio’
Ejercicios:

¿Cuántos hechos, reglas, conjunciones y disyunciones hay en la siguiente base


de conocimiento?
Ejercicios:

¿Si se tiene la siguiente base de conocimiento, cual es la respuesta a los


siguientes queries?.

1.wizard(ron).
2.witch(ron).
3.wizard(hermione).
4.witch(hermione).
5.wizard(harry).
6.wizard(Y).
7.witch(Y).
Predicados sobre directorios y archivos

Los programas Prolog se almacenarán en ficheros de texto (con extensión '.pl').


Prolog adquiere nuevos conocimientos consultando (es decir, leyendo) estos
programas. Para facilitar al programador el acceso a los programas almacenados
en los ficheros, Prolog define un conjunto de predicados especiales que permiten
navegar por el sistema de ficheros y visualizar los directorios. Un detalle
importante a tener en cuenta es que Prolog utiliza notación diferente a MS-DOS
para representar las rutas de los ficheros. Mientras que en MS-DOS los directorios
que forman parte de una ruta se separan entre sí por el carácter '\', en Prolog se
emplea el carácter '/' con el mismo propósito.

Así, el directorio MS-DOS c:\juegos\pacman se escribe en notación Prolog


c:/juegos/pacman

El predicado pwd imprime el directorio de trabajo actual (es equivalente al


comando MS-DOS cd sin parámetros). Por ejemplo, si nuestro directorio de
trabajo es c:\prolog\mauricio (en notación MS-DOS), al preguntar a Prolog por el
directorio actual obtenemos

?- pwd.
c:/prolog/mauricio
Predicados sobre directorios y archivos

El predicado ls lista el contenido del directorio de trabajo actual (es equivalente


al comando MS-DOS dir). Por ejemplo, si el directorio actual contiene los ficheros
'patos.pl' y 'familia.pl' al ejecutar ls obtenemos.

?- ls. patos.pl familia.pl

Finalmente, es posible cambiar el directorio actual mediante el predicado cd


(equivalente al comando MS-DOS cd). El nombre del nuevo directorio debe ser
una ruta (absoluta o relativa) en notación Prolog, encerrada entre comillas
simples. Por ejemplo

?- cd(‘../paty’).

establecerá c:\prolog\paty como nuevo directorio de trabajo, mientras que

?- cd('c:/prolog/mauricio’).

Reestablece c:/prolog/mauricio como directorio de trabajo.


? - [swi(demo/likes)].

Con esto se carga el archivo likes.pl para ver ejemplos:

Que alimentos tiene el restaurante sam

?- likes(sam, X).
X = dahl ;
X = tandoori ;
X = kurma ;
X = chow_mein ;
X = chop_suey ;
X = sweet_and_sour ;
X = pizza ;
X = spaghetti ;
X = chips.

?- likes(sam,papas).
false.

?-
? –listing(mild).

mild(dahl).
mild(tandoori).
mild(kurma).

true.
?- listing(mexicana).
ERROR: procedure `mexicana' does not exist (DWIM could not correct goal)
^ Exception: (13) setup_call_catcher_cleanup(system:true,
prolog_listing:listing_(user:mexicana, []), _6604, prolog_listing:close_sources)
?- listing(italian).
italian(pizza).
italian(spaghetti).

?italian(X).
?- italian(X).
X = pizza ;
X = spaghetti.

?- italian(tacos).
false.
Ruta para ver el archivo DEMO. Archivos de programa > swipl > demo>likes.pl

Para terminar use el comando

?- halt.

?-
progenitor(clara,jose). %Hecho 1
progenitor(tomas, jose). %Hecho 2
progenitor(tomas,isabel). %Hecho 3
progenitor(jose, ana). %Hecho 4
progenitor(jose, patricia). %Hecho 5
progenitor(patricia,jaime). %Hecho 6

?- progenitor(patricia,jaime).
true
?- progenitor(jaime,X).
false
?- progenitor(X,jaime)
patricia
?- progenitor(X,jose),progenitor(X,isabel)
Objetivo 1 Objetivo 2

?-progenitor(tomas,X);
progenitor(X,Y);progenitor(Y,Z).

?-progenitor(clara,X),
progenitor(X,Y),progenitor(Y,jaime)
X = jose
Y = patricia
Z= jaime

cabeza :- objetivo1, objetivo2, ...,


objetivon.
cabeza :- objetivo1, objetivo2, ..., objetivon.
?- madre(X,Y)

?- padre(X,Y)
Ejemplo: Factorial

Funcion factorial(X)
SI (X==0) regresa 1
OTRO
regresa X * factorial(X-1)
FIN Funcion.
Ejemplo: Fibonacci

Funcion fibonacci(N)
SI (N < 2) regresa N
OTRO
regresa fibonacci(N-1) + fibonacci(N-2)
FIN Funcion.
En Prolog la palabra Functor es usado para referirse al átomo al comienzo de una estructura,
junto con su aridad, es decir, el número de argumentos que toma.

Por ejemplo, en likes(mary, pizza), likes y 2 argumentos son el functor.

?- functor(likes(mary, pizza), Name, Arity).


Name = likes
Arity = 2

?- functor(likes, Name, Arity).


Name = likes
Arity = 0

?- functor(X, likes, 2).


X = likes(_G232, _G233)

?- functor(likes(X, Y), Name, Arity).


X = _G180 Y = _G181
Name = likes
Arity = 2

?- functor(2 < 4, Name, Arity).


Name = (<),
Arity = 2

?- 2 <4. ?- <(2,4).
true true
[a,1, [b.2.0]]
?- append([a],[b],X).

X= [a,b].

?- append(X,[b,c,d],[a,b,c,d]).

X= [a]

?- append([a],[1,2,3],[a,1,2,3]).
true

?- append([a],[1,2,3],[hola,vida,mal]).
false
?- append([a],[b],X).

?- append(X,[b,c,d],[a,b,c,d]).

?- append([a],[1,2,3],[a,1,2,3]).

?- append([a],[1,2,3],[hola,vida,mal]).

append([],L,L).
append([X|L1],L2,[X|L3]) :- append(L1,L2,L3).
Se tiene el siguiente programa:

esta-en-biblio( libro(moby_dick, autor(herman, melville)) ).


esta-en-biblio( libro(ojos_de_robot, autor(isaac, asimov)) ).
esta-en-biblio( libro(the_art_of_PROLOG, autor(leon, sterling)) ).

esta-en-biblio( revista(eroski, mensual) ).


esta-en-biblio( revista(autopista, semanal) ).

?- esta-en-biblio( libro(X, autor(isaac,asimov))).

? esta-en-biblio( libro(moby_dick, X)).

? esta-en-biblio( revista(X, mensual)).


Longitud de una lista.

Dada una lista obtener los enteros positivos que la componen.

Agregar un 1 a los elementos numéricos que componen una lista.

Mapping de una lista

Generar la permutación de una lista


Resolución SLD
Función Mapping

En Prolog (y en Haskell) es muy habitual aplicar una operaci´on a cada uno de los
elementos de una lista y obtener una nueva lista resultado.

Por ejemplo, incrementar en 1 cada uno de los elementos de la lista [5,4,7,8,9]. O más
en general, incrementar en 1 los elementos de una lista L dada:

incLst([],[]).
incLst([X|Xs],[Y|Ys]):- Y is X+1, incLst(Xs,Ys).

También se puede programar de una forma un poco m´ss genérica:

incLst([],[]).
incLst([X|Xs],[Y|Ys]):- inc(X,Y), incLst(Xs,Ys).
inc(X,Y):- Y is X+1.
Expresiones aritméticas
? X is Y, Y is 4.

? X is 4+5, Y is X*2, Y =\=X.


put(b), nl, put(c)
A B E

F
C
Resumen

Prolog se basa en un conjunto de cláusulas de Horn ( hechos y reglas)

 Todas las cláusulas acaban con .


 El símbolo ← se escribe como :- que se lee como si (condicional).
 En los hechos no es necesario (basta poner el átomo y ) .
 Los nombres de predicado y función siguen las pautas habituales de
los identificadores pero comienzan con minúscula .
 Los nombres de variable también siguen esas pautas, pero
comienzan por mayúscula .
 Los átomos en el cuerpo de las reglas se separan mediante comas ,
que se leen como y.
 Ejecutar un programa no tiene sentido como tal (en principio). Lo
que se hace es plantear objetivos a resolver.
(a1,a2,…an) * (b1,b2,..bn) = Suma( i=1, N) ai*bi

[1,3] * [4,5] = (1*4) + (3*5)

Hecho, dada dos listas vacías 0


Operadores de comparación
intervalo(X,X,[X]).
intervalo(X,Y,[X|Xs]):- X<Y, Z is X +1, intervalo(Z,Y,Xs).

pares([],[]).
pares([X|Xs], [X,Ys]):- par(X), pares(Xs,Ys).
pares([X|Xs],Ys):- impar(X), pares(Xs,Ys).

ordena([]).
ordena([_]).
ordena([X,Y|Ys]):- X<Y, ordena([Y|Ys]).

permutation([2 ,3 ,4 ,1 ,5],L ),ordena(L).

sortPerm(L1,L2):- permutation(L1,L2), ordena(L2).

inserta(X ,[] ,[X]).


inserta(X ,[Y|Ys ] ,[X,Y|Ys]) : -X < Y.
inserta(X ,[Y|Ys ] ,[Y|Zs]) :-X >= Y, inserta(X,Ys ,Zs).
% Inserción
insertSort([],[]).
insertSort([X|Xs],S):- insertSort(Xs,S1), inserta(X,S1,S).

%Burbuja
bubbleSort(L,S):- swap(L,L1),!, write(L1), nl , bubbleSort(L1 ,S).
bubbleSort(S,S).

swap([X,Y|Ys ] ,[Y,X|Ys]): - X>Y.


swap([Z|Zs ] ,[Z| Zs1 ]):- swap(Zs , Zs1).

%QuickSort
quickSort([] ,[]).
quickSort([X|Xs], Res) :-
split(X,Xs , MasPeques , MasGrandes),
quickSort(MasPeques , PequesOrd),
quickSort(MasGrandes , GrandesOrd),
append(PequesOrd ,[X| GrandesOrd ], Res).

split(_ ,[] ,[] ,[]).


split(X ,[Y|Ys ] ,[Y| Peques ], Grandes) :-
X > Y, ! ,
split(X,Ys , Peques , Grandes).
split(X ,[Y|Ys], Peques ,[Y| Grandes ]) :-
split(X,Ys , Peques , Grandes).
? - write (38).

? - write ( ’ una cadena de caracteres ’).

? - write ([ a ,b ,c ,d ,[ e ,f , g ]]) , nl .

?- write ( ’ Carrera en ’) , nl , write ( ’ TI ’) , nl .

?- write ( ’ Centro de Cómputo’) , tab (5) , write ( ’ en TI ’).

? - writeq ( ’ una cadena de caracteres ’).

? - writeq ( ’ mira mis apóstrofos ’).

? - write ( ’ ahora no los ves ’).


writelist([]).
writelist([H|T]) :-
write(H), nl ,
writelist(T).

? - writelist ([[1 ,2 ,3] ,[4 ,5 ,6] ,[7 ,8 ,9]]).

? - read ( X ).

? - read ( p ( X )).

? - put (97) , nl .

? - get0 ( X ).

? - get ( X ).
Qué hace el siguiente programa:

readin :-
get0(X),
process(X).

process(42).
process(X) :-
X =\= 42 ,
write(X), nl ,
readin .
writelist([]).
writelist([H|T]) :-write(H), nl,writelist(T).

par(X):- 0 is X mod 2.

impar(X):- 1 is X mod 2.
9, 3,1,0

i=5
j=2
Lista[0] = 999999 Lista[2] = 0
Para i desde 2 Hasta Tamaño(Lista) Haz Lista[1] = 1
j=i
Mientras Lista[j] < Lista[j-1] Haz 3,9,1,0
intercambia(Lista[j],Lista[j-1])
j = j -1 3,1,9,0
FinMientras
FinPara 1,3,9,0

1,3,0,9

1,0,3,9

0,1,3,9

También podría gustarte