In functional programming, you work with functions. Functions have a
well-defined list of inputs and a single output. So you can say of the
function cons, for example, that it takes as input a value and a list,
and yields as output a new list with the value prepended to the given
list; for example (cons 1 '(3 4)) would yield the value '(1 3 4).
In logic (sometimes called relational) programming, you work with
relations. A relation defines some link between multiple values. The
equivalent example to the above, usually denoted "conso" in the
Clojure world (and something like cons° in miniKanren, I think) would
be a relation of three values: (conso a b c). The more mathematical
interpretation would be "conso is true for any three values a, b, c
such that c is a list of at least one element, a is the first element
of c and b is a list of all elements of c except the first, in the
same order". In practical terms, a logic engine like miniKanren will
allow you to supply real values for some of the arguments and let the
others be free, and will return example values for which the relation
holds.
For example, (conso 1 '(3 4) c) would return something that says "c
must be '(3 4)". In this case, by analogy to the functional version,
we say we are running the relation "forward", i.e. in the same
direction as the function. But you can also ask a logic engine for
"(conso a b '(1 3 4))", and it will reply with something like "a
should be 1, b should be '(3 4)", and, again, by analogy with the
functional equivalent, we would say you are running the "function"
backwards. In terms of relational programming, in either case you're
just applying the relation, but most people who hear about relational
programming are more familiar with functions (or procedures) and will
relate to the notion that the "natural", i.e. "forward" way of running
conso is to supply the first two arguments and expect the engine to
supply values for the third, rather than the other way around.
Note that you could also ask a logic engine for "(conso 1 b '(1 3
4))", and it should respond with "b should be '(3 4)", which is
running it middleward, I guess. It's harder to relate to functions
when you have more than one "input", as logic programming would let
you specify any subset of them. Relations can also fail, such as if
you ask for "(conso 1 b '(3 4 5))"; in that case the logic engine,
depending on the robustness of its own implementation and the
definition of conso, would either enter an infinite loop trying to
find a value for which this holds or just respond with "there is no
value for b that makes this relation hold".
conso is a simple example in that it is bijective. If you instead
consider a concat function, such that (concat l1 l2), where l1 and l2
are lists, would yield a new list l3 with first all of the elements of
l1 then all of the elements of l2, then you can get a more interesting
equivalent relation ("concato"?).
Let's imagine you define (concato l1 l2 l3) to be true if l3 = (concat
l1 l2) (you cannot just state it like that to the logic engine). Then,
if you ask your logic engine about the relation (concato a b '(1 2
3)), it would respond with "Here are some possible values for a and b:
a = '(), b = '(1 2 3); a = '(1), b = '(2 3); a = '(1 2), b = '(3); a =
'(1 2 3), b = '()".
What Byrd and Friedman have been working on for some time now is the
(research) question of "can we write a relation that defines a Lisp
interpreter?", where a Lisp interpreter is thought of as the function
eval, essentially. So if (eval '(+ 1 2)) would yield 3, can you define
a relation evalo, within the constraints of their specific logic
engine ({mini,alpha}Kanren), that mimics that behaviour when run
"forward" (i.e. given the first argument as a value and the second one
as a free-floating logic variable) and also works "backwards" (i.e.
given the first argument as a free-floating variable and the second
one as a value).
So they would define (evalo a b) such that it is satisfied iff (eval
a) yields b (though again you cannot tell it that simply to your logic
engine, hence the research part); this means that for example (evalo
'(+ 1 2) 3) would be satisfied, (evalo '(+ 1 2) 4) would not be
satisfied, (evalo '(+ 1 2) a) would return something like "this can be
satisfied if a = 3" (forward), and (evalo a 3) (i.e. "backward with
respect to the equivalent function) would return something along the
lines of "There are a bunch of programs that can evaluate to 3. Here
are a few of them: '(+ 0 3), '(+ 1 2), '(+ 2 1), '(- 4 1), '((fn []
3)), ..."
Their last question is a bit tricky: what program, when evaluated,
yields itself? This is the notion of a quine, and quines are pretty
hard to generate for humans (apart from the simple self-evaluating
values, of course). An example quine in Clojure, which I just stole
from
http://programming-puzzler.blogspot.co.uk/2012/11/clojure-makes-quines-way-too-easy.html,
would be:
((fn [x] (list x (list (quote quote) x))) (quote (fn [x] (list x (list
(quote quote) x)))))
I'm not sure it's the simplest possible quine, but you can probably
already see that this is far from trivial to generate by searching
through the space of all possible programs.