Declarative Strategies for Solving Software Problems
By Roy Furman, M.D., Ph.D., Scientific Software Tools, Inc.
Many software and hardware producers take pride in the exponential pace of technology
change, but for users and consumers of their products and services the rapid technological
obsolescence often means increased costs, frustrations, and unfulfilled promises.
Corporate America expects to make capital investments in goods and facilities that
should last five, ten, even twenty years, but only an eighteen-month lifetime for
computer software and hardware investment is not uncommon.
Lowering the costs to develop new software solutions or extending the lifetime
of software applications are two complementary approaches to addressing technological
change. These goals can often be met by taking a declarative strategy when designing
software systems independent of the programming methodology employed.
Issues with Imperative Programming
Most programming projects today use the imperative style of programming. Developers
write sequences of operations in a language, such as C++, Java, Visual Basic, etc.,
that implement an algorithm, or recipe, for performing tasks. The algorithm for
the task mixes logical, or relational, statements about the task to be solved and
control statements about how to calculate the solution. The logical statements describe
"what-to" calculate while the control statements describe "how-to" calculate. Debugging
the algorithm consists of verifying the accuracy of the logical statements and fixing
the control statements, if necessary.
There are many problems with the imperative approach. The sequence of operations
critically determines the correctness of the algorithm. Unexpected execution sequences
through an algorithm caused by user input actions or real-time events in a multitasking
environment may result in subtle or catastrophic algorithm failure. Writing the
control logic is the programmer's responsibility and, therefore, subject to implementation
errors. Understanding a program's algorithm is often difficult for other developers
without extensive metadata, or comments, on the code and empirical tracing of the
program's execution with sample data. Verifying program correctness consumes a significant
portion of the development effort, but also usually fails to discover a significant
number of defects.
To address the problems associated with imperative programming, the computer
industry has developed and advocated many approaches. Structured programming and
campaigns against "go-to" statements address some of the problems discovered with
ad hoc control structures and statements. Modularization initiatives stress decomposition
techniques on the premise that humans can better comprehend, reason about, and maintain
smaller pieces of code. Object-oriented programming advocates program constructions
using reusable components, libraries, and frameworks. The pattern programming school
stresses analogies to other fields, such as architecture, by constructing programs
using well-designed and crafted solutions, or patterns, that recur in many programming
contexts.
What is Declarative Programming?
Declarative programming separates the logic, or what, of an algorithm from the
control, or how, of an algorithm. The programmer still specifies the logic or equations
specifying the problem's relations, but the programming system is responsible for
control, or how the logic is evaluated. The most familiar examples are spreadsheets
and query languages for relational databases. The user, or programmer, specifies
a mathematical relation as a query, say in SQL, for what to retrieve, while the
database engine determines how to execute the query against the database.
There are many advantages to declarative programming over the imperative style.
In declarative languages, programmers do not specify sequences of operations, but
only definitions or equations specifying relations. Unlike imperative programming,
the logic relations in declarative programming are execution order independent,
free of side effects of evaluation, and semantically clear to visual inspection.
The declarative family of programming languages has a long history in the academic
computer science community and specialized areas of commercial application, such
as compiler construction, expert systems, and databases. Declarative languages have
two main family trees. The logic declarative languages, such as Prolog, are based
on first-order predicate calculus, which generalizes the notions of Aristotelian
true or false values to statements, or predicates, involving relations among any
entities. The other family branch consists of functional declarative languages,
such as Miranda, Haskell, and SML. The functional declarative languages are based
on the l-calculus developed by the mathematician, Alonzo Church in the 1930's. l-calculus
formalizes the notions of recursive application of pure functions to computable
problems. Although not widely known as such, the latest programming fashion, XSLT,
an extensible stylesheet language for transforming XML, is also a functional declarative
language.
Despite the theoretical advantages of declarative programming languages, they
do not have widespread use in commercial programming practice despite an attempt
in the 1980's by Borland to mass-market a PC version of Prolog along with the highly
popular Turbo Pascal. There are many factors contributing to the infrequent use
of declarative languages. A large contributor is the paucity of collegiate training
in declarative languages, but awkward syntaxes of some languages, inefficient compilers
and run-times, and restricted domains of applicability of generalized "how-to" mechanisms
are all contributors.
Using Declarative Strategies in Commercial Software
While declarative programming languages have not received wide-spread commercial
usage, the strategy of separating logic, or what, from control, or how, in an algorithm
is a powerful, generalized technique for increasing ease of use and extending the
longevity of software. Declarative techniques are particularly powerful in user
interfaces and application programming interfaces (APIs) that have a rich, complex
set of inputs over a relatively small field of execution behaviors.
Two examples of commercial software that illustrate the applicability of declarative
techniques are
DriverLINX® and
ExceLINX™ in the fields of data acquisition and test instrument control.
Using Declarations for Data Acquisition
DriverLINX is an API for controlling data-acquisition hardware used to measure
and generate analog and digital signals interfaced to all types of external transducers.
Data-acquisition applications include laboratory research, medical instrumentation,
and industrial process control.
Traditionally, APIs for data-acquisition devices modeled the characteristics
of the hardware design and had a large number of functions of one or more parameters
to setup the hardware and control data flow through the system. The ordering of
sequences of operations was often critical to correctly programming and controlling
the hardware. Upgrading to new data-acquisition hardware was often costly as hardware-necessitated
changes in the order of operation sequences to program the hardware required costly
software changes.
To surmount these problems, DriverLINX takes an abstract and declarative approach
to data-acquisition programming. Instead of modeling specific board designs, DriverLINX
abstracts the functional subsystems of data-acquisition hardware into generalized
attributes and capabilities. Programs request the measurement task they want to
perform by parameterizing a "service request" declaration. The DriverLINX runtime
determines how to satisfy the service request using the available hardware and returns
the measurements as a packetized stream to the program. The data-acquisition programmer
is relieved of any responsibility for data-acquisition algorithm control.
Besides relieving the programmer of control responsibility, the DriverLINX abstract
declarative approach gives the program syntactic and semantic interchangeability
when migrating to equivalent hardware products. The abstract, declarative approach
also helps isolate the software vendor from early technological obsolescence of
change in the computer industry by focusing on the immutable logic of data-acquisition
relations while the control mechanisms vary with software developments. DriverLINX
has been a viable approach to data-acquisition programming for more than 12 years
despite the market evolution from 16-bit Windows to .NET today.
Using Declarations for Test Instruments
Test instruments, such as digital voltmeters and electrometers, have evolved
from simple devices with a front panel knob and display screen to sophisticated
measurement processors performing dozens of measurement and control functions. Like
data-acquisition devices, typically developers send a carefully ordered sequence
of commands to an instrument to setup the measurement and then send additional command
sequences to control the data flow of measurements from the instrument. The aforementioned
problems for developers using imperative approaches to instrument control significantly
limit ease of use and prohibit quick instrumentation solutions to short-term measurement
needs.
ExceLINX is an add-in to Microsoft Excel that allows rapid specification of instrument
test setups by using worksheet forms. Users specify, or declare, the channels, configurations,
sampling rates, triggering, and data locations for the measurements they wish to
perform by filling out an Excel worksheet. When the user selects the "start" button
on the toolbar, ExceLINX translates the specification into the correct command sequence
for the target instrument, initiates the measurement, and flows the data back to
the requested worksheet. Users can setup and collect measurements by themselves
in minutes using logic specifications compared to days or weeks using programmer's
time for imperative specifications.
Internally, ExceLINX also uses a declarative approach to handling the complex
problem of field validation for the worksheet forms. Instruments have hundreds of
parameters with complex overlaps among parameters. To validate whether the instrument
supports the parameter set the user selected, ExceLINX maintains a dependency tree
of allowed, disallowed, and unused parameters for every input cell on the worksheet.
Each node in the tree also maintains logical relations among the selected set of
parameters that ExceLINX evaluates at runtime to cross validate user input selections.
Each supported instrument model has different parameter semantics, but ExceLINX
can easily handle this complexity by switching model trees because the model-specific
logic in the validation tree is separate from the shared control implementation
in the ExceLINX code.
Declarative programming strategies that separate logic from control in algorithms
are powerful techniques that can be used with today's popular imperative languages.
These techniques can make software more interchangeable, maintainable, usable, and
endurable.