Functional Logic Programming Introduction


      Navigate the pages page 2 >>
2. VM Features 3. VM Instruction Set 4. VM Execution Examples
Glossary of terms

Research Goal

The following pages are an introduction to the virtual machine research to create a narrowing engine for functional logic language programs. Narrowing engines available to date are all prototypical and almost universally incomplete. The goal with this virtual machine is to design and develop a general, efficient, and complete narrowing engine for functional logic languages.

Background Information

In order to first understand the workings of the functional logic language virtual machine, it is important to first understand some background of the functional logic programming languages. Functional logic programming aims at integrating in a single paradigm the characteristic features of functional and logic programming. It tries to add on three main features to typical functional programming which are: non-determinism, narrowing, and residuation. But to start out with, we should first look at the functional and logical programming languages to begin to understand what is characteristic about each of them.

Functional Programming

The basis of functional languages is to offer an alternative to the step by step imperative languages that we may be used to. Typical examples of imperative languages are C, Java, Pascal, etc. where the programs are a series of instructions expected to be executed in order. A functional language instead is evaluated as an expression that does not put restrictions on the order of execution, except in regards to dependancies that need to be used within an expression. Another way to think about it is from an example given at http://www.haskell.org/aboutHaskell.html. If we considered a spreadsheet, many of the columns in our spreadsheet may not be concrete values themselves, but formulas that rely on values from other cells. The importance is not in how the spreadsheet calculates the end results, but just that they are correct when finished. The sequence of calculations is up to the spreadsheet, as the data is provided.

Other great examples and more information can be found at:

Logic Programming

Logic programming in contrast has a very different method of evaluation. Logic programming specifies some properties that a solution should have for a valid answer. By specifying the properties, the interpreter will execute the given set of facts, and verify whether or not amongst those facts, there are any answers that meet the properties specified. A classic example of a logical programming language would be Prolog. In Prolog, a program would consist of a set of predicates that represent facts, and rules for interpreting those predicates.

Example

In Prolog, the basic unit is a predicate, such as:
    parent(bill, steve).
This predicate is considered to be true, although it does not have defined behavior yet. What I mean by defined behavior, is that the two elements bill and steve are in a fixed order parent relationship, but it is not stated here which one is the parent. If we enter the goal: parent(bill, steve). the interpreter will return yes, because that is how we defined the predicate. If we changed the order and entered the goal: parent(steve, bill). the interpreter will return no, as expected since that is not the predicate we specified. But if we wanted to change this in order to define who is the parent in a relationship, we must also define a rule for the relationship of the parent.

Here, we will use par to represent the rule to define a parent relationship. If we want to define the parent relationship to mean that the second person is the parent of the first, which in our example would mean that steve is a parent of bill, then we would define the rule par as such:
    par(X):-parent(X, Y).
By defining the par rule using the variables X, and Y, we are looking to see if for any person that we enter into par as a goal, if there is a Y that exists to satisfy the relationship. In other words, if we enter the goal: par(bill) the interpreter will return yes because bill does exist in a relationship with steve, where steve satisfies the variable Y in our search to see if bill has a parent. However, if we searched using par(steve). as a goal, this would return no, because we defined the relationship to only be valid as the second person being the parent of the first.

The advantage that we would like to utilize from this logic programming model is the ability to have a non-deterministic search technique. Non-determinism allows us to have more than one possible relationship that could satisfy our goal. For our example, you could think of this as bill having both a father and mother that could satisfy our rule par, and to the interpreter either answer is sufficient.


Functional Logic Programming

Now that we have looked at functional and logic languages, it gives us a basis to understand what we are capable of with a functional logic language. It should provide the ability to incorporate the expressiveness of a functional language, with the non-determinism of a logic language. Additionally, it will use narrowing and residuation to allow complex expressions to be broken down and simplified for computation. One example of a functional logic language is Curry. It is not necessary to understand functional logic languages in order to understand the working of the vitual machine, however, it may be useful in understanding some examples presented. If you would like to get a better feel for functional logic languages, it might be helpful to look over a tutorial on Curry.

