Lenguajes de Programación

Transcripción

Lenguajes de Programación
Lenguajes de Programación
Adrián Pérez Alonso
21 de junio de 2013
1.
Paradigma imperativo. Máquina de Turing
Una máquina de Turing es un dispositivo teórico que manipula sı́mbolos en una cinta infinita de
acuerdo a una tabla de reglas. A pesar de su simplicidad, puede adaptarse para simular la lógica de
cualquier algoritmo de computación y es especialmente útil para explicar el funcionamiento dentro
de una CPU, y en especial el modelo de cálculo del paradigma de programación imperativo. [6]
La máquina consta de una cabeza lectora que se mueve a derecha e izquierda y cuando se mueve
puede modificar el sı́mbolo de la cinta al que apunta.
La máquina de Turing se define mediante la siguiente séptupla: [1](cáp. 8.2.2)
M = (Q, Σ, Γ, δ, q0 , B, F )
Q Es el conjunto finito de estados de la unidad de control.
Σ Es el conjunto finito de sı́mbolos de entrada
Γ Es el conjunto completo de sı́mbolos de cinta /Σ ⊂ Γ
δ Es la función de transición. Los argumentos de δ(q, X) son un estado q y un sı́mbolo de cinta
X. El valor de δ(q, X), si está definido, es (p,Y,D) donde p es el siguiente estado de Q, Y es
el sı́mbolo de Γ y D es la dirección que puede ser derecha o izquierda.
q0 Es el estado inicial. q0 ∈ Q
B Es el sı́mbolo espacio en blanco. B ∈ Γ
F Es el conjunto finales o de aceptación. F ⊂ Q
Ejemplo: Diseñar una máquina de Turing que acepte el lenguaje {0n 1n |n ≥ 1}
M = ({q0 , q1 , q2 , q3 , q4 }, {0, 1}, {0, 1, X, Y, B}, δ, q0 , B, {q4 })
donde δ se especifica en la siguiente tabla: [1](cáp. 8.2)
0
1
X
Y
B
Estado
q0
(q1 , X, D)
(q3 , Y, D)
q1
(q1 , 0, D) (q2 , Y, I)
(q1 , Y, D)
q2
(q2 , 0, I)
(q0 , X, D) (q2 , Y, I)
q3
(q3 , Y, D) (q4 , B, D)
q4
La secuencia de movimientos para la entrada 0011 será la siguiente (aceptada por la MT):
q0 0011 ` Xq1 011 ` X0q1 11 ` Xq2 0Y 1 ` q2 X0Y 1 `
Xq0 0Y 1 ` XXq1 Y 1 ` XXY q1 1 ` XXq2 Y Y ` Xq2 XY Y `
XXq0 Y Y ` XXY q3 Y ` XXY Y q3 B ` XXY Y Bq4 B
Veamos cual es la secuencia de movimientos para la entrada no aceptada 001:
q0 001 ` Xq1 01 ` X0q1 1 ` Xq2 0Y ` q2 X0Y ` Xq0 0Y ` XXq1 Y ` XXY q1 B
1
1.1.
Recursividad
El término ‘recursivo’ se utiliza en matemáticas como sinónimo del término ‘decidible’, no tiene
que ver con el término de recursividad en programación.
Definición 1.1. Sea la función entera f calculada por M. Si M está definido en toda la entrada
para f decimos q f es totalmente recursiva. La MT de una función totalmente recursiva siempre
termina en el estado final y genera una respuesta. [7]
Definición 1.2. Una función parcialmente recursiva será aquella cuya máquina de Turing pare,
pero no sabemos si lo hará en el estado final o no. [7]
Si pensamos en el lenguaje de una función totalmente recursiva como en un ‘problema’, entonces
se dice que el problema es decidible si es un lenguaje recursivo e indecidible si es un lenguaje no
recursivo. Es decir, si no sabemos si la MT se para o no se para, decimos que el problema es no
decidible y por lo tanto no sabemos si se puede resolver (si es resoluble) o si no se puede resolver
(no resoluble).
Tambien existen los problemas intratables, que son problemas recursivos y resolubles de los que
no se puede obtener un resultado final. Esto es debido a que su complejidad es exponencial y por lo
tanto, siempre existirá alguna posibilidad de que, aun con capacidades de computación mucho mas
elevadas a las actuales, no seamos capaces de hacer frente a cálculos tan complejos. Un ejemplo de
este tipo de problemas es el problema del viajante para un número elevado de ciudades a visitar.
1.2.
El problema de la parada
Despues de esta introducción, nos damos cuenta de que no todos los problemas se pueden
resolver, y que la resolución de los problemas dependerá de que la MT acepte o no el lenguaje
y si esta se para o no se para. Este problema se conoce como el problema de la parada (Halting
problem).
Se podrı́a definir H(M) (halting problem para la MT M) como el conjunto de entradas, tales
que M se detiene para la entrada dada, independientemente de si M acepta o no la entrada.[1](cáp.
9.2.4)
1.2.1.
Hipótesis de Church
Todas las propuestas serias de modelos de computación calculan las mismas funciones o reconocen los mismos lenguajes. La hipótesis (no demostrada) de Church dice: [1](cáp. 8.2.1)
Definición 1.3. Cualquier forma general de computación no permite calcular sólo las funciones
recursivas parciales (que es lo que pueden calcular las computadoras actuales y las máquinas de
Turing)
2.
Paradigma funcional. λ-calculus
Los términos de λ-calculus, o λ-términos, serán usados para definir funciones.
La función x 7−→ x + 1 será escrita en lisp de la siguente forma (lambda(x)0 (+x1)). [2] (cáp. 3)
Para expresiones aritméticas, existen (por ejemplo) los operadores (+, *, etc), variables y constantes. La estructura de estas expresiones viene del aspecto funcional de los sı́mbolos. Cada sı́mbolo
requiere un número determinado de argumentos llamado aridad.
2.1.
Σ-notación
Definición 2.1. Sea Σ un alfabeto, definimos Σ-términos finitos (TΣ ) como el subconjunto más
pequeño de Σ. [2](cáp. 2.2.1)
Si f ∈ Σ y ar(f)=n, f es de aridad n, también se dice que f es n-ario. Por ejemplo: ‘not’ es un
operador unario, ‘suma’ es un operador binaro, etc.
Si c ∈ Σ, ar(c)=0, entonces c ∈ E. Denotamos a c sı́mbolo constante si la aridad es igual a 0
2
Si f ∈ Σ, ar(f ) = n ≥ 1, y si M1 , ..., Mn ∈ E, entonces f M1 , ..., Mn ∈ E. Denotamos a f
sı́mbolo funcional si la aridad es mayor o igual a 1.
Definimos TΣ (x) como el álgebra de Σ-términos finitos como variables en el conjunto x. De una
forma mas intuitiva, podrı́amos decir que TΣ (x) es el conjunto de todas las posibles combinaciones
de objetos de un lenguaje. [7]
Ejemplo: (1*2)*3.
(Numeros naturales y simbolo ‘∗0 )
Σ = {N+, ∗}
X = {x, y}
*
*
1
3
2










