Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
37 views

Functional Logic Programming (Antoy)

This document discusses functional logic programming, which combines features of functional and logic programming languages. It introduces the concepts of data constructors and defined operations common to functional logic languages. Pattern matching is used to define operations through equations for different argument patterns. Functional logic languages also allow narrowing to reuse definitions by instantiating logic variables, enabling operations to be applied to unknown arguments.

Uploaded by

johnvaran
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views

Functional Logic Programming (Antoy)

This document discusses functional logic programming, which combines features of functional and logic programming languages. It introduces the concepts of data constructors and defined operations common to functional logic languages. Pattern matching is used to define operations through equations for different argument patterns. Functional logic languages also allow narrowing to reuse definitions by instantiating logic variables, enabling operations to be applied to unknown arguments.

Uploaded by

johnvaran
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

Functional Logic Programming

Sergio Antoy Michael Hanus


Portland State University Institut für Informatik, CAU Kiel
Portland, OR 97207, U.S.A. D-24098 Kiel, Germany.
antoy@cs.pdx.edu mh@informatik.uni-kiel.de

1. INTRODUCTION minism and predicates with multiple input/output modes


The evolution of programming languages is the stepwise that offer code reuse. Since all these features are useful for
introduction of abstractions hiding the underlying computer developing software, it is worthwhile to amalgamate them
hardware and the details of program execution. Assem- into a single language. Functional logic languages combine
bly languages introduce mnemonic instructions and sym- the features of both paradigms in a conservative manner.
bolic labels for hiding machine codes and addresses. For- Programs that do not use the features of one paradigm be-
tran introduces arrays and expressions in standard mathe- have as programs of the other paradigm. In addition to the
matical notation for hiding registers. Algol-like languages convenience of using the features of both paradigms within
introduce structured statements for hiding gotos and jump a single language, the combination has additional advan-
labels. Object-oriented languages introduce visibility levels tages. For instance, the demand-driven evaluation of func-
and encapsulation for hiding the representation of data and tional programming applied to non-deterministic operations
the management of memory. Along these lines, declarative of logic programming leads to more efficient search strate-
languages, the most prominent representatives of which are gies. The effort to develop languages intended to meet this
functional and logic languages, hide the order of evaluation goal has produced a substantial amount of research span-
by removing assignment and other control statements. A ning over two decades (see [22] for a recent survey). These
declarative program is a set of logical statements describing achievements are reviewed in the following. The concrete
properties of the application domain. The execution of a syntax of the examples is Curry [27], but the design of the
declarative program is the computation of the value(s) of code and most of our considerations about the programs are
an expression with respect to these properties. Thus, the valid for any other functional logic language, e.g., T OY [30],
programming effort shifts from encoding the steps for com- based on reduction for functional evaluation and on narrow-
puting a result to structuring the application data and the ing for the instantiation of unbound logic variables.
relationships between the application components. A common trait of functional and logic languages is the
Declarative languages are similar to formal specification distinction between data constructors and defined opera-
languages, but with a significant difference: they are exe- tions. This distinction is also essential for functional logic
cutable. The language that describes the properties of the languages to support reasonably efficient execution mecha-
application domain is restricted to ensure the existence of nisms. Data constructors build the data underlying an ap-
an efficient evaluation strategy. Different formalisms lead plication, whereas defined operations manipulate these data.
to different classes of declarative languages. Functional lan- For instance, True and False construct (are) Boolean values.
guages are based on the notion of mathematical function; A simple example of a more complex data structure is a list
a functional program is a set of functions that operate on of values. We denote by [] the empty list and by (x:xs) a
data structures and are defined by equations using case dis- non-empty list where x is the first element and xs is the list of
tinction and recursion. Logic languages are based on pred- remaining elements. We create longer lists by nested appli-
icate logic; a logic program is a set of predicates defined cations of constructors: (1:(2:(3:[]))) denotes a list with
by restricted forms of logic formulas, such as Horn clauses elements 1, 2, and 3. Since lists are ubiquitous structures
(implications). in declarative languages, syntactic sugar is usually provided
Both kinds of languages have similar motivations but pro- to ease writing lists; thus, [1,2,3] abbreviates the previous
vide different features. E.g., functional laguages provide ef- list structure.
ficient, demand-driven evaluation strategies that support in- A further feature common to many functional and logic
finite structures, whereas logic languages provide non-deter- languages is the definition of operations by pattern matching.
Functions are specified by different equations for different
argument values. For instance, a function, “++”, returning
the concatenation of two input lists is defined as follows (here
we use infix notation for “++”):
Permission to make digital or hard copies of all or part of this work for
[] ++ ys = ys
personal or classroom use is granted without fee provided that copies are
not made or distributed for profit or commercial advantage and that copies (x:xs) ++ ys = x : (xs ++ ys)
bear this notice and the full citation on the first page. To copy otherwise, to The first equation is applicable to calls to “++” with an empty
republish, to post on servers or to redistribute to lists, requires prior specific list as the first argument. The second equation is used to
permission and/or a fee.
Comm. of the ACM | April 2010 | vol. 53 | no. 4. evaluate calls with non-empty lists. The pattern (x:xs) of
the second equation combines case distinction (the list is not we can compute the prefix of a list by narrowing. Similarly,
empty) and selectors (let x and xs be the head and tail of the equation zs++[e] =:= [1,2,3] is solved by instantiat-
the argument list) in a compact way. The meaning of this ing e with the last element of the right-hand side list (and
definition is purely equational; expressions are evaluated by zs with the remaining list). Thus, we can reuse the con-
replacing left-hand sides by corresponding right-hand sides catenation operation “++” to compute the last element of a
(after substituting the equation’s variables with correspond- list. We can also define an explicit function last for this
ing actual arguments). These replacement steps are also purpose:
called reductions, and the defining equations are often called last xs | zs++[e] =:= xs
rules, since they are applied from left to right. = e
The definition of functions by equations containing pat- where zs,e free
terns in the parameters of the left-hand sides is well known
from functional languages, such as ML [32] or Haskell [34]. Here, we add both a condition to a defining rule, so that this
Purely functional languages stipulate that rules must be rule is applicable only if the condition (the equation between
constructor-based. The patterns in each defining rule con- “|” and “=”) can be solved, and a declaration of the variables
sist only of constructors and variables (formal parameters). introduced by the rule (the where . . . free clause).
This excludes rules like Although code reuse originating from the evaluation of
operations with unknown arguments is well known in logic
(xs ++ ys) ++ zs = xs ++ (ys ++ zs) programming, functional logic languages provide additional
Such an equation describes a property (associativity) of the structures for reusing code. For instance, it is apparent from
operation “++” rather than a constructive definition about its the above rule defining last that this rule is applicable only
evaluation. The restriction to constructor-based rules is an if the actual argument has a form that matches the result
important factor to support execution with efficient strate- of narrowing zs++[e]. Thus, we can re-formulate the above
gies. “Execution” in functional languages means reducing rule as:
expressions containing defined operations to values (i.e., ex- last (zs++[e]) = e
pressions without defined operations) by applying defining
equations (“replacing equals by equals”). Note that purely functional languages, such as Haskell, do
The logic component of functional logic languages comes not allow this rule because it is not constructor-based; rather
into play when computing with incomplete information. The it contains a functional pattern, that is, a pattern with a de-
problem is to evaluate an expression containing a subexpres- fined function inside. When a rule of this kind is applied
sion e such that the value of e is unknown, but it is known to some function call, the functional pattern is evaluated
that e must satisfy certain conditions. In a computation, (by narrowing) to some value (which likely contains vari-
e is represented by a free variable and the conditions on e ables) which is then unified with the actual argument. Since
constrain the values that e may assume. For instance, con- the functional pattern zs++[e] can be narrowed to infinitely
sider the equation zs++[2] =:= [1,2]. (We use the symbol many values ([e] for zs=[], [x1,e] for zs=[x1], . . . ), it ab-
“=:=” for equations that should be solved in order to syn- breviates an infinite number of ordinary patterns. Similarly
tactically distinguish them from equations that define oper- to narrowing, it is not necessary to guess all these patterns
ations.) This equation states that we are interested only in at run time. Instead, the necessary patterns can be com-
the values for the variable zs that satisfy the equation. In puted in a demand-driven manner during pattern matching
this case we have to replace or instantiate zs with the list [7].
[1]. The combination of variable instantiation and term These examples show the potential of functional logic lan-
reduction is called narrowing, originally introduced in auto- guages: writing programs as clear specifications that ab-
mated theorem proving [37] and first proposed for program- stract from the evaluation order, contain unknowns, and
ming in [35]. In general, there might be infinitely many reuse defined operations in various ways. Of course, there is
instantiations for the free variables of an expression. The a price to pay for these advanced features. Dealing with un-
research on functional logic languages has led to reasonable knowns requires the consideration of different alternatives
narrowing strategies that avoid a blind guessing of all the po- during run time; in other words, computations might be
tential values of a variable [4]. Good strategies, as discussed non-deterministic. This non-determinism is don’t-know, and
later, perform only “constructive” guessing steps: They in- finding all the solutions of a problem may require consid-
stantiate free variables only if the instantiation is necessary ering many non-deterministic alternatives. To make non-
to sustain a computation and only with those values that are determinism practically useful, the number of alternatives
demanded to perform a reduction step. For instance, in the that must be considered by the execution of a program
evaluation of zs++[2], zs is replaced either by the empty list should be contained. This is provided by the evaluation
[] or by a non-empty list (x:xs) where the head and tail strategy that will be the subject of Section 3.
are again unknown. Either instantiation enables a reduction
step with one of the rule defining “++”. Not every instantia-
tion will lead to a result; thus, some instantiation might be
2. CURRY
later discarded. Good narrowing strategies ensure that the Curry [27] is a functional logic language developed by an
cost of an instantiation is incurred only when a guess for an international community of researchers to produce a stan-
unknown value is necessary. Expressions without unknowns dard for research, teaching, and application of functional
or with unknowns that don’t need to be known are evaluated logic programming. Details can be found at www.curry-
as in functional languages. language.org. In the following we give an overview of Curry
The capability to compute with unknowns supports new with emphasis on aspects relevant to functional logic pro-
forms of code reuse. As we have seen in the equation above, gramming.
The syntax of Curry borrows heavily from that of Haskell
[34]. In fact, Curry mainly introduces a single syntactic ex- deterministic operations, consider the definition of an oper-
tension (the declaration of free variables) with respect to ation that inserts an element into a list at an indeterminate
Haskell, although the underlying evaluation strategy is dif- position:
ferent (see below). Thus, a Curry program is a set of defini- insert x ys = x : ys
tions of data types and operations on values of these types. insert x (y:ys) = y : insert x ys
A data type is declared by enumerating all its constructors
with the respective argument types. For example: Since both rules are applicable in evaluating a call to insert
with a non-empty list, insert 0 [1,2] evaluates to any of
data Bool = True | False [0,1,2], [1,0,2], or [1,2,0]. Thus, insert supports a
data BTree a = Leaf a straightforward definition of a permutation of a list by in-
| Branch (BTree a) (BTree a) serting the first element at some position of a permutation
The type BTree is polymorphic, that is, the type variable of the tail of the list:
a ranges over all possible type expressions, and it has two perm [] = []
constructors Leaf and Branch. Operations on polymorphic perm (x:xs) = insert x (perm xs)
data structures are often generic. For instance, the follow-
ing operation computes the number of nodes (branches and Permutations are useful for specifying properties of other
leaves) of a binary tree, where the (optional) first line defines operations. For instance, a property of a sort operation on
the type signature: lists is that the result of sorting is a permutation of the input
list where all the elements are in ascending order. Since
size :: BTree a -> Int functional logic languages can deal with several results of
size (Leaf _) = 1 an operation as well as failing alternatives, it is reasonable
size (Branch t1 t2) = 1 + size t1 + size t2 to specify sorted lists by an operation sorted that is the
Thus, size (Branch (Leaf 1) (Leaf 3)) evaluates to 3. identity only on sorted lists (and fails on lists that are not
As in Haskell, the names of (type) variables and operations sorted):
usually start with lowercase letters, whereas the names of sorted [] = []
type and data constructors start with an uppercase letter. sorted [x] = [x]
The application of f to e is denoted by juxtaposition (f e), sorted (x:y:ys) | x<=y = x : sorted(y:ys)
except for infix operators such as “+”. Curry, in contrast to
Haskell, may use the operation size to compute binary trees Altogether, the expression “sorted (perm xs)” specifies the
of a particular size. For example, if t is a free variable, the sorted permutation of a list xs. Since Curry evaluates non-
evaluation of size t =:= 3 instantiates t to the tree struc- deterministic definitions, this specification of sorting is ex-
ture (Branch (Leaf x1) (Leaf x2)), where x1 and x2 are ecutable. Although it seems that a complete strategy has
free variables that remain unspecified because their values to compute all the permutations, a good strategy does bet-
do not affect the equation. ter than that. Modern functional logic languages, includ-
As discussed earlier, finding solutions to an under-specified ing Curry, use demand-driven strategies that do not always
problem requires the non-deterministic evaluation of some evaluate expressions completely. For instance, the argument
expression. The ability to perform non-deterministic com- (perm xs) of the expression sorted (perm xs) is evaluated
putations supports an interesting language feature, namely only as demanded by sorted. If a permutation starts with
the definition of non-deterministic operations. These opera- out of order elements, as in 2:1:perm ys, sorted will fail
tions deliver more than one result with the intended mean- on this expression without further evaluating perm ys, i.e.,
ing that one result is as good as any other. The archetype the evaluation of all the permutations of ys is avoided. This
of non-deterministic operations is the binary infix operation demand-driven search strategy reduces the overall complex-
“?”, called choice, that returns one of its arguments: ity of the computation compared to a simple generate-and-
test strategy. Compared to other sorting algorithms, this
x ? y = x program is still inefficient since we have not used specific
x ? y = y knowledge about sorting collections of objects efficiently.
Thus, we can define the flipping of a coin as: Non-deterministic operations promote concise definitions,
coin = 0 ? 1 as shown by perm above. It is interesting to observe that
narrowing, that is, computation with free variables that
Non-deterministic operations are unusual at first glance, but are non-deterministically instantiated, and the evaluation
they are quite useful for programming. Note that the result of non-deterministic operations without free variables, have
of a non-deterministic operation is a single (indeterminate) the same expressive power. For instance, one can replace
value rather than the set of all possible values. Thus, the a free variable of type Bool in an expression by the non-
value of coin is not the set {0, 1}, but one element of this set. deterministic operation genBool that is defined by
The “single-element” view is important to exploit demand-
driven evaluation strategies for the efficient evaluation of genBool = True ? False
such operations. For instance, it might not be necessary so that it evaluates to any value of type Bool. The equiva-
to compute all results of a non-deterministic operation if lence of narrowing with free variables and the evaluation of
the context demands only values of a particular shape. A such non-deterministic generator functions is formally stated
concrete example of this advantage is given in the following in [8] and the basis of a recent Curry implementation [13].
paragraphs. It should be noted that there exists a declara- Nevertheless, modern functional logic languages provide
tive foundation (i.e., model-theoretic, big-step, and fixpoint both features because both are convenient for programming.
semantics) for non-deterministic operations [19]. There is a subtle aspect to consider when non-deterministic
To show a slightly more interesting example involving non- expressions are passed as arguments to operations. Consider
the operation: A crucial semantic component of a functional logic pro-
double x = x+x gramming language is its evaluation strategy. Informally,
the strategy tells which subexpression of an expression should
and the expression “double coin”. Evaluating the argument be evaluated first. For non-strict semantics, such demand-
coin (to 0 or 1) before passing it to double yields 0 and 2 as driven semantics, that do not evaluate every subexpression,
results. If the argument coin is passed unevaluated to dou- the problem is non-trivial. For functional logic languages
ble, we obtain in one rewrite step the expression coin+coin the inherent difficulties of the problem are compounded by
which has four possible rewrite derivations resulting in the the presence of incomplete information in the form of free
values 0, 1 (twice), and 2. The former behavior is referred variables. The formalization of an evaluation strategy re-
to as call-time choice semantics [28] since the choice of the quires a formal model of computation. Various models have
value of a non-deterministic argument is made at call time, been proposed for functional logic programming. Rewrit-
whereas the latter is referred to as need-time choice seman- ing systems [9] are the model that more than any other has
tics since the choice is made only when needed. promoted significant progress in this area.
Although the call-time choice resembles an eager or call- A functional logic computation is the repeated transforma-
by-value evaluation behavior, it fits well into the framework tion, by narrowing steps, of an expression e. An elementary
of demand-driven or lazy evaluation where arguments are step of e involves a subexpression of e. This subexpression
shared to avoid the repeated evaluation of the same expres- either is a redex (red ucible ex pression) or is instantiated
sion. For instance, the actual argument (e.g., coin) asso- to obtain a redex. This redex is then reduced according to
ciated to the formal parameter x in the rule “double x = some rewrite rule of the program. The strategy produces the
x+x” is not duplicated in the right-hand side. Rather both subexpression, the optional instantiation, and the rewrite
occurrences of x refer to the same term, which consequently rule that constitute a step. The strategy may produce sev-
is evaluated only once. This technique, called sharing, is eral elementary steps of an expression. Steps that do not
essential to obtain efficient (and optimal) evaluation strate- “interfere” with each other can be combined into a multistep
gies. The call-time choice is the semantics usually adopted and executed concurrently.
by current functional logic languages since it satisfies the A computation of an expression terminates when the ex-
principle of “least astonishment” in most situations [19]. pression does not allow further steps. This expression is
Curry also supports the use of constraint solvers, since the called a normal form. If a normal form contains occurrences
condition of a rule is not restricted to a Boolean expression of defined operations, such as a division by zero or the head
as in Haskell. The condition of a rule can be any constraint of an empty list, it is called a failure, and the corresponding
that must be solved before applying the rule. A constraint computation is said to fail. Otherwise, the normal form is
is any expression of the predefined type Success. The type called a result or value and the corresponding computation
Success is a type without constructors but with a few ba- is said to succeed. The non-deterministic nature of func-
sic constraints that can be combined into larger expressions. tional logic programming may lead to multiple outcomes of
We have already seen the constraint operation “=:=”, which a computation. An expression may have several distinct re-
is a function that maps its arguments into the type Suc- sults and several distinct failures. Failures are discarded,
cess. Furthermore, there is a conjunction operation on con- but failing computations may be both desirable and explic-
straints, “&”, that evaluates its two arguments concurrently. itly planned in the design of some programs [6]. The pri-
Beyond these basic constraints, some Curry implementa- mary task of the strategy is to ensure that every result of
tions also offer more general constraint structures, such as an expression is eventually obtained. Not surprisingly, for
arithmetic constraints on real numbers or finite domain con- functional logic programs it is undecidable to tell whether a
straints, together with appropriate constraint solvers. This computation will terminate.
enables the implementation of very effective algorithms to The strategy of contemporary implementations of func-
solve specific constraints. In this way, programs access and tional logic languages is based on a formalism called a defini-
combine predefined constraints in a high-level manner. tional tree. A definitional tree [2] is a hierarchical structure
Curry has a variety of other language features for high- of the rewrite rules defining an operation of a program. For
level general purpose programming. Similarly to Haskell, it example, consider the operation that returns a prefix of a
is strongly typed with a polymorphic type system for reli- given length of a list, where for the purpose of illustration
able programming. Generic programming is also obtained natural numbers are represented by Zero and Successor.
through higher-order operations. Curry supports modules
and offers input/output using the monadic approach like take Z _ = []
Haskell [34]. Moreover, it predefines primitive operations to take (S n) [] = []
encapsulate search, that is, to embed the non-deterministic take (S n) (x:xs) = x : take n xs
search for solutions into purely functional computations by The operation take makes an initial distinction on its first
passing some or all the solutions into list structures. The argument. The cases on this argument are zero and non-
combined functional and logic programming features of Curry zero. In the non-zero case, the operation makes a subse-
have been shown useful in diverse applications: for exam- quent distinction on its second argument. The cases on this
ple, to provide high-level APIs to access and manipulate argument are null and non-null. A definitional tree of the
databases [12], to construct graphical user interfaces [26], operation take [4, Example 8] encodes these distinctions,
and to support web programming [21, 26]. These develop- the order in which they are made, and the cases that they
ments were instrumental in identifying new design patterns consider.
that are specific to functional logic programming [6]. Definitional trees guide the evaluation strategy similarly
to case expressions of functional languages, but there are
significant differences. Case expressions are explicitly coded
3. STRATEGY
by the programmer, whereas definitional trees are inferred In this section, we are not concerned with the efficiency of
from the rules defining an operation. This difference be- computations, but we will come back to this issue in Sec-
comes apparent in some situation where both strategies are tion 6.
applicable. For example, consider the following operation: We start with an example where the formal specification
f 0 0 = 0 can be directly written as a program. A regular expression
f _ 1 = 1 over some alphabet is an expression of the following type
(we omit empty regular expressions for simplicity):
In Curry, the computation of f t 1 results in 1 even if t
does not terminate, whereas in the lazy functional language data RE a = Lit a
Haskell the same computation does not terminate on non- | Alt (RE a) (RE a)
terminating t due to the fixed left-to-right evaluation of de- | Conc (RE a) (RE a)
manded arguments. | Star (RE a)
To fully support non-determinism, in a functional logic Except for the syntax, this declaration is identical to the
program the textual order of the rules defining an opera- usual definition of regular expression. The type variable a
tion is irrelevant. A consequence of this stipulation is that ranges over the type of the alphabet, i.e., we do not restrict
not every operation has a definitional tree, and definitional our alphabet to characters only. A regular expression is one
trees may have different characteristics. This has prompted of the following expressions: a letter of the alphabet (Lit),
a classification of functional logic programs according to the a choice between two expressions (Alt), a concatenation of
existence and/or the kinds of the definitional trees of their two expressions (Conc), or zero or more repetitions of an
operations. Implementations of functional logic languages expression (Star). For instance, the regular expression ab∗
have grown more sophisticated over time. The modern ap- is given a name, abstar, and is defined as an expression of
proach transforms a program of a source language, which type (RE Char) as follows:
allows operations without a definitional tree, functional pat- abstar = Conc (Lit ’a’) (Star (Lit ’b’))
terns, and/or partially applied functions, into a semantically
equivalent program of a core language in which every opera- Derived constructors for regular expressions are defined as
tion has a definitional tree [3, 8]. Each such tree is compiled new operations, e.g.:
into code or bytecode that implements a finite state automa- plus re = Conc re (Star re)
ton that determines both the evaluation of the arguments of
The language of a regular expression is defined by a mapping
a function application and the instantiation of free variables
that takes a regular expression and yields a set of words over
when necessary to perform a reduction.
the given alphabet. We prefer the functional logic program-
Essential properties of an evaluation strategy are sound-
ming style and define this mapping as a non-deterministic
ness (each computed result is correct with respect to an
operation sem that yields any word (represented as a list) in
equational model of the program) and completeness (for
the language of the given regular expression:
each correct result, a corresponding value or a representative
of this value is computed). Sound and complete strategies sem :: RE a -> [a]
support a high-level abstract view of programming: The pro- sem (Lit c) = [c]
grammer states the intended properties of the application sem (Alt r s) = sem r ? sem s
domain rather than the effects of individual computation sem (Conc r s) = sem r ++ sem s
steps. sem (Star r) = [] ? sem (Conc r (Star r))
Very strong properties are known for the evaluation strat- This is a concise specification of the semantics of regular ex-
egy of Curry’s core language. In particular, needed narrow- pressions. Since it is a functional logic program, it is also
ing [5] is sound and complete, computes only needed steps executable so that we can use it to generate words in the
(each computation has minimal length), and computes only language of a regular expression. For instance, sem abstar
disjoint instantiations (no computation is unnecessarily re- evaluates to "a", "ab", "abb", . . . Therefore, sem can be also
peated). used to check whether a given word w is in the language of
The last two decades have seen a wealth of results in the a given regular expression re through the equation sem re
area of strategies. Despite these achievements, we believe =:= w. The correctness of this claim stems from the sound-
that more remains to be discovered. The promise lies in the ness and completeness of the strategy, which guarantees that
development of strategies able to exploit the potential of par- the equation is satisfiable if and only if w is a value of sem
allel architectures. We will briefly discuss some possibilities re, hence, according to the definition of sem, if and only if
in Section 6. w is in the language of re.
Moreover, we can check whether a string s contains a word
4. PROGRAMMING generated by a regular expression re (similarly to the Unix’s
grep utility) by solving the equation:
Functional logic programs mostly encode functional com-
putations, but they can also encode non-deterministic com- xs ++ sem re ++ ys =:= s
putations and computations with incomplete information. where xs, ys free
This combination simplifies the design of some programs If s contains a word w generated by a regular expression
to a degree unparalleled by other programming paradigms. re as a substring, w must be a value of sem re, and there
Simpler design leads to simpler proofs of program properties. are sequences xs and ys such that the concatenation of xs,
The focus of this section is on encoding a specification into a w, and ys is identical to s. The correctness of our “grep”
program and discussing the correctness of the program with program again follows from the soundness and completeness
respect to its specification. We keep our discussion infor- of the evaluation strategy. However, there is a noteworthy
mal, and out of necessity, we consider only small examples.
difference with the previous case. Earlier, the equation was cannibals and the presence of the boat on the initial bank
verified, whereas in the case the equation is solved for xs of the river:
and ys. data State = State Int Int Bool
If a regular expression r contains the Star constructor,
the language it generates is infinite so that sem r has in- Thus, the initial state is “State 3 3 True”, but we do not
finitely many results. Since a demand-driven strategy com- construct it directly. Our program constructs a state only
putes only the values that are required by the context, the through a function, makeState, with the same signature as
previous equation might have a finite search space even in State. The function makeState applies State to its own
the presence of regular expressions with an infinite language. arguments only if the resulting state is “sensible,” other-
For instance, the equation wise it simply fails. (This programming pattern is called
“constrained constructor” in [6].) In this context, a state is
xs ++ sem abstar ++ ys =:= "abb" sensible only if it is valid according to the numbers of mis-
where xs, ys free sionaries and cannibals involved in the puzzle and is safe for
has a finite search space with exactly three solutions in xs the missionaries. In the following code, the operators &&, ||
and ys. Observe that we have used sem to generate regular and == are respectively the Boolean conjunction, disjunction
expressions satisfying a given property. and equality.
For the next example let us consider an informal specifi- makeState m c b | valid && safe
cation of the straight selection sort algorithm: given a non- = State m c b
empty sequence of elements, (1) select the smallest element, where valid = 0<=m && m<=3 && 0<=c && c<=3
(2) sort the remaining ones, and (3) place the selected ele- safe = m==3 || m==0 || m==c
ment in front of them. The only difficult-to-implement step
of this algorithm is (1). Step (3) is trivial. Step (2) is ob- For example, the initial and final states are constructed by:
tained by recursion. Step (1) is more difficult because it is initial = makeState 3 3 True
specified non-constructively. The specification must define final = makeState 0 0 False
the minimum of a sequence—in the obvious way—but will
There are a total of 10 different moves, five to cross the
not say how to compute it. The algorithm to compute the
river in one direction and five in the other direction. Cod-
minimum, a problem considerably more complicated than
ing all these moves becomes particularly simple with non-
its definition, is left to the programmer. A functional logic
determinism and makeState. Both contribute to free the
programmer, however, can avoid the problem entirely coding
code from controlling which move should be executed.
a program that “executes” the specification.
The first rule of operation sort coded below implements move (State m c True)
the above specification. The head of the rule employs a = makeState (m-2) c False - 2 miss
functional pattern. The input of sort is (evaluated to) a ? makeState (m-1) c False - 1 miss
list, and one element of this list, min, is non-deterministically ? makeState m (c-2) False - 2 cann
selected. The condition of the rule ensures that the selected ? ...
element is indeed minimal in the list. The remaining code is A solution is a path through the state space from the initial
straightforward. The operation all, defined in a standard to the final state. A path is a sequence of states each of
library (Prelude) loaded with every program, returns True if which, except the first one, is obtained from the previous
its first argument, a predicate, is satisfied by every element state by a move. The path is represented by a sequence
of its second argument, a list. The expression (min <=) is of states. Our program adds new states at the front of a
called a section. It it equivalent to a function that takes an sequence that initially contains only the initial state, hence
argument x and tells whether min 6 x. the order of the states in a path is reversed with respect to
sort (u++[min]++v) the moves that produce a solution. Our program constructs
| all (min <=) rest a path only through a function, extendPath, that takes a
= min : sort rest non-empty path p and adds a state at the front of p so that
where rest = u++v the resulting path is sensible. In this context, a path is
sort [] = [] sensible only if it does not contain a cycle and each state of
the path except the initial one is obtained with a move from
The assertion to establish the correctness of this program
the preceding state.
states that sort l is sorted and contains all and only the
elements of l. The proof is by induction on the length of l. extendPath p | noCycle = next : p
The base case is trivial. In the inductive case, if sort l = x : where next = move (head p)
xs , then the head of the rule ensures that x is an element of noCycle = all (next /=) p
l, and the condition of the rule ensures that x is not greater Computing a solution of the puzzle becomes particularly
than any element of xs. The induction hypothesis ensures simple with non-determinism and extendPath. Our program
that xs contains all and only the elements of l except x and simply extends a path until it produces the final state.
that xs is sorted. This entails the assertion.
Our final problem is the well-known missionaries and can- main = extendToFinal [initial]
nibals puzzle. Three missionaries and three cannibals want extendToFinal p =
to cross a river with a boat that holds up to two people. if (head p == final) then p
Furthermore, the missionaries, if any, on either bank of the else extendToFinal (extendPath p)
river cannot be outnumbered by the cannibals (otherwise, The assertion to establish the correctness of this program
as the intuition hints, they would be eaten). A state of the states that operation main terminates with a solution of the
puzzle is represented by the numbers of missionaries and
puzzle if and only if the puzzle has a solution. We discuss with similar goals. We briefly discuss some of them and
the properties of the program that inspire confidence in its relate them to Curry.
correctness and would be key to a more rigorous proof. The language T OY [30] has strong connections to Curry
The program maintains two invariants: (1) Every state since it is based on similar foundations. In contrast to Curry,
constructed during an execution does not violate the prob- it supports more advanced constraint structures (e.g., dise-
lem’s constraints, and (2) the value returned by every in- quality constraints) but misses application-oriented libraries
vocation of operation extentToFinal is a path in the state so that it has not been used for application programming.
space. Invariant (1) stems from the fact that a state is con- This is also the case for Escher [29], a functional logic lan-
structed only by operation makeState, which ensures the guage where functions are deterministically evaluated in a
legality of a state through conditions valid and safe. In- demand-driven manner and logic features are modelled by
variant (2) stems from invariant (1) and the fact that a path simplification rules for logical expressions.
is constructed only by operation extendPath, which ensures The language Oz [38] is based on a computation model
that the sequence of states it returns is indeed a path since that extends concurrent constraint programming with fea-
only states originating from valid moves are added to a path. tures for distributed programming and stateful computa-
Operation extendToFinal keeps extending its argument, a tions. It supports functional notation, but operations used
path, unless the last inserted state is the goal, in which case for goal solving must be defined with explicit disjunctions.
it returns its argument. Operation extendToFinal is in- Thus, functions used to solve equations must be defined dif-
voked by main with the initial state. Hence, if operation ferently from functions to rewrite expressions.
extendToFinal successfully terminates, it returns a solution Since functions can be considered as specific relations,
of the problem. Operation extendPath invokes operation there are also approaches to extend logic languages with fea-
move, which is non-deterministic. The completeness of the tures for functional programming by adding syntactic sugar
evaluation strategy ensures that every move available in a for functional notation. For instance, [14] proposes to add
state is chosen. Hence, if the problem has a solution, ex- functional notation to Ciao-Prolog which is translated by
tendToFinal, and consequently main, will return this solu- a preprocessor into Prolog. The functional logic language
tion. Mercury [39] restricts logic programming features in order
A further property of the program is that any solution re- to provide a highly efficient implementation. In particu-
turned by extendToFinal contains no repeated states. This lar, operations must have distinct modes so that their ar-
condition stems from the fact that the paths returned by guments are either completely known or unbound at call
extendToFinal are constructed by operation extendPath time. This inhibits the application of typical logic program-
which, through condition noCycle, fails to insert a state in ming techniques, e.g., computing with structures that are
a path that already contains that state. only partially known, so that some programming techniques
for functional logic programming [6, 26] cannot be applied
5. LOOKING BEHIND with Mercury. This condition has been relaxed in the lan-
guage HAL [18] which adds constraint solving possibilities.
A motivation behind the emergence of functional logic
However, both languages are based on a strict operational
programming was the desire to unify the two most promi-
semantics that does not support optimal evaluation as in
nent branches of the declarative paradigms, functional and
functional programming.
logic programming. Much progress has been made toward
Some of the concepts and techniques developed for func-
this goal, and in the process the goal has somewhat changed.
tional logic programming have migrated to other areas of
Unification in the form of a single common language ac-
computer science. A success story is Tim Sheard’s Ωmega
cepted and used by the two communities, as perhaps some
system [36]. Ωmega is a type systems with an infinite hier-
of us may have envisioned, does not appear near. It may be
archy of computational levels that combines programming
difficult to understand all the reasons of this development,
and reasoning. At the value level computation is performed
but investments, personal preferences, and specialization are
by reduction. At all higher levels computation is performed
certainly contributing factors. However, each community
by narrowing in the inductively sequential [2] systems using
has become much more aware of the other, there are confer-
definitional trees to execute only needed steps.
ences in which papers from each paradigm are understood
and equally enjoyed by all the participants, and efforts such
as introducing logic variables in functional languages [15] 6. LOOKING AHEAD
and functions in logic languages [14] are indisputable evi- Will functional logic programs ever execute as efficiently
dence that the gap between the two paradigms has been nar- as imperative programs? We do not have the final answer,
rowed. More interestingly, functional logic programming has but the future is promising. Needed narrowing, the strategy
become a paradigm in its own right with results whose depth for a core functional logic language, exhibits two distinct as-
and solidity rival those of the other declarative paradigms. pects of non-determinism: don’t care and don’t know choices.
We sketched the concepts of Curry and used it for con- Don’t care choices occur in some function calls when more
crete examples. Curry is currently the only functional logic than one subexpression needs to be evaluated, such as when
language which is based on strong theoretical foundations both arguments of an equation or of an arithmetic binary
(e.g., sound, complete, and optimal evaluation strategies operation are demanded. Because the language is free of
[4, 5]) and has been used for a variety of projects, such side effects, we do not care which argument is evaluated
as web-based information systems, embedded systems pro- first. Therefore, it is viable to evaluate both arguments con-
gramming and e-learning systems [24, 25]. Curry has been currently. A small amount of synchronization may be neces-
also used from the very beginning to teach functional and sary to determine when the evaluation of a set of don’t care
logic programming concepts with a single programming lan- choices is completed and to instantiate variables shared by
guage [20]. Nevertheless, there are various other languages these choices.
Don’t know choices occur in some function calls when Declarative languages describe programming tasks through
more than one alternative is available, such as when a free high-level, concise and executable abstractions. Other para-
variable argument is instantiated with different values and/or digms offer similar abstractions, but to a lesser degree. Com-
when an expression is reduced by different rules. Since we pared to purely functional languages, functional logic lan-
don’t know in advance which alternative(s) will produce a guages allow non-deterministic descriptions with partial in-
solution (if we did, we would have coded a program that formation that simplify encoding some problems into pro-
selects these alternatives only), all the alternatives must be grams. Compared to purely logic languages, functional logic
sampled to ensure the completeness of the computation. Be- languages allow functional composition, which improves code
cause each alternative is independent of any other alterna- modularity, reuse and efficiency of execution.
tive, in this situation too, the order of execution of the alter- Functional logic programs have the flavor of executable
natives is irrelevant. As a consequence, both don’t care and specifications or high-level descriptions. Correctness proofs
don’t know choices can be executed concurrently. This is one for these programs are simpler than those in other pro-
area where the abstraction from the evaluation order pays gramming paradigms. These executable specifications are a
off. For many problems, in particular non-numeric ones, good solution of some programming problems, for example,
functional logic programs have a degree of parallelism that is when the execution is efficient or when no other algorithm
potentially higher than corresponding imperative programs. is known. Of course, the performance of this specification-
Future research will investigate this potential and attempt oriented approach may not be satisfactory for some prob-
to exploit it for faster execution of functional logic programs. lems. Even in this situation, the effort to develop an initial
The most important aids to the development of real ap- prototype is not wasted. First, building one or more proto-
plications, after a solid and efficient compiler, are a set of types is a reasonable step for many software artifacts since a
libraries for application programming and environments and prototype clarifies problems and allows experimentation at
tools that support program development and maintenance. a reduced cost. Second, a prototype can be refined with the
Promising work has been started in both areas. Functional introduction of more efficient data structures (e.g., replace a
logic programming supports abstractions that lead to high- list with a tree) or more efficient algorithms (e.g., replace
level, declarative programming interfaces. Libraries with in- linear search with binary search). Since declarative lan-
terfaces with these characteristics ease the construction of guages also allow the implementation of efficient data struc-
reliable applications in various application areas, as shown tures [33], the stepwise improvement of a prototype can be
by the various libraries developed for Curry (see Section 2). done without changing the implementation language. This
The formal foundations of functional logic languages are also methodology has the advantage that the initial prototype
useful for the development of environments and tools for rea- serves as a specification for verifying the improved program
soning about programs. Developments in these areas include and/or as an oracle for testing it.
tools for program analysis [23], verification [16], partial eval- Last but not least, programming in a functional logic lan-
uation [1], profiling [11], debugging [10], and testing [17]. guage is fun. As we move through the evolution of program-
These initial efforts demonstrate the suitability of func- ming languages sketched in the introduction, we find that
tional logic programming techniques for application program- programming problems become easier and solutions more re-
ming. Some applications have been successfully developed, liable. Evaluating an arithmetic expression directly through
as already mentioned above. Due to the availability of Curry its standard notation without using registers, temporaries
implementations with support for meta-programming, most and stacks is safer and more convenient. Structuring pro-
of the current tools are implemented in Curry. These ap- grams without labels and gotos is more elegant and increases
plications demonstrate the potential of functional logic pro- the confidence in the correctness of the result. Legitimate
gramming. Future research and development in these areas concerns about the efficiency of these revolutionary innova-
will promote and ease the use of functional logic programs tions were raised at the times of their introductions. His-
in practice. tory repeats itself. Programming with non-determinism is
The major asset of functional logic programming is the exhilarating when a potentially difficult task is replaced by
amalgamation of functional and logic programming features. an almost effortless choice followed by a much simpler con-
Logic programming emphasizes non-determinism, whereas straint. In our examples, this was to determine whether a
functional programming is deterministic, that is, the input string belongs to the language defined by a regular expres-
of a computation completely determines the output. This sion, or to find the minimum of a sequence, or to produce
dichotomy creates a conflict when it is desirable to reason the solution of a puzzle without any concern for selecting
“functionally” about non-deterministic computations, for ex- the moves. This approach is not always the best one in
ample, to select a minimal or maximal value among a set of practice. For example, the non-deterministic choice of the
results of a non-deterministic computation, or to print all minimum of a list may have to be replaced by an algorithm
these results in some order. Curry provides some features that is much faster for the computer to execute and much
to encapsulate non-deterministic computations. For exam- slower for the programmer to code and verify. But also in
ple, the set of results of a non-deterministic computation these cases, there is understanding and value in the proto-
can be collected in some data structure and processed in typical implementation and satisfaction in its simplicity and
its entirety. These features are useful and are used in some elegance.
applications [13, 31] but have limitations. Future research
on the theory of these features and on their implementation Acknowledgments.
should ease the encoding of complex problems into relatively This work was partially supported by the German Re-
simple functional logic programs. search Council (DFG) grant Ha 2457/5-2, the DAAD grants
D/06/29439 and D/08/11852, and the NSF grants CCR-
7. CONCLUSION 0110496 and CCR-0218224.
8. REFERENCES Haskell. In Proc. ACM SIGPLAN Haskell Workshop,
[1] E. Albert, M. Hanus, and G. Vidal. A practical partial Montreal, 2000.
evaluator for a multi-paradigm declarative language. [16] J.M. Cleva, J. Leach, and F.J. López-Fraguas. A logic
Journal of Functional and Logic Programming, programming approach to the verification of
2002(1), 2002. functional-logic programs. In Proceedings of the 6th
[2] S. Antoy. Definitional trees. In Proc. of the 3rd International ACM SIGPLAN Conference on
International Conference on Algebraic and Logic Principles and Practice of Declarative Programming,
Programming, pages 143–157. Springer LNCS 632, pages 9–19. ACM Press, 2004.
1992. [17] S. Fischer and H. Kuchen. Systematic generation of
[3] S. Antoy. Constructor-based conditional narrowing. In glass-box test cases for functional logic programs. In
Proc. of the 3rd International ACM SIGPLAN Proceedings of the 9th ACM SIGPLAN International
Conference on Principles and Practice of Declarative Conference on Principles and Practice of Declarative
Programming (PPDP 2001), pages 199–206. ACM Programming (PPDP’07), pages 75–89. ACM Press,
Press, 2001. 2007.
[4] S. Antoy. Evaluation strategies for functional logic [18] M.J. Garcı́a de la Banda, B. Demoen, K. Marriott,
programming. Journal of Symbolic Computation, and P.J. Stuckey. To the gates of HAL: A HAL
40(1):875–903, 2005. tutorial. In Proc. of the 6th International Symposium
[5] S. Antoy, R. Echahed, and M. Hanus. A needed on Functional and Logic Programming (FLOPS 2002),
narrowing strategy. Journal of the ACM, pages 47–66. Springer LNCS 2441, 2002.
47(4):776–822, 2000. [19] J.C. González-Moreno, M.T. Hortalá-González, F.J.
[6] S. Antoy and M. Hanus. Functional logic design López-Fraguas, and M. Rodrı́guez-Artalejo. An
patterns. In Proc. of the 6th International Symposium approach to declarative programming based on a
on Functional and Logic Programming (FLOPS 2002), rewriting logic. Journal of Logic Programming,
pages 67–87. Springer LNCS 2441, 2002. 40:47–87, 1999.
[7] S. Antoy and M. Hanus. Declarative programming [20] M. Hanus. Teaching functional and logic programming
with function patterns. In Proceedings of the with a single computation model. In Proc. Ninth
International Symposium on Logic-based Program International Symposium on Programming Languages,
Synthesis and Transformation (LOPSTR’05), pages Implementations, Logics, and Programs (PLILP’97),
6–22. Springer LNCS 3901, 2005. pages 335–350. Springer LNCS 1292, 1997.
[8] S. Antoy and M. Hanus. Overlapping rules and logic [21] M. Hanus. Type-oriented construction of web user
variables in functional logic programs. In Proceedings interfaces. In Proceedings of the 8th ACM SIGPLAN
of the 22nd International Conference on Logic International Conference on Principles and Practice of
Programming (ICLP 2006), pages 87–101. Springer Declarative Programming (PPDP’06), pages 27–38.
LNCS 4079, 2006. ACM Press, 2006.
[9] M. Bezem, J. W. Klop, and R. de Vrijer (eds.). Term [22] M. Hanus. Multi-paradigm declarative languages. In
Rewriting Systems. Cambridge University Press, 2003. Proceedings of the International Conference on Logic
[10] B. Brassel, S. Fischer, M. Hanus, F. Huch, and Programming (ICLP 2007), pages 45–75. Springer
G. Vidal. Lazy call-by-value evaluation. In Proc. of the LNCS 4670, 2007.
12th ACM SIGPLAN International Conference on [23] M. Hanus. Call pattern analysis for functional logic
Functional Programming (ICFP 2007), pages 265–276. programs. In Proceedings of the 10th ACM SIGPLAN
ACM Press, 2007. International Conference on Principles and Practice of
[11] B. Braßel, M. Hanus, F. Huch, J. Silva, and G. Vidal. Declarative Programming (PPDP’08), pages 67–78.
Run-time profiling of functional logic programs. In ACM Press, 2008.
Proceedings of the International Symposium on [24] M. Hanus and K. Höppner. Programming autonomous
Logic-based Program Synthesis and Transformation robots in Curry. Electronic Notes in Theoretical
(LOPSTR’04), pages 182–197. Springer LNCS 3573, Computer Science, 76, 2002.
2005. [25] M. Hanus and F. Huch. An open system to support
[12] B. Braßel, M. Hanus, and M. Müller. High-level web-based learning. In Proc. 12th International
database programming in Curry. In Proc. of the Tenth Workshop on Functional and (Constraint) Logic
International Symposium on Practical Aspects of Programming (WFLP 2003), pages 269–282, 2003.
Declarative Languages (PADL’08), pages 316–332. [26] M. Hanus and C. Kluß. Declarative programming of
Springer LNCS 4902, 2008. user interfaces. In Proc. of the 11th International
[13] B. Braßel and F. Huch. On a tighter integration of Symposium on Practical Aspects of Declarative
functional and logic programming. In Proc. APLAS Languages (PADL’09), pages 16–30. Springer LNCS
2007, pages 122–138. Springer LNCS 4807, 2007. 5418, 2009.
[14] A. Casas, D. Cabeza, and M.V. Hermenegildo. A [27] M. Hanus (ed.). Curry: An integrated functional logic
syntactic approach to combining functional notation, language (vers. 0.8.2). Available at
lazy evaluation, and higher-order in LP systems. In http://www.curry-language.org, 2006.
Proc. of the 8th International Symposium on [28] H. Hussmann. Nondeterministic algebraic
Functional and Logic Programming (FLOPS 2006), specifications and nonconfluent term rewriting.
pages 146–162. Springer LNCS 3945, 2006. Journal of Logic Programming, 12:237–255, 1992.
[15] K. Claessen and P. Ljunglöf. Typed logical variables in [29] J. Lloyd. Programming in an integrated functional
and logic language. Journal of Functional and Logic
Programming, (3):1–49, 1999.
[30] F. López-Fraguas and J. Sánchez-Hernández. TOY: A
multiparadigm declarative system. In Proc. of
RTA’99, pages 244–247. Springer LNCS 1631, 1999.
[31] W. Lux. Implementing encapsulated search for a lazy
functional logic language. In Proc. 4th Fuji
International Symposium on Functional and Logic
Programming (FLOPS’99), pages 100–113. Springer
LNCS 1722, 1999.
[32] R. Milner, M. Tofte, and R. Harper. The Definition of
Standard ML. MIT Press, 1990.
[33] C. Okasaki. Purely Functional Data Structures.
Cambridge University Press, 1998.
[34] S. Peyton Jones, editor. Haskell 98 Language and
Libraries—The Revised Report. Cambridge University
Press, 2003.
[35] Uday S. Reddy. Narrowing as the operational
semantics of functional languages. In Proceedings of
the IEEE International Symposium on Logic in
Computer Science, pages 138–151, Boston, 1985.
[36] T. Sheard. Type-level computation using narrowing in
Ωmega. Electron. Notes Theor. Comput. Sci.,
174(7):105–128, 2007.
[37] J.R. Slagle. Automated theorem-proving for theories
with simplifiers, commutativity, and associativity.
Journal of the ACM, 21(4):622–642, 1974.
[38] G. Smolka. The Oz programming model. In J. van
Leeuwen, editor, Computer Science Today: Recent
Trends and Developments, pages 324–343. Springer
LNCS 1000, 1995.
[39] Z. Somogyi, F. Henderson, and T. Conway. The
execution algorithm of Mercury, an efficient purely
declarative logic programming language. Journal of
Logic Programming, 29(1-3):17–64, 1996.

You might also like