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. |