Command yourself to declare

Command yourself to declare

Leer en Español

Declarative programming is a transformational trend in the industry that not only improves the programming paradigm but also enables the automation revolution.

Imperative

In the opposite corner, we have imperative programming, which means that a programmer would write the specific instructions for which actions would be performed and in which order. For example, if an application needed to copy a file the developer would call a function like this:

// pseudo-code
copy_file(/source,/target)

The user of the copy_file is issuing a command, to copy the file from the source to the target, and we can assume that the intention of the caller is that the file is made available at the target location, but the intention is not 100% clear from the imperative style. If we are implementing the imperative copy_file command, we have to either make some assumptions or ask the caller to make their intentions clear through some extra parameters (i.e. do_not_copy_if_exists: true).

// pseudo-code
function copy_file(source, target)
  sourceExists = file_exists(source)
  targetFolderExists = folder_exists(target)
  if sourceExists then
    if no targetFolderExists then
      create_folder(target)
    end if
    targetIsIdentical = compare_size_and_date(source,target)
    if targetIsIdentical then
      return success // You were told to copy, you did not copy... why success?
    else
      contents = read_file(source)
      write_file(contents,target)
      return success
    end if
  end if
  return failure
end function

This begs the question if the command needed extra arguments to clarify the intention, wouldn’t it make more sense for the user to express the intention without the command? Welcome to the world of “declarative” or “desired state” programming.

Declarative

Declarative programming is NOT replacing the imperative, procedural[1] style of programming, but enhancing the interface to the users by asking their intentions instead of instructions. The declarative model changes the paradigm and states that libraries should not provide the user with actions/verbs, instead they should allow the requestor to assert the desired final state and let the library or tool figure out how to get there. Of course, declarative programming is not a new thing, it has been used for decades in the data world in SQL programming, but it is making a huge rebirth in the world of automation. So, again, using pseudo-code:

ensureFileAvailable:
  whichFile: /source
  where: /target

We can see three immediate benefits from this approach over the imperative style:

  • Clear intentions. The user’s desire is clearly stated, “ensure the file is available at the target location”, without any imposition of how that happens. If that desire is accomplished the routine is successful and if not, then it fails, regardless of the actual actions required to make it a reality.
  • Idempotency. Developers and application owners should not feel afraid of executing declarative code twice or many more times. In the imperative paradigm a procedure of start_server will continue incrementing the number of servers every time it is called, in declarative, one should be able to state the final desired state (ensure_server_running) and the engine should be able to determine if it is already there, or if it needs to correct some drift[2], or start from scratch.
  • Portability. By staying away from the details of how the desire is to be realized, the interface has a better chance to be preserved over many different implementations. For example, file_copy makes sense in a local file system, but if the functionality gets extended to a file server the action would be a file transfer even though it is still called file_copy, and if it is implemented on a web environment, it will be a file upload. By enabling the user to express their final desired state, the interface tends to be less attached to the current implementation.

Conclusion

If you are thinking that there is still plenty of imperative programming under the covers to enable my little declarative example, you’ll be right. The desired state (pun intended) is not to rid the world of imperatives, but to improve the interface with the users. If that wasn’t enough, declarative programming is one of the key components that make modern automation possible, without it we wouldn’t have Ansible, Kubernetes, or Terraform, all of which depend on the concepts of declarative programming to deliver the awesome automation revolution we have seen in the last few years.


[1]: Structural or procedural programming is a style of imperative programming where the pure imperative “gotos” are replaced by functions and procedures.

[2]: Drift – In declarative programming, a drift happens when external factors change the state after a successful accomplishment of the desired state. Drift detection is a commonly used technique to watch for changes to the desired state and Drift reconciliation is the process by which the system is returned to the desired state after a drift.