When Procedural Is Better Than Declarative

Written by derek7mc | Published 2018/10/10
Tech Story Tags: functional-programming | procedural-programming | programming-paradigms | programming-languages | puzzle-solving

TLDRvia the TL;DR App

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

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:

-- zipfibs = 0 : 1 : zipWith (+) fibs (tail fibs)

-- recursivefib :: Int -> Intfib 0 = 0fib 1 = 1fib n = fib (n-1) + fib (n-2)

n = 37main = doprint "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 btmp = a;a = b;b = tmp;// add a and ba = 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.


Published by HackerNoon on 2018/10/10