HAL
HAL is a strongly typed, weakly moded, constraint logic/functional language
designed to support the construction and extension and use
of new constraint solvers
being developed jointly at the University of Melbourne and Monash University.
|
|
Constraint logic programming (CLP) languages are evolving to support more
flexible experimentation with constraint solvers. First generation CLP
languages, such as CLP(R) [Jaffar et al
92], provided almost no support. They had a fixed underlying solver for
each constraint domain which was viewed as a closed ``black box.'' Second
generation CLP languages, such as clp(fd) [Diaz
and Codognet 93], provided more support by viewing the solver as a
``glass box'' which the programmer could extend to provide problem-specific
complex constraints. However, CLP programmers want more than this: they
want to be able to develop new problem-specific constraint solvers, for
example by using ``hybrid'' methods that combine different constraint
solving techniques (see e.g. [Wallace
98]). For this reason, recent versions of the CLP languages ECLiPSe
and SICStus support the addition and specification of new constraint
solvers by providing features such as dynamic scheduling, constraint
handling rules [Fruhwirth 98] and attributed
variables [Holzbaur92]. Unfortunately,
support for developing solvers in these languages is still less than
satisfactory for the reasons detailed below.
HAL is a new CLP language, explicitly designed to support experimentation
with different constraint solvers and development of new solvers. Our
specific design objectives were four-fold:
- Efficiency: Current CLP languages are considerably slower than
traditional imperative languages such as C. This efficiency overhead has
limited the use of CLP languages, and becomes even more of an issue when
constraint solvers are to be (partially) implemented in the language
itself.
- Integrability: It should be easy to call procedures (in particular,
solvers) written in other languages, e.g.~C, with little overhead.
Conversely, it should be possible for HAL code to be readily called from
other languages, facilitating integration into larger applications.
Although most CLP languages provide a foreign language interface, it is
often complex and may require rewriting the foreign language code to use
``safe'' memory management routines.
- Robustness: Current CLP languages provide little compile-time
checking. However, when developing complex multi-layered software such as
constraint solvers and when calling foreign language procedures, additional
compile-time checking can detect common programming errors and so improve
program robustness.
- Flexible choice of constraint solvers: It should be easy to ``plug
and play'' with different constraint solvers over the same
domain. Furthermore, it should be straightforward to extend an existing
solver, create a hybrid solver by combining solvers and to write a new
constraint solver.
HAL has four interesting features which allow it to meet these objectives.
The first is semi-optional type, mode and determinism declarations for
predicates and functions. Information from the declarations allows the
generation of efficient target code, improves robustness by using
compile-time tests to check that solvers and other procedures are being
used in the correct way, and facilitates efficient integration with foreign
language procedures. Type information also means that predicate and
function overloading can be resolved at compile-time, allowing a natural
syntax for constraints even for user-defined constraint solvers.
The second feature is a well-defined interface for solvers. Solvers are
modules which provide various fixed predicates and functions for
initializing variables and adding constraints. Obviously, such an
interface supports ``plug and play'' experimentation with different
solvers.
The third feature is support for ``propagators'' by means of a specialized
delay construct. HAL allows the programmer to annotate goals with a delay
condition which tells the system that execution of that goal should be
delayed until the condition is satisfied. By default, the delayed goal
remains active and is reexecuted whenever the delay condition becomes true
again. Such dynamic scheduling of goals is useful for writing simple
constraint solvers, extending a solver and combining different solvers.
The fourth feature is a provision for ``global variables.'' These behave a
little like C's static variables and are only visible within a module. They
are not intended for general use; rather they allow the constraint solver
writer to efficiently implement a persistent constraint store.
A well-defined solver interface and global variables are, to the best of
our knowledge, novel in the context of CLP. While declarations and dynamic
scheduling are not new, incorporating them into a CLP language which also
allows user-defined constraint solvers has proven challenging. In
isolation, each feature is relatively well understood; it is their
combination that is not. Major difficulties have been to provide (limited)
automatic coercion between types, compile-time reordering of literals
during mode-checking with appropriate automatic initialization of solver
variables, and efficient, yet accurate mode and determinism checking in the
presence of dynamic scheduling. Dynamic scheduling has also complicated the
design of the solver interface since the choice and implementation of delay
conditions is necessarily solver dependent. One interesting feature has
been the need to provide an external and internal view of the type and mode
of a solver variable.
As a simple example, the following program is a HAL version of the now
classic CLP program mortgage for modelling the relationship
between P the principal or amount owed, T the number of periods in the
mortgage, I the interest rate of the mortgage, R the repayment due each
period of the mortgage and B the balance owing at the end.
:- module mortgage. (L1)
:- import simplex. (L2)
:- export pred mortgage(cfloat,cfloat,cfloat,cfloat,cfloat). (L3)
:- mode mortgage(in,in,in,in,out) is semidet. (L4)
:- mode mortgage(oo,oo,oo,oo,oo) is nondet. (L5)
mortgage(P,0.0,I,R,P). (R1)
mortgage(P,T,I,R,B) :- (R2)
T >= 1.0,
NP = P + P * I - R,
mortgage(NP,T-1.0,I,R,B).
The first line of the file (L1) states that this is the definition of the
module mortgage. Line (L2) imports a previously defined module called
simplex. This provides a simplex-based linear arithmetic constraint
solver for constrained floats, called cfloats. Line (L3) declares
that this module exports the predicate mortgage which takes five {\tt
cfloat}s as arguments. This is the \emph{type} declaration for {\tt
mortgage}. Lines (L4) and (L5) are examples of \emph{mode of usage}
declarations. Since there are two declarations, mortgage has two
possible modes of usage. In the first, the first four arguments have an
in mode meaning their values are fixed when the predicate is called,
and the last has a mode out which means it is uninitialized when
called, and fixed on the return from the call to mortgage. Line (L5)
gives another mode for the mortgage where each argument has mode {\tt
oo} meaning that each argument takes a ``constrained'' variable and returns
a ``constrained'' variable. This more flexible mode allows arbitrary uses
of the mortgage predicate, but will be less efficient to execute. Line
(L4) also states that for this mode mortgage is semidet,
meaning that it either fails. For example
mortgage(0.0,-1.0,0.0,0.0,B) fails or succeeds with exactly
one answer. For the second mode (L5) the determinism is nondet
meaning that the query may return 0 or more answers.
The rest of the file contains the standard two rules defining {\tt
mortgage}. The first states that when the number of repayments is 0, then
the balance is simply the principal. The second states that if the number
of repayments is greater than one, then we make one repayment, compute the
new principle NP and take out a mortgage with this new principle for
one less time period.
As an example of the advantages of overloading, imagine that we wish to
write a module for handling complex numbers. We can do this by leveraging
from the simplex solver.
:- module complex.
:- import simplex.
:- export_abstract typedef complex -> c(cfloat,cfloat).
:- export pred cx(cfloat,cfloat,complex). % access/creation
:- mode cx(in,in,out) is det.
:- mode cx(out,out,in) is det.
:- mode cx(oo,oo,oo) is semidet.
cx(X,Y,c(X,Y)).
:- export func complex + complex --> complex. % addition
:- mode in + in --> out is det.
:- mode oo + oo --> oo is semidet.
c(X1,Y1) + c(X2,Y2) --> c(X1+X2,Y1+Y2).
Note that the type definition for complex is exported abstractly,
which means that the internal representation of a complex number is hidden
within the module. This ensures that code cannot create or modify complex
numbers outside of the complex module. Thus, this module also needs
to export a predicate, cx, for accessing and creating a complex
number. As this example demonstrates, the programmer can use
functions. The symbol ``-->'' should be read as ``returns.''
Using this module the programmer can now use complex arithmetic as if it
were built into the language itself. If both simplex and {\tt
complex} are imported, type inference will determine the type of the
arguments of each call to + and appropriately qualify the call with
the correct module.
As an example of solver implementation, consider implementing a simple
integer bounds propagation solver. The following declarations can be used:
:- module bounds.
:- export_abstract typedef cint = ... %% internal cint representation
:- export pred init(cint).
:- mode init(no) is det.
:- export_only pred cint = cint.
:- mode oo = oo is semidet.
:- coerce coerce_int_cint(int) --> cint.
:- export func coerce_int_cint(int) --> cint.
:- mode coerce_int_cint(in) --> out is det.
The type cint is the type of bounds variables. It might be
defined, for example, as an integer indexing into a HAL global variable
tableau, as an integer representing a CPLEX variable number, or as a C
pointer for a solver implemented in C. Usually, a constraint solver type
is exported abstractly. This ensures other modules cannot modify
cint variables without calling module bounds.
The init predicate is used to initialize a variable, its mode is
no or new -> old. The compiler will automatically add
appropriate calls to the initialization predicate in user code that makes
use of the bounds solver.
The equality predicate is required for all constraint solvers. In the
above code for cints it is annotated as export_only.
This indicates that it should be visible to importing modules but not
within this module. This is useful because inside the module
bounds we manipulate the internal (rather than the external)
representation of cints using equality. We can, however, access
the equality predicate by using explicit module qualification.
A complete bounds module would also export declarations for
(primitive constraint) predicates such as >=, <=, \==,
as well as functions such as +, - and *.
Automatic coercion is important because it allows constraints and
expressions to be written in a natural fashion. Currently, the type system
for HAL only supports simple coercion, namely, a type can be involved in at
most one coercion relationship. Even handling this restricted kind of
coercion is difficult and is one of the most complex parts of the type
checking and inference mechanism.
Automatic solver-dependent type coercion allows constraints and expressions
to be written in a natural fashion. In this case, we would like to be able
to write integer constants as cint arguments of predicates and
functions, for example as in X + 3*Y >= Z + 2. Thus, we need to
instruct the compiler to perform automatic coercion between an int
and a cint. The coerce declaration does this, indicating
that the coercion function is coerce_int_cint.
- Monash University (Australia):
- Melbourne University (Australia):
- Katholieke Universiteit Leuven (Belgium)
- IC-PARC (United Kingdom)
- Universite de La Reunion (La Reunion- France)
- Other people that have participated in HAL
- Dr. David Jeffery
- Nick Nethercote
- Dr. Peter Schachte
- Greg Denehey
- Michael Taylor
- Extending arbitrary solvers with constraint handling rules.
G.J. Duck, P.J. Stuckey, M. Garcia de la Banda, and C. Holzbaur. In Proceedings of
the ACM-SIGPLAN
International Conference on Principles and Practice of Declarative
Programming-- PPDP'2003 , ACM, in print. Sweeden 2003
Postscript.
- Vimer: a visual debugger for Mercury.
M. Cameron, M. Garcia de la Banda, K. Marriott, and P. Moulder.
In Proceedings of the ACM-SIGPLAN
International Conference on Principles and Practice of Declarative
Programming-- PPDP'2003 , ACM, in print. Sweeden 2003
Postscript.
- To the Gates of HAL: A HAL Tutorial.
M. Garcia de la Banda, B. Demoen, K. Marriott, and P.J. Stuckey:
In Proceedings of the International
Symposium on Functional and Logic Programming-- FLOPS 2002, LNCS,
pages 47-66, Japan 2002
Postscript.
- Trailing analysis for HAL.
T. Shrijvers, M. Garcia de la Banda, and B. Demoen.
In Proceedings of the Eighteenth International Conference on Logic
Programming-- ICLP'2002, LNCS,
pages 38--53, Denmark 2002
Postscript.
- Building constraint solvers with HAL.
M. Garcia de la Banda, D. Jeffery, K. Marriott, P.J. Stuckey,
N. Nethercote, and C. Holzbaur.
In Proceedings of the Seventeenth International Conference on Logic
Programming-- ICLP'2001, LNCS,
pages 90--1004, December 2001
Postscript.
- Optimizing compilation of constraint handling rules.
C. Holzbaur, P.J. Stuckey, M. Garcia de la Banda, and
D. Jeffery.
In Proceedings of the Seventeenth International Conference on Logic
Programming-- ICLP'2001, LNCS,
pages 78--89, December 2001
Postscript.
- A Model of Inter-module Analysis and Optimizing
Compilation.
F. Bueno, M. Garcia de la Banda, M. Hermenegildo, K. Marriott, G. Puebla, and P.J. Stuckey. International Workshop on Logic-based Program
Synthesis and Transformation: Selected Papers LNCS 2042, pages 86-102,
2001.
Postscript.
- Mode Checking in HAL.
M. Garcia de la Banda, P. Stuckey, W. Harvey, and K. Marriott.
In Proceedings of the First
International Conference on Computational Logic,
LNCS 1861, 1270--1284. Springer-Verlag, July 2000.
Postscript.
- Type constraint
solving for parametric and ad-hoc polymorphism.
B. Demoen, M. Garcia de la Banda, and P. Stuckey. In Proceedings of
the 22nd Australian Computer Science Conference, pages 217--228, January
1999. Postscript.
The benchmarks used in this paper can be found in ....
- An overview of HAL.
B. Demoen, M. Garcia de la Banda, W. Harvey, K. Marriott, and P. Stuckey.
In Proceedings of Principles and Practice of
Constraint Programming, pages 174--188, October 1999.
Postscript.
The benchmarks used in this paper can be found in ....
- Herbrand constraint solving in HAL.
B. Demoen, M. Garcia de la Banda, W. Harvey, K. Marriott, and P. Stuckey.
In Proceedings of the
International Conference on Logic Programming, pages 260--274, December 1999. Postscript. The benchmarks used in this paper can be found in ....
Currently, the HAL compiler, system and libraries consists of some 24,000
lines (comments and blank lines excluded) of HAL code (which is also
legitimate SICStus Prolog code). HAL programs are compiled to Mercury
(particular subsets of HAL can also be compiled to SICStus Prolog) which,
in turn, compiles to C and makes use of the information in declarations to
produce efficient code.
Current features:
- Global Variables: behave a little like C's static variables and
are only visible within a module. They are not intended for general
use; rather they allow the constraint solver writer to efficiently
implement a persistent constraint store.
- Dynamic Scheduling: HAL includes a form of ``persistent'' dynamic
scheduling designed specifically to support constraint solving. It
allows the programmer to annotate goals with a delay condition which
tells the system that execution of that goal should be delayed until
the condition is satisfied. By default, the delayed goal remains
active and is reexecuted whenever the delay condition becomes true
again.
- Well-defined solver interface: solvers are modules which provide
various fixed predicates and functions for initializing variables and
adding constraints. Such an interface supports ``plug and play''
experimentation with different solvers.
- Type analysis: HAL performs type checking and inference. Partial type
information may be expressed by using a `?' in place of an argument
type. The analysis algorithm is based on a constraint view of types and
is described in [Demoen et al 99a].
- Type classes: HAL supports multi-parameter type classes,
and the new
solver interface under development will be based on type classes.
- Mode analysis: currently HAL does not perform mode inference, but
does perform mode checking, including a restricted polymorphic version
of mode checking for high order modes.
- Determinism analysis: currently determinism declarations are
checked by HAL, but not inferred.
- Intra-module analysis: currently HAL requires full type, mode and
determinism declarations for exported predicates and functions.
- Herbrand Solver: in general HAL provides an equality predicate that
only supports modes for constructing a new term, deconstructing a bound
term or comparing two bound terms. More complex constraint solving for
a type is available by explicitly declaring the type as Herbrand. HAL
provides a Herbrand constraint solver for each term type defined as
Herbrand by the programmer.
- Integer solver: It is a bounds propagation solver which keeps linear
constraints in a tableau form, and simplifies them during execution to
improve further propagation.
It was described in [Harvey and Stuckey 98] and
was originally embedded in CLP(R)s compiler and runtime system,
yielding the language CLP(Z). It has since been interfaced to Mercury,
and then to HAL via the Mercury interface.
- Real solver: it is the solver from CLP(R) interfaced to HAL in the
same way.
Currently under development:
- Termination analysis (Fred and Seb)
- Generic analyser. (Nick)
- Solver type classes. (Maria, Peter, Kim, David)
- Extended type class facilities. (Peter, David)
- Interface to CPLEX solver. (David)
- Input/output, error messages, debugging, etc. (Maria)
Broadly speaking, HAL unifies two recent directions in constraint
programming language research. The first direction is that of earlier CLP
languages, including CLP(R), clp(fd), ECLiPSe and SICStus. The second
direction is that of logic programming languages with declarations as
exemplified by Mercury [Somogyi et al 96].
Earlier CLP languages provided constraints and constraint solvers for
pre-defined constraint domains and many provided dynamic
scheduling. However, they did not allow type, mode and determinism
declarations. Providing such declarations has influenced the entire design
of HAL, from the module system to delay constructs. Another important
difference is explicit language support for extending or writing constraint
solvers. Like HAL, the Mercury language also provides type, mode and
determinism declarations. It is probably the most similar language to HAL,
and we have leveraged greatly from its sophisticated compilation support by
using it as an intermediate target language. The key difference is that
Mercury is logic programming based and does not support constraints and
constraint solvers. Indeed, it does not even fully support Herbrand
constraints since it provides only a limited form of unification.
We know of only one other language that integrates declarations into a CLP
language: CIAO [Hermenegildo et al
99]. The design of CIAO has proceeded concurrently with that of
HAL. HAL has been concerned with providing a language for experimentation
with constraint solvers. In contrast, CIAO has focused on exploring the
design and use of more flexible declarations for program analysis,
debugging, validation, and optimization, and on supporting parallelism and
concurrency. Constraint solving in CIAO is inherited from the underlying
\&-Prolog/SICStus Prolog implementation: solvers are written using
attributed variables. Thus, CIAO does not provide an explicit solver
interface, does not provide a dual view of constraint variables, requires
the programmer to explicitly insert initialization and coercion predicate
calls, and, if more than one (non-Herbrand) constraint solver is used, the
programmer must explicitly call the appropriate solver.
- [Demoen et al 99a]
B. Demoen, M. Garcia de la Banda, and P.J. Stuckey.
Type constraint solving for parametric and ad-hoc polymorphism.
In Procs. of the 22nd Australian Comp. Sci. Conf. , pages
217--228, 1999.
- [Diaz and Codognet 93]
D. Diaz and P. Codognet.
A minimal extension of the WAM for clp(fd).
In Procs. of ICLP93, pages 774--790, 1993.
- [Fruhwirth 98]
T. Fruhwirth.
Theory and practice of constraint handling rules.
Journal of Logic Programming, 37:95--138, 1998.
- [Harvey and Stuckey 98]
W. Harvey and P.J. Stuckey.
Constraint representation for propagation.
In Procs. of PPCP98, pages 235--249, 1998.
- [Hermenegildo et al 99]
M. Hermenegildo, F. Bueno, D. Cabeza, M. Garcia de la Banda,
P. Lopez, and G. Puebla.
The CIAO multi-dialect compiler and system.
In Parallelism and Implementation of Logic and Constraint Logic
Programming. Nova Science, 1999.
- [Holzbaur92]
C. Holzbaur.
Metastructures vs. attributed variables in the context of extensible
unification.
In Procs. of the PLILP92, pages 260--268, 1992.
- [Jaffar et al 92]
J. Jaffar, S. Michaylov, P. Stuckey, and R. Yap.
The CLP(\cal R) language and system.
ACM Transactions on Programming Languages and Systems,
4(3):339--395, 1992.
- [Somogyi et al 96]
Z. Somogyi, F. Henderson, and T. Conway.
The execution algorithm of Mercury: an efficient purely declarative
logic programming language.
Journal of Logic Programming, 29:17--64, 1996.
- [Wallace 98]
M. Wallace, editor.
CP98 Workshop on Large Scale Combinatorial
Optimiza tion and Constraints, 1998.
http://www.icparc.ic.ac.uk/~mgw/chic2_workshop.html.
Disclaimer