Derek McDaniel

@derek7mc

When Procedural Is Better Than Declarative

Procedural programming allows you to reason through complex processes, which can help you solve otherwise unapproachable problems, even if more work is required.

Recently, I started following a magician/puzzle channel on youtube. The channel author is Chris Ramsey, and his videos are surprisingly satisfying to watch. If you’re wondering what this has to do with procedural and declarative programming, I promise you we’ll get to that, if you can just be patient for a minute.

Here’s a couple different puzzles that Chris has solved on his channel:

Puzzle 1: The Impossible Bottle

The Impossible Bottle Puzzle

Puzzle 2: Thor’s Hammer

Only those worthy can wield the hammer of Thor

So What’s The Difference?

I think puzzles are great metaphor for programming, that can help non-programmers understand the difficulty involved in programming. Often, we don’t know how long something will take, or what the best approach will be, until we make an effort. Two different programmers can approach and solve the same challenge in very different ways.

Both of these puzzles are labelled as “impossible”, but there is actually a very clear difference in the challenge involved with each. The impossible bottle allows you to clearly see what is going on, but the process of manipulating the parts and pieces is difficult to perform. If it weren’t for the bottle blocking access, it would be trivial to solve.

With the “Thor’s Hammer” puzzle, exactly the opposite is true. Each of the pieces is accessible to touch and manipulate, but the relationship between them is hidden inside, and not visible to the programmer. . . i mean, puzzle solver.

Declarative Programming Hides Process, Reveals Relationships

There are many different forms of declarative programming. One of the most recognized forms of declarative programming is functional programming. Functional programming is simply describing the mathematical relationship between inputs and outputs. It relies heavily on ideas like recursion and composition.

functions are simply mathematical maps between inputs and outputs

One of the most common examples, to highlight the pros and cons of functional vs imperative programming, is computing the Fibonacci sequence.

Here are a couple different ways to compute the fibonacci sequence in Haskell:

-- zip
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
-- recursive
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
n = 37
main = do
print "zip lists"
print (fibs !! n)
print (fibs !! (n+1))
print (fibs !! (n+2))
print (fibs !! 2000)
    print ""
print "recursive"
print (fib n)
print (fib (n + 1))
print (fib (n + 2))
-- edited Nov 2018

This code was adapted from this stack overflow question. Here is the description that was given to explain how the version with ‘zip’ works:

So the infinite list of Fibonacci numbers can be calculated by prepending the elements 1 and 1 to the result of zipping the infinite list of Fibonacci numbers with the tail of the infinite list of Fibonacci numbers using the + operator.

Even though implementing the fibonacci sequence using ‘zip’ is quite concise, there is a lot going on, as that answer demonstrates.

In the recursive Haskell version, we describe very specific things about the mathematical relationships involved. The type signature tells us this function takes an integer and returns another integer. Then we use a special haskell trick called pattern matching to describe the initial values for the function. From those values, we recursively describe how to compute further values.

Compare those implementations to this version in C I wrote:

#include <stdio.h>
int fibonacci(n){
int a = 1;
int b = 1;
int i;
int tmp;
for(i = 0; i < n - 1; ++i){
// swap a and b
tmp = a;
a = b;
b = tmp;
// add a and b
a = b + a;
}
return a;
}
int main(){
printf("\nfibonacci(0) = %d", fibonacci(0));
printf("\nfibonacci(1) = %d", fibonacci(1));
printf("\nfibonacci(2) = %d", fibonacci(2));
printf("\nfibonacci(3) = %d", fibonacci(3));
printf("\nfibonacci(4) = %d", fibonacci(4));
printf("\nfibonacci(5) = %d", fibonacci(5));
printf("\nfibonacci(6) = %d", fibonacci(6));
printf("\nfibonacci(10) = %d", fibonacci(10));
printf("\nfibonacci(20) = %d", fibonacci(20));
printf("\nfibonacci(40) = %d", fibonacci(40));
printf("\nfibonacci(50) = %d", fibonacci(50));
printf("\nfibonacci(100) = %d", fibonacci(100));
printf("\ndone");
printf("\n");
return 0;
}

This code needs to be explicit about what values are stored, how many steps are involved in the loop, etc. It can be confusing because there are often minor tweaks to handle edge cases, or proper initialization. In this case, we have to loop n -1 times, and it may not be clear why. Also, if you run the code, you will see that eventually it gives the wrong answer, because with procedural programming the programmer is responsible for things like overflow, which we don’t handle in this case. In haskell, the language will take care of that for us.

Declarative Programming is Great For Common Tasks Where You Need Help With The Details

Some may think that procedural and declarative programming are mutually exclusive, but usually, we mix the two approaches. One good example, is that even in procedural languages, it has become the norm to use declarative statements for memory management.

Think about the new keyword. This keyword exists in several languages, including, Java, C++, and Javascript, and it is actually mostly comparable in each one.

Here is an example from the online Java docs:

Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);

In C, malloc and free are used to manually allocate and unallocate memory on the heap. In these other languages, the new keyword handles a lot of that automatically for us. First of all, it determines what object we are going to be creating, and the size of memory required. Also, within the context of the expression, the new keyword indicates that the Object constructor should be called, with the provided parameters. Finally, an expression with new resolves to a value that can be used according to its object type. In the statement above it is used on the right hand of an assignment, but we could also call one of its methods anonymously, or pass it to another object’s method.

Like the impossible bottle puzzle, declarative programming makes the details inaccessible, but makes it easy to see the relationships involved.

Procedural Is Better When You Care About The Steps

Recently, I was trying to write some complicated MySQL queries, for a web development task. SQL is a perfect example of how declarative programming includes more than just functional programming.

For this particular problem, I needed to perform computations to transform my data, using data from different tables in the database. SQL is great for querying and selecting datasets, but it isn’t always the best choice for performing computations with that data.

Initially, I was using an imperative approach to process my data, on the server with django. But this proved to be too slow for my purposes, as the query would time out while the python interpreter was trying to perform the computations.

I then decided to move the computations into MySQL, to speed up the query time. While it looked like this would be fast enough, it soon became difficult to reason about the queries, steps, and the relationships involved. I found myself using loops and procedures in MySQL, which it supports, but isn’t the ideal use case for the language.

Instead, I am going to use SQL queries to select the appropriate datasets, and then perform the computations on the client with javascript. I did this previously for other stats I needed to compute from the database, and I really like this approach because the computations are offloaded to the client, so it’s an efficient use of resources, and the page renders quickly as well. Additionally, javascript is a great language for working with datasets in a flexible way, because of its JSON support, even if the browser isn’t the best environment for supporting mathematical or accounting computations.

Final Thought: Procedural Programming Gives You More Control, But Is it Worth It?

Our intrepid youtube puzzle solver, Chris Ramsey, decided to burn Thor’s hammer, instead of attempting to put it back together again, due to the sheer frustration and challenge involved in the task. But if he had kept track of his procedure for taking it apart, he could have simply reversed his steps to reassemble it. I think that is a good example of the benefit and drawbacks of procedural programming. Keeping track of all the steps involved, can allow you to perform more complex challenges, with greater control over the detail, but whether it is worth it is depends on the task, and whether it can be solved with a library or a different approach.

More by Derek McDaniel

Topics of interest

More Related Stories