The main features that allow for the non-deterministic abilities to be appreciated for Curry are equational constraints, concurrent conjuctions, and existential quantification. Included below is an explanation of each of these three concepts, and how non-determinism is used in them.

Equational Constraint

Equational constraint is a feature to the functional logic languages that returns values from a pair of expressions that are reducible to the same ground data term. For a simple example of this, first lets suppose we have a function that returns a fixed set of numbers. A simple example is a function one23 that returns a 1, 2, or 3 (for the 3 line code example of one23, look at page 10 of the Curry tutorial). If then we want to see what is returned from another function, that is within the domain we have specified. We could use the equational constraint to do just that. Now if we had another function that returned values from 2 to 5, we could find the intersection of the two functions though the use of the equational constraint. Lets say that our function returning numbers from 2 to 5 is named similar to our first example, two345. The code for our equational constraint would be:
    one23 =:= two345
Since the values returned for both sides must be equivalent, the result from both equations combined with the equational constraint would be 2 and 3. While this is a simple example, it gives a small illustration of how our non-determinism can be utilized in combination with constraints to obtain a result, without having multiple lines of code to compare a set of all possible answers from each expression. Instead, the philosophy is to let the machine do the work of calculate and compare, while we are more interested with the results.

Concurrent Conjuction

The concurrent conjunctiion is rather similar to the equational constraint concept, but has a primary difference to consider. The equational constraints have a notion of equality that can be satisfied if both sides are reducible to the same ground data term. This leads to a potential that one side could be undefined (nonterminating), which would not allow for the equality to be satisfied. This condition would cause the strict equality to not hold for the two expressions. Alternatively, concurrent conjuction allows for the evaluation of terms concurrently. Concurrent conjuction which is of the form,
    e1 & e2
allows the expressions to be evaluated together. For example, if the execution of e1 suspends, the evaluation of e2 can proceed which may cause the reactivation of e1 at some point. The expression may then be solvable due to the binding of common variables. The seperation of sequential (non-concurrent) and concurrent computations allows the use of efficient evaluation strategies for the sequential computations, where similar techniques for the concurrent parts are not available. The use of the concurrent evaluations though, gives us greater expressive power for our computations. For an example of this that combines the use of equational constraing with concurrent conjuction, we'll once again look at an example from the Curry tutorial. Our expressions will also contain a free variable X in them. The complete expression and results are:
choose> X=:=one23 & X+X=:=X*X
Free variables in goal: X
Result: success
Bindings:
X=2 ?
The execution of this example requires the computation of both sides of the concurrent conjuction to produce the results. Since X is bound to 1,2, or 3 by the first equational constraint, that is necessary for the computation of X in the second equational constraint. The second set of expressions can then compute to see for what values of X that X+X=:=X*X. Which obviously the only value of X from 1 to 3 that satisfies that equality is 2, so the result is success when X is bound to that. This gives a basic example of the goal for functional languages. Where we can use constraints to define properties that a valid solution will have, along with the expressiveness of the functional languages.

Existential Quantification

Existential Quantification should be a rather easy topic to understand in comparison. It is the ability to qualify free variables in an expression. For example, a function definition that we will use later is palind (that determines if an odd length string is a palindrome), and is defined as follows:
    palind s = s =:= x ++ y : rev x where x, y free
By defining x and y free, they are non-deterministic since they can be bound to any values. The one main thing to remember though, is although they are free, multiple occurences of the same variable in the same expression must be bound to the same values. Therefore, x must be the same value at both occurences, but that value is free to be determined non-deterministically. With the equational constraint used in here as well, the values that they take on must be reducible to the same base terms that s contains, and so they are constrained in that aspect, but in regards to themselves, they are not set values. This is a quick example of how existential quantification might be used, and where it comes in handy.

The more practice you get with a functional logic language will of course allow you to have a better understanding of how functional logic languages work, but this is sufficient for now to discuss the need for operationally complete non-determinism in the virtual machine.

[ top of page ]     [ next page >> ]

The ongoing virtual machine research is supported by NSF grant #0218224.
This web introduction was made possible through another generous NSF grant #0334405.

Designed by: Travis Berg
On the web at: http://www.cs.pdx.edu/~bergt/