Directive programming VS declarative programming

To summarize, we can write code in two ways: directive and declarative.

We can define it as follows:

  • Instruction programming: telling a machine what to do and getting the result it wants.
  • Declarative programming: Tell the machine what you want and let the machine calculate how to do it.

Examples of directive and declarative programming

As a simple example, suppose we want each value in the array to be twice as large.

The code for instruction programming can look like this:

Var count = [1,2,3,4,5]. i < numbers.length; I ++) {var newNumber = numbers[I] * 2. Push (newNumber)} console.log(Copy the code

We walk through the array, take out each element, multiply by 2, and then put the new value in the new array until we’re done.

A declarative way of programming can be array.map, as follows:

Var double = [1,2,3,4,5] var double = [1,2,3,4,5]Copy the code

Map returns a new array based on the old array, in this case, by passing elements from the old array into map (function(n) {return n*2}, it returns a new array with each value twice the corresponding value of the old array.

The map function abstracts out the process of traversing a set of numbers, making us more focused on what we want to get. Note that the function we pass into the map is pure. It can’t have any side effects (changing other extra states), it just takes a number and doubles it.

There are other common declarative abstraction functions for arrays. For example, to sum all the elements in an array, we can do this:

Var numbers = [1,2,3,4,5] var total = 0; i < numbers.length; i++) { total += numbers[i] } console.log(total) //=> 15Copy the code

Or we can use the declarative reduce function:

Var count = [1,2,3,4,5] var total = numbers. Reduce (function(sum, n) {return sum, 0); console.log(total) //=> 15Copy the code

Reduce computes a value by traversing the array using a given function. It applies this function to each element in the array. In each call, the first argument (sum in the example) is the result of the previous element calling the function, and the second argument (n) is the current element. So, in this example, at each step, we add the current array element N to sum, and then we end up with the sum of the entire array.

Similarly, Reduce abstracts the loop traversal and state management aspects for us, giving us a general way to iterate over a set of numbers to compute a value. All we need to do is figure out what we want.

It’s strange?

I guarantee you will be surprised if you haven’t seen Map or Reduce before. As programmers, we’re used to specifying how to make things happen, “iterate through arrays of numbers,” “if and then what,” “update this variable with a new value.” Why should we learn this seemingly strange abstraction when we already know how to tell machines what to do?

In many cases, directive code is good. When we write business logic, we usually have to write most of the necessary code because there is no more general abstraction in our business logic.

But if we take the time to learn (or build!) Declarative abstract methods that allow us to use powerful shortcuts when writing code. First, we generally write less and less, which is a quick win. Then, too, we can think at a higher level, standing in the clouds thinking about what we want to happen, rather than getting stuck in the mud thinking about how to make it happen.

SQL

You may not realize it, but you already use declarative programming in SQL.

You can think of SQL as a declarative query language for working with data sets. Have you ever written an entire application in SQL? Probably not. But it can be very powerful for working with associated data sets.

Make a query:

SELECT * from dogs
INNER JOIN owners
WHERE dogs.owner_id = owners.id
Copy the code

Imagine you write this logic in instruction programming:

//dogs = [{name: 'Fido', owner_id: 1}, {...}, ... ]  //owners = [{id: 1, name: 'Bob'}, {...}, ...]  var dogsWithOwners = [] var dog, owner for(var di=0; di < dogs.length; di++) { dog = dogs[di] for(var oi=0; oi < owners.length; oi++) { owner = owners[oi] if (owner && dog.owner_id == owner.id) { dogsWithOwners.push({ dog: dog, owner: owner }) } }} }Copy the code

I’m not saying SQL is easy to understand, or that it’s easy to understand when you first look at it, but it’s much simpler than that complex code.

But not only is it shorter and easier to read, SQL offers many other benefits. Since we’ve abstracted the concrete implementation, we can just focus on what we want and let the database optimize the concrete implementation steps.

If we didn’t use it, our own code would be slow because we would have to traverse the entire Owners array for every dog in the list.

But in the example of SQL code, we can leave it to the database to implement how to return the correct results to us. If it makes sense to use an index (assuming we already have one), the database will do so, which can lead to significant performance gains. If the query was executed a second ago, it can be read directly from the cache. By letting computers decide how to implement it, we can reap huge benefits by changing our perceptions only slightly.

d3.js

Another place where declarative programming can be beneficial is in user interfaces, diagrams, and animations.

Writing a user interface is difficult. Because we have user interaction, and to do user interaction well, we often have a lot of state management and directive code, which can be abstracted out, but usually isn’t.

A good example of declarative abstraction is d3.js. Using JavaScript and SVG (mostly), the D3 library helps us create interactive, graphically-driven visualizations of data.

The first (fifth, or even tenth) time you see or try to write D3 code, you probably have a headache. Just like SQL, D3 encapsulates almost all the ways you can work with visual data, letting you focus on what you want.

Here’s an example (I encourage you to look at this example for context). The D3 diagram draws a circle based on each object in the Data array. To show what’s going on, let’s add a circle every second.

The interesting code is:

//var data = [{x: 5, y: 10}, {x: 20, y: 5}]

var circles = svg.selectAll('circle')
                    .data(data)

circles.enter().append('circle')
           .attr('cx', function(d) { return d.x })
           .attr('cy', function(d) { return d.y })
           .attr('r', 0)
        .transition().duration(500)
          .attr('r', 5)
Copy the code

It’s not necessary to find out exactly what’s going on here (it’s going to take you a while to wake up anyway), but here’s the gist:

First we select all circlesVGs (none initially). And then bind some data to them.

D3 keeps track of which data points are bound to which circles in the diagram. So we start with two data points, but no circles; We can then use the.Enter () method to retrieve the data points that have been “entered”. For these points, we want to add a corresponding circle to the graph, centered on x and y of the data, with an initial radius of 0, but gradually changing to a radius of 5 after 0.5s.

Why is this interesting?

Take a look at the code again and ask if we have described what kind of visualization we want, or how to draw it? You’ll notice that there is very little code to draw. We just described what we wanted:

> < p style = “max-width: 100%; clear: both; And if there’s a new circle, add it, and animate the radius.

It’s amazing, we didn’t write a loop, we didn’t write state management. Writing diagrams is often difficult and cumbersome, but D3 has done most of the wrapping for us, we just need to figure out what we want.

Is d3.js easy to understand now? No, it definitely takes a while to learn. And much of what you’re going to learn is to give up dictating how things happen and learn how to figure out what you want.

This is difficult at first, but after a few hours something amazing happens — you become more and more productive. By encapsulating the implementation, D3.js really lets you focus on what you want to see, and that’s all you need to focus on when you implement visualization. It frees you from tedious details, allows you to think at a higher level, and opens up creative possibilities.

The last

Declarative programming allows us to describe what we want and let the underlying software/computer etc deal with how to achieve it.

As you can see, a lot of times, this allows us to get better at writing code, not just fewer lines of code, or performance, but the high abstraction of coding allows us to focus more on what we want, which is what we as problem solvers really care about.

The problem is, we used to be used to instruction programming. It makes us feel comfortable and natural, even powerful (able to control how it happens), refusing to hand over the implementation to programs we can’t see or understand.

Sometimes it is ok to write concrete implementations. If we need to tweak the code to improve performance, we may need to specify in more detail how to do it. Or for business code, there is nothing inherently abstracted or encapsulated, so we write directive code.

But we can (and should) always look for declarative ways to write code, and if we can’t find them, we should build them. Was it difficult at the beginning? Yes, almost certainly! But as we’ve seen with SQL and D3.js, the long-term benefits are huge!