(1)
∈ TΣ (x)









Ejemplo:
f1
M ≡ {ε, 1, 2, 3, 2,1, 2,2, 3,1, 3,2} ≡ a1
g2
a1
h3
a2
a1
(2)
a2
Sea M ∈ TΣ (x), donde M es un árbol, definimos la siguiente notación[7]:
Θ(M ) se define como el conjunto de nodos del árbol M.
M(u) donde u ∈ Θ(M ). M(u) se define como la etiqueta del nodo ‘u’.
Algunos ejemplos para el árbol dado anteriormente serı́an:
M (ε) = f1
M (2,1) = a1
M (3) = h3
M/u donde u ∈ Θ(M ). M/u es el subtérmino (es decir, el subárbol) que ‘cuelga’ del nodo ‘u’.
Algunos ejemplos para el árbol dado anteriormente serı́an:
M (ε) = M
(3)
g
M/2 =
(4)
a1
2.2.
a2
λ-notación
λ-notación es un caso particular y mas especı́fico de Σ-notación.
Un λ-término se construye como un subconjunto de TΣ (x) donde: [2](cáp. 2.3.2)
Sea x ∈ X una variable ⇒ x es un λ-término.
Sea x ∈ X una variable y M un λ-término ⇒ λxM es un λ-término llamado abstracción
(definición de la función).
3
Sean M,N λ-términos ⇒ MN es un λ-término llamado aplicación de M sobre N.
Donde λ es el nombre de la función, x son los parámetros de la función, M es el cuerpo de la
función y N son los argumentos de la función.
Definición 2.2. Un redex es una aplicación cuyo término izquierdo es una abstracción (aplicación
sobre una abstracción, (λxM )N ).
Por ejemplo: con el redex I ≡ λf x.f (f x) (redex de nombre ‘I’ donde el parámetro de la función
f (x) se aplica sobre si misma), IPQ contrae a P(PQ). Un término que no tiene un subtermino que
es un redex es llamado normal (ver a continuación). El conjunto de todos los subtérminos de M
que son redex se escribe Ored (M ). Entonces M es normal ⇔ Ored (M ) = ∅.
El resultado de contraer el redex (λxM )N es el término M [x := N ]. Donde M [x := N ] es la
sustitución de x por N aplicada a M. [2] (cáp. 3.3.4)
En lenguaje coloquial un redex se podrı́a definir como un término que puede ser ‘simplificado’.
Nota: Los λ-términos representan funciones o trozos de funciones.
Pongamos por ejemplo la función
(λx)(x + 1)(3)
que tendrı́a un equivalente en lisp a
(def un masuno (x)(+ x 1))(masuno 3)
y un equivalente en C a
masuno(x){return x + 1; }
masuno(3);
2.3.
β-reducción
Definición 2.3. Denominamos β-reducción al proceso de derivación que sirve de base a la interpretación funcional.[7]
Como hemos visto anteriormente, cualquier λ-término es una variable, una aplicación o una
abstracción.
El término β-reducción se define como una relación basada en la contracción de un redex. Se
diferencian los siguientes tipos: [2] (cáp. 3.1.1)
β-reducción inmediata (→β ) (en un solo paso)
β-reducción (→∗β ) (en múltiples pasos)
β-conversion (=β ) (inter-reducción)
Cada una de estas definiciones es dada usando un sistema de inferencia que contiene la misma
sentencia M B N , donde M y N son términos y B es un nuevo sı́mbolo que significa ‘se reduce a’.
[2] (cáp. 3)
Definición 2.4. Inferencia es el acto de derivar conclusiones lógicas a partir de premisas conocidas
o asumiendo que es cierto [5]
Definición 2.5. Sean M y N ∈ TΣ (x) decimos q M se β-reduce a N si existe una derivación que
transforme M en N mediante la combinación de las siguientes reglas de inferencia: [7]
Definición 2.6. β-reducción es una relacion binaria escrita M →β N si y solo si existe una
derivación cerrada de la sentencia M B N del sistema de inferencia: [2] (cáp. 3.1.1)
(red) :
M B M0
(λ) :
λxM B λxM 0
(λxP )Q B P [x := Q]
M B M0
M B M0
(1) :
(2)
:
M N B M 0N
NM B NM0
4
El primero es el caso mas básico, donde se dice que un redex se reduce cambiando los valores
de x por la aplicación (Q) en la abstracción (P).
En los sigientes casos decimos que si un término M se reduce en otro término M’, un término
mas complejo, también se reducirá. De este modo en la segunda regla de inferencia decimos como se
reduce una abstracción, y en las dos reglas siguientes decimos como se reduce una aplicación tanto
por la derecha, como por la izquierda.
2.4.
2.4.1.
Propiedades fundamentales
Confluencia
Para la relación binaria → sobre E, donde x ∈ E. Se define: [2](cáp. 3.1.2)
Definición 2.7. Confluente en x ⇔ ∀x1 , x2 ∈ E/
x →∗ x1
x →∗ x0
, ∃x0 ∈ E/ 1 ∗ 0
∗
x → x2
x2 → x
Definición 2.8. Localmente confluente en x ⇔ ∀x1 , x2 ∈ E/
x → x1
x →∗ x0
, ∃x0 ∈ E/ 1 ∗ 0
x → x2
x2 → x
Definición 2.9. Fuertemente confluente en x ⇔ ∀x1 , x2 ∈ E/
x → x1
x → x0
, ∃x0 ∈ E/ 1
x → x2
x2 → x0
2.4.2.
Normalización
Definición 2.10. Si M es un λ-término normalizable, ⇒ ∃ una β-reducción M →∗ N , tal que N
es normal. [2](cáp. 3.1.2)
Es decir, un λ-término es normalizable si es interpretable hasta sus últimas consecuencias. De
este modo un λ-término normal ha sido ‘simplificado’ al máximo.
2.4.3.
Teorema Church-Rosser
Definición 2.11. El teorema de Church-Rosser dice que la β-reducción →β es confluyente. Si M
es normalizable, ∃N/N ≡ λ-término normal /M ↔∗ N [2](cáp. 3.1.2)
Definición 2.12. El problema de la normalización de un λ-término no decidible. [2](cáp. 3.1.2)
Un λ-término puede ser normalizable, y sin embargo, caer en una estrategia de normalización
que no nos lo permita. Veámoslo en los siguientes ejemplos: [7]
Ejemplo:
5 + 9 = 14
5 + 9 = 9 + 5 = 5 + 9 = ...
Ejemplo: ¿Es siempre decidible la normalización de la siguiente expresión?
(λxy)(λx.xx)(λx.xx)
En este ejemplo podemos realizar la normalización por la izquierda de forma exitosa, donde el
primer grupo de paréntesis (λxy) será la declaración de la función lambda con los parámetros x e y,
el segundo paréntesis (λx.x x) será el cuerpo de la función, y el tercer paréntesis (λx.x x), serán los
parámetros con los que se llama a esa función. Vemos que el parámetro ‘y’ no es usado (no aparece
en el cuerpo de la función):
(λxy)(λx.xx)(λx.xx) = (λx.λxλx)
Donde (λx.λxλx) no es un redex y por lo tanto no es normalizable.
5
Esta normalización la podrı́amos expresar de una forma (seguramente) más simple, con un sı́mil
a un lenguaje imperativo de programación 1 :
lambda(x, y){return λx.x x; };
lambda(λx, xx);
Podemos ver que el resultado de esta llamada a la función lambda será la devolución de λx. seguido
dos veces del parámetro segundo (xx) , es decir, será (λx.λxλx).
Si realizamos la normalización por la derecha, no obtendremos éxito. Para normalizar de este
modo, debemos dejar de lado el término (λxy) y trabajar con (λx.xx)(λx.xx) de forma que λx
será la declaración a nuestra función, xx será el cuerpo de la misma y (λx.xx) será el parámetro
con los que se llama esa función:
(λxy)(λx.xx)(λx.xx) = (λxy)(λx.xx)(λx.xx)
Vemos que obtenemos el mismo resultado del que partı́amos, que es a su vez un redex, por lo que
concluimos que no se puede realizar una normalización por la derecha.
Esta normalización la podrı́amos expresar de una forma (seguramente) más simple, con un sı́mil
a un lenguaje imperativo de programación 2 :
lambda(x){return x x; };
lambda(λx.xx);
De esta función obtendrı́amos el único parámetro de entrada duplicado, es decir (λx.xx)(λx.xx) al
que deberı́amos adjuntar el elemento que eliminamos anteriormente (λxy), quedando finalmente la
misma expresión que tenı́amos en origen, por lo que no la podemos normalizar: (λxy)(λx.xx)(λx.xx)
3.
Paradigma lógico
Definición 3.1. Un injerto es el complemento de la construcción de subárboles [7]
En programación, el injerto de código se llama macro. En lenguaje C se hace mediante la
directiva de preprocesado #define.
Por ejemplo: sea P ≡ Kx (función constante de valor x), y M ≡ λxN (función x → N ).
Entonces M [λ ← P ] ≡ λx(Kx) (función x → Kx, la funcion constante de valor x).
Definición 3.2. Una sustitución es una lista de pares (variable, término). Sean x un conjunto de
variables sobre Σ∗, σ es una sustitución: [7]
σ ≡ X → T2 (x)
Q = {X1 ← T1 , X2 ← T2 , ..., Xn ← Tn }
3.1.
Unificación
El paso de variables a constantes se le da el nombre de unificación o proceso de resolución
Definición 3.3. Un unificador de dos expresiones (términos) T1 y T2 , es una sustitución σ que
hace T1 σ = T2 σ [3] (cáp 7.2.2)
Es decir, una unificación es una sustitución que aplicada sobre dos términos, los iguala.
En este ejemplo deducimos que pepe es el hijo de luis:
1 Este
2 Este
ejemplo no es totalmente exacto, pero es una ilustración para facilitar el entendimiento por parte del lector
ejemplo no es totalmente exacto, pero es una ilustración para facilitar el entendimiento por parte del lector
6
padre(y,x) → hijo(x,y)
padre(luis,pepe)
hijo(pepe,luis)
Ejemplo: Dados los siguientes términos lógicos:
T1 = f (X, g(X, h(Y )))
T2 = f (Z, g(Z, Z))
Unificando según Q obtenemos el siguiente resultado:
Θ = {X ← h(1), Z ← h(1), Y ← 1}
T1 = f (h(1), g(h(1), h(1)))
T1 = f (h(1), g(h(1), h(1)))
Vemos que la unificación no es única, también podrı́amos aplicar lo siguiente:
Θ = {X ← Z, Z ← h(Y )}
Θ = {X ← Z, Z ← h(1), Y ← 1}
Definición 3.4. Dado Σ como un conjunto de todos los unificadores de T1 y T2 . Existe una substitución µ ∈ Σ que tiene la propiedad µσ = σ para todo σ ∈ Σ y es llamada unificador mas general
(mgu) de T1 y T2 [3] (cáp 7.2.2)
3.2.
Algoritmo de Robinson
El algoritmo de Robinson es el algoritmo de unificación mas usado en los intérpretes Prolog.
Representa el sistema de ecuaciones como una pila y se opera con la ecuación mas superior a menos
que esta no se pueda aplicar. [2] (cáp. 6.2.4) Ante la entrada de dos términos T1 y T2 obtendremos
una salida de σ = mgu(T1 , T2 ) si es que este existe. En otro caso obtendremos fail.
El siguiente pseudocódigo describe el método de unificación de Robinson [4](Algoritmo 6.3.1).
Entrada: Dos términos lógicos T1 y T2 .
Salida: El mgu(T1 ,T2 ), si existe; en otro caso fail.
INICIO
Θ := ∅
push (T1 = T2 , Pila )
MIENTRAS Pila 6= ∅ HACER
( X = Y ) := pop ( Pila )
CASO
X ∈
/ Y : sustituir (X , Y )
anadir (Θ, X ← Y )
Y ∈
/ X : sustituir (Y , X )
anadir (Θ, Y ← X )
( X←Y ) : NADA
( X←f ( X1 , ... , Xn )) y ( Y←f ( Y1 , ... , Yn )) :
PARA i := n HASTA 1 PASO -1 HACER
push ( Xi = Yi , Pila )
FIN PARA
SINO :
DEVOLVER fail
FIN CASO
FIN MIENTRAS
DEVOLVER Θ
FIN
7
Donde la función push(T1 = T2 , Pila) introduce la ecuación lógica T1 = T2 en la pila y la
función pop(Pila) extrae la ecuación lógica X=Y de la pila. La función sustituir (X, Y ) sustituye
la variable X por Y en la pila y en la sustitución Θ. Finalmente, la función anadir(Θ, σ) añade al
unificador Θ (la variable que vamos a devolver en la finalización) la sustitución que realicemos en
cada caso.
Ejemplo: Podemos describir de esta forma la unificación de los términos, siguiendo la siguiente
secuencia de configuraciones de la pila [4] (cáp. 6.3):
T1 = f (X, g(X, h(Y )))
T2 = f (Z, g(Z, Z))
Θ≡{}
`
T1 = T2
Θ≡{}
Θ ≡ { X ← Z}
Θ ≡ { X ← Z}
X=Z
Z=Z
`
`
`
g(Z,Z) = g(X, h(Y) )
g(Z,Z) = g(X, h(Y) )
Z = h(Y)
Θ ≡ { X ← Z}
Θ ≡ { X ← Z ← h(Y), Z ← h(Y) }
`
Z = h(Y)
En este ejemplo podemos ver, en cada momento del algoritmo, el valor de Θ (que será al final el
valor del mgu(T1 , T2 ) que devolvamos y que comienza siendo vacı́o) y el último elemento de la pila
(el único elemento al que podemos tener acceso de la pila). Además vemos también el elemento que
añadimos mediante el push(T1 = T2 , Pila).
Un aspecto importante es el llamado test de ciclicidad que se expresa mediante X ∈
/YyY∈
/ X.
Debido al elevado costo de su aplicación, la mayorı́a de intérpretes Prolog lo eliminan, lo que puede
dar lugar errores a la hora de la programación, como se ve en el siguiente ejemplo. [4](cáp 6.3)
Interroguemos con la siguiente pregunta, suponiendo que tenemos implementado el término
igual(X, X):
: −igual(Y, f (Y )).
Θ≡{}
Θ ≡ { X ← Z}
X=Y `
Y=f(Y)
X=f(Y)
Θ≡{}
`
igual(X,X)=igual(Y,f(Y))
Observemos que en la ultima de las configuraciones Y aparece en f(Y) y parece que en unifican, pero
en realidad no lo hacen. Si continuamos con el proceso ocurrirá que sustituiremos toda ocurrencia
de Y por f(Y), y como consecuencia obtendremos que la unificación entra en un ciclo:
X ← Y ← f (Y =← f (f (Y )) ← f (f (f (Y ))) ← ...
¿Es entonces imposible trabajar con términos cı́clicos? La respuesta es no. Es posible en algunos
casos, en particular este ejemplo si es resoluble, pero por razones de eficiencia computacional se
opta por una respuesta fail.
3.3.
Algoritmo de resolución
[7] La mayorı́a de los intérpretes Prolog están basados en el método de resolución lógica denominado SLD3 que se describe mediante el siguiente algoritmo:
Entrada: Un programa lógico y una pregunta Q.
Salida: QΘ, si Q es una instancia deducible a partir de P; en otro caso FAIL.
3 por
Selecting a literal, using a Linear strategy and searching the space of possible deductions Depth-first.
8
INICIO
Resolvente := { Q }
MIENTRAS Resolvente 6= ∅ HACER
A ∈ Resolvente
SI ∃ P : - Q1 ,... ,Qn tal que ∃Θ = mgu (A , P ) ENTONCES
borrar ( Resolvente , A )
anadir ( Resolvente ,Q1 Θ, ... , Qn Θ)
aplicar (Θ, Resolvente )
SINO
DEVOLVER fail
FIN SI
FIN MIENTRAS
DEVOLVER Θ
FIN
Donde consideramos que la Resolvente contiene en cada instante el conjunto de objetivos a
resolver (en el siguiente ejemplo son resolventes {persona(Juan)}, {madre(P,M1 ), persona(M1 )} y
{persona(Rosa)}). P:- Q1 , ..., Qn es una regla (por ejemplo: persona(P):-madre(P,M),persona(M))
y Qi es cada una de las partes de la cola de dicha regla. Θ = mgu(A,P) es el unificador mas general
del objetivo de la Resolvente (A) al aplicar la reducción sobre la regla P. Es decir, si hay una regla
que simplifique, entonces simplificamos.
La función borrar(Resolvente, A) borra el objetivo A de Resolvente, mientras que la función
anadir(Resolvente, Q1 Θ, ..., Qn Θ) añade los objetivos indicados a Resolvente. La función aplicar(Θ,
Resolvente) aplica sobre el conjunto de objetivos de Resolvente la restricción representada por la
sustitución Θ.
Ejemplo: Dado el siguiente programa:
madre(Juan, Rosa).
persona(P ) : −madre(P, M ), persona(M ).
persona(Rosa).
Temenos el siguiente árbol de resolución:
{persona(Juan)}
P ← Juan
|
{ madre(P,M1 ), persona(M1 ) }
M1 ← Rosa
|
{ persona(Rosa) }
{}
Existen diferencias entre el algoritmo teórico de resolución y la implementación de la resolución
tal y como hacen los intérpretes y como la realizamos en el ejemplo anterior. En el algoritmo teórico
no se define ningún orden, mientras que en la práctica se sigue un orden de izquierda a derecha.
Además se mantienen el orden en las colas de las reglas al realizar la sustitución. Es decir, en la
regla: persona(P ) : −madre(P, M ), persona(M ). la cola madre(), persona() se mantiene siempre
en ese orden y no irá nunca persona antes que madre. Esto tampoco está definido en el algoritmo
teórico.
4.
Mapa de nomenglatura
Paradigma imperativo
asignación de datos y tipos
mov. cabeza lectora en MT
Paradigma funcional
sustitución
β-reducción
Paradigma lógico
unificación
resolución
Referencias
[1] Teorı́a de autómatas, lenguajes y computación. J. E. Hopcroft y J. D. Ullman, 3a Edición, 2007.
9
[2] Computation as Logic. R. Lalément, 1st Edition, 1993.
[3] Logic for computer science. S. Reeves, M. Clarke, 1st Edition, 1990.
[4] Programación Lógica. Vilares Ferro, M., Alonso Pardo, M.A. y Valderruten Vidal, A, Ed. Tórculo, 2a edición, 1996.
[5] http://www.thefreedictionary.com/inference. Internet, Consulta: enero 2013
[6] https://en.wikipedia.org/wiki/Turing machine. Internet, Consulta: enero 2013
[7] Clase de teorı́a LPR. M. Vilares, 2012.
10

Documentos relacionados