Declarative Programming Style

This article is based on Real World Functional Programming. It is being reproduced here by permission from Manning Publications. Visit the book’s page for more information.
by Tomas Petricek

Declarative programming style

In this article, I’ll talk about two properties of functional languages that together define the
overall style of writing functional code. Most of the examples will be implemented in C# using the new
C# 3.0 functional features, however you’ll also see an example in F#.
In many functional languages every language construct is an expression. The languages try to
minimize the number of built-in concepts and are very succinct and extensible. We’ll see that
basic building blocks implemented in a functional language use recursion. However, writing every
operation explicitly using recursion would be difficult, so we’ll look how to write a single
function that can be used in many variations for different purposes.
These two concepts together define what a declarative programming style is, so after discussing them,
we’ll talk about the benefits of this style.

Functional programming and recursion

Functional programs try to minimze the use of mutable data structures and variables, which means that
most of the programs are written without real variables and instead use immutable values. Lets start by discussing
how to write a more complicated functions just using immutable values. For an example, we’ll use the following
function, which sums numbers in a specified range. This could be of course calculated directly, but well use
it as an example of a calculation which uses a loop and we’ll also see later how to change this code into
a more generally useful function:

int SumNumbers(int from, int to) {
    int res = 0;
    for (int i = from; i <= to; i++)
        res = res + i;
    return res;
}

In this case, we just cant directly rewrite the code not to mutate the value of res, because
we need to modify the value during every evaluation of the loop. The program needs to keep certain state
and the state is changed during every execution of the loop, so we cant declare a new variable for every
change of the state as we did in our earlier example. This means that we need to do a fundamental
change in the code and use a technique called recursion instead of using loops:

int SumNumbers(int from, int to) {
    if (from > to) return 0;
    int sumRest = SumNumbers(from + 1, to);
    return from + sumRest;
}

As you probably already know, recursion means that a function (SumNumbers in our case) calls itself
(in our case this is when we calculate a value for sumRest variable). In this code, were using only value
binding, so it is using only purely functional features. The state of the computation, which was originally
stored in a mutable variable, is now expressed using recursion. When I originally mentioned that we
cant declare a new variable for every change of the state I was actually wrong, because this is what our new
recursive implementation does. Every time the function recursively calls itself, it skips a first number and
calculates a sum of the remaining numbers. This result is bound to a variable sumRest, which exists
as a new variable during every execution of the recursive function, so this is how the state is represented
in our new implementation.

Of course, writing the recursive part of computation every time would be difficult, so functional languages
provide a way for “hiding” the difficult recursive part and expressing most of the problems without
explicitly using recursion.

Functions as values

The question that motivates this section is: “How can we separate the functionality that will vary during
every use from a recursive part of the code?” The answer is simple – we will write the recursive part as
a function with parameters and these parameters will specify what the “unique operation” that the function
should perform is.

Let me demonstrate the idea on a previous SumNumbers function. We wrote a function that takes an
initial value, loops through a specified numeric range and calculates a new “state” of the calculation during
every iteration using the previous state and the current number from the range. In the function above we used
zero as an initial value and we used addition as an operation that is used to fold the numbers in the
range, so a resulting computation for a range from 5 to 10 would look like (((((0 + 5) + 6) + 7) + 8) + 9) + 10.

What if we now decided to modify this function to be more general and allow us to perform computations
using different operations, for example multiply all numbers in the range and generate the following
computation: (((((1 * 5) * 6) * 7) * 8) * 9) * 10? If you think about the differences between these two computations,
you’ll see that there are only two changes. First, we changed the initial value from 0 to 1 (because we
don’t want to multiply any result of a call by zero!) and we changed the operator used during the computation.
Let’s see how we could write a function like this in C#:

int SumNumbers(Func<int, int, int> op, int init, int from, int to) {
   if (from > to) return init;
   int sumRest = SumNumbers(op, initial, from + 1, to);
   return op(from, sumRest);
}

We added two arguments to the function – the initial value (init) and an operation that specifies
what to perform with the intermediate result and a number from the range (op). To specify the second
argument, we’re using a delegate Func<int, int, int>, which represents a function that takes
two arguments of type int and returns an int. In functional languages, we don’t have to use
delegates, because they have much simpler concept – a function. In fact, the generic Func delegate in C# 3.0
represents exactly the same idea. This is exactly what the term “functions as values” refers to – the fact that we
can use functions in a same way as with any other data type available in the language. We can write
functions that take functions as an argument (as we did in this example), but also return function as
a result or even create a list of functions and so on. Now, before discussing the benefits of this approach, let’s
look at second key aspect that enables the declarative style, which is the syntax of functional languages.

Succinct and extensible syntax

As we already said, a key aspect of functional language syntax is that there is usually a small number of language
constructs that are defined explicitly by the language specification. This means that many typical constructs are
implemented as ordinary declarations in the library distributed with the language. As a result, you can implement a
set of primitives, use them to construct your programs and the programs will look very much as if the
primitives were standard part of the language.

To explain what this means in a practice, let’s look at the following example. This time, we’ll use a real
F# code, which we’ll implement later in the book. Demonstrating this on C# (or even non-existing “functional
C#”) would be quite difficult, because this aspect is easier to show on a language that was primarily
designed with functional programming in mind.

