Declarative programming for the rescue
By: Vladyslav Rybnikov
Tags: Fsharp, C Sharp Language, Declarative Programming, Programming Languages, Programming

Since the moment the first machine language program saw the world, thousands of programming languages ​​have been invented with the aim of simplifying programming and bringing machine language closer to human language. And now we are as close as possible to this idea.

Each time with the invention of a new compiler, we have come closer and closer to this, more and more abstracting over machine language.

Programming paradigms

Looking at the development of programming languages, they can be divided into certain stages of their formation. Each stage significantly improved both the syntax and the capabilities of the languages. Such stages of formation are called generation. Each of the generations of programming language aims to provide a higher level of abstraction under computer internals, making the language more user-friendly, powerful, and versatile. Today we can identify five main generations that form 2 main distinct groups

Low-level programming languages

1.1 First generation (1GL) - machine code

1.2 Second generation (2GL) - assembler High-level programming languages

2.1 Third generation (3GL) - machine-independent languages

2.2 Fourth generation (4GL) - domain-specific languages

2.3 Fifth generation (5GL) - logical programming

Functional programming languages, as well as object-oriented and procedural languages, are the main representatives of the third generation. These languages form 2 independent groups inside one generation: imperative and declarative. And the second one, declarative lies at the beginning of the next generations: domain-specific languages and logical programming.

In imperative programming, the code consists of successive calls to certain blocks (operations), which are a bit like the previous level of assembler languages. Declarative programming operates mainly with syntactic constructions that form code quite similar to a set of sentences of human language. That is, in imperative programming, the programmer specifies the specific operations to be performed by the computer, and in declarative programming describes the logic of the algorithm without the use of details in the form of constructions: if / else, for, while, etc. Usually, a declaratively written program looks like a sentence in ordinary, natural language, after reading which, you can quickly understand the workflow of the program. Let's dive into an example of calculating an average number in a set written on the C-like language (C#) :

Procedural

public static int AverageImperative(int[] arr)

{

var total = 0;

var i = 0; for(; i < arr.Length; i++)

{

total += arr[length]

} return total / i;

}

2. Declarative

public static int AverageDeclarative(int[] numbers) {

return numbers.Sum() / numbers.Length; }

Declarative programming

There is a significant difference in approaches to writing code in these two examples. If in the first case you need to have at least a basic understanding of C-like languages and syntax constructions like for, the second can be understood even by not technical folks! It just feels like a regular sentence: return a sum of numbers divided by number length. Using the power of C# extension methods we could even write something like this:

public static int Average(int[] numbers) {

return numbers.Sum().DividedBy(numers.Length);

}

And when we start to think in this direction we came into the functional way of programming things. For example, we could express the same statements as this set of functions using F#.

let average numbers = sumOf numbers |> dividedBy numbers.length

And in my opinion, this looks just impressive! Even if the language we use does not introduce any high-level constructions or libraries for declarative programming (like LINQ in C#), using the functional (declarative) approach we could hide all imperative stuff inside ‘helper’ functions and then use them inside our business logic.

This simple change in your codebase could completely change the way your code looks and feels. Such a little change could help non-technicians (business analysts, product managers) to read your code, you could even add some code blocks into documentation and be sure that it will be understandable for each team member. It could be the first steps to DDD, which could be the topic for a new story…

SQL and beyond

The most interesting moment in this idea of structuring code in a declarative way is not new at all! Even good old SQL was invented with this idea in mind. I think this is the main SQL feature that allowed language to stay afloat for almost 35 years from the first standardized version — SQL-86! Structured query language or SQL (sequel) belongs to the 4th programming language generation — domain-specific languages.

Using SQL, we do not think about how our query will be executed, we only describe what we want to get in the end.

Let's look at this simple query:

SELECT Name FROM Users WHERE Name LIKE '%Vlad%';

Writing such requests to our DB, we do not think about how these instructions are being executed under the hood.

If we had to write such queries in an imperative C-like language, we would get the following:

resultTable = new Table(); for (i = 0; i < rows.count; i++)

{

if(rows[i]["Name"].Contains("Vlad"))

{ resultTable.Add(rows[i]["name"]);

} } return resultTable;

So, there could be a reasonable question: ‘Why not use SQL all over the time ?’. The problem of maybe all the languages from the 4th generation is that they have very limited applicability because we could not write needed statements like SELECT for each situation…

At the top of these generations, there are 5th gen — logical languages like Prologue, but now they have the same problems as the domain-specific languages and used mainly in artificial intelligence.

Conclusion

Using a declarative/functional approach could make domain logic more readable and understandable for each team member, even if you use completely procedural or OOP language, simply by composing imperative parts into well-named functions!