let solarSystem =
   sun
   -- (rotate 80.00f 4.1f mercury)
   -- (rotate 150.0f 1.6f venus)
   -- (rotate 215.0f 1.0f
        (earth -- (rotate 20.0f 12.0f moon)))

Even without knowing anything about F#, you can see what the program describes. It defines our solar system
with the first three planets (Mercury, Venus and Earth with Moon) and defines how the cosmic objects
rotate round each other. If you think about the code for a while, you can see that Moon rotates around the
Earth and Earth, together with two other planets, rotates around the Sun. The rotation is specified using
two numbers and if you could play with the program, you would quickly recognize that the first number specifies
how far the planet is from the center of rotation and the second specifies how fast it should rotate. You
can see a screenshot demonstrating the application below

The most surprising fact about this example is that if you didn’t know that the code is written in
F#, you could think that it is written in some kind of simulation language. You could think that
rotate is a keyword of this language (that defines how things rotate around each other) and that
-- is some connector used in simulations. In fact, rotate is an ordinary F# function
and -- is a custom operator, which you can easily define in F# according to your needs. Let’s
now look what do the last two concepts mean for the real-world development.

Benefits of the declarative style

The approach to define some basic constructs (like rotate and -- in our example) is very popular
and powerful. In context of F# it is often called language oriented programming. This term points
out, that we first design a “language”, using which we later model our problems. Aside from the definition
of the language, we also need a way for “executing” the model. In our example with solar system, the
execution of the model means to show a window with an animation of planets, but you could imagine other means of
execution, for example calculating possible intersections between planets and asteroids and many other possibilities.

Language Oriented Programmings

These languages are sometimes called DSLs (domain specific languages), which indicates that the language is designed to
describe problems from some specific domain. In some sense, it gives us a vocabulary and grammatical
rules for constructing the models. The key benefit of language oriented programming is that these languages can
be quite simple and easy to explain (our animation DSL is a good example), so they
can be in principle used even by those who understand the problem domain, but are not experts in functional programming. This
means that those who use the DSL can be a distinct people from those who design it and develop it. Indeed, there
are different requirements for people in both groups – the designers should be more advanced functional
developers, whereas for the users, it is more important to well understand the problem domain. This is very important aspect
for any pragmatic employer, who is considering using a functional programming language.

Of course, a language for writing animations is a very specific example. However, the same approach can
be used for less specific problems, such as working with collections of data. In this
example, we have a basic set of primitive constructs (operators) and we use them to specify what results we want to
get. This highly relies on the possibility to use functions as values, because this allows us
to specify part of the overall behavior of the operation as an argument. Another interesting aspect of the previous example
is that it doesn’t explicitly say how the animation should be executed and we’ll talk about this in the next subsection.

Declarative problem solving

The approach in which we model what results do we want is called declarative, because we declare what
properties should the result have instead of giving a step-by-step guide for the computer how to get the
result. Even though this may sound a bit abstract, we can demonstrate it using a very well known example.
Let’s look at the following SQL query:

SELECT [ID], [Name]
   FROM [Customers]
   WHERE [City] = "London"

This is a typical example of declarative programming style. It uses standard operators (SELECT and WHERE) to describe what results we want to get. The operators are parameterized by giving them two “functions” as arguments, even though we don’t write them as functions explicitly in SQL. The function is used as an argument to WHERE and it is a predicate specifying what customers should be selected. The function that parameterizes the SELECT operator specifies what properties of the customer should appear in the result.

The difference between SQL and declarative development in functional languages is that SQL works with a built-in set of primitive operators (such as SELECT and WHERE). On the other side, in functional languages, you can design and implement operators on your own for essentially any problem domain. Of course, this is exactly the idea of separating a language for solving problems from writing a code that solves a particular problem using that language, which we described above when talking about language oriented programming.

Let’s now look at the benefits of the declarative style. First of all, using this style it is possible to represent the ideas in a much terser way. This of course leaves more work to the underlying library and the computer, which is however the preferred way in those days. Instead of specifying unnecessary details, we want to the development process more productive and foolproof and the benefits that can be gained by executing the program for example automatically in parallel fully compensate the overhead that we may get by leaving more work to the underlying library.

We’ve seen that using declarative style and language oriented programming, the program is composed from simple operations (that is our “vocabulary”). Some of the functional languages have rather sophisticated ways for specifying how the simple operations can be composed together and for ensuring that this is done correctly. This is done using types and we’ll talk about it in the next section.

Summary

In this article I shortly discussed the idea behind declarative programming style. The key thing about
declarative style is that the program expresses “what should be don” rather than “how exactly it should be achieved”.
This makes the code more readable and also easy to implement. Once you’ve seen the planet simulation I presented here,
you don’t have to be an F# expert to be able to add other planets.

I also explained how the declarative style relates to functional programming. The ability to use functions as
values means that you can hide a lot of repetitive parts of code in a reusable unit. Many modern languages including
C# now include some of functional features and you can find a lot of inspiration in languages like F# that makes you
a better programmer even when working in C#.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read