Like most web developers, I spend my days giving instructions to computers. These instructions generally involve some input (a request for a web page), some logic (get the right content from a database) and some output (send the content to the requesting browser). This process of telling a computer how to perform a task, such as generating a web page, is what we commonly call “programming,” but it’s only a subset of programming: imperative programming.
There’s another type of programming, declarative programming, that most web developers also use every day but don’t often recognize as programming. With declarative programming, we tell a computer what, not how. We describe the result we want, and the details of how to accomplish it are left to the language interpreter. This subtle shift in approach to programming has broad effects on how we build software, especially how we build the future web.
Further Reading on SmashingMag:
- An Introduction To Programming Type Systems
- An Introduction To Redux
- An Introduction To Full-Stack JavaScript
So, let’s take a moment to investigate declarative programming and the web we can build with it.
Hidden In Plain Sight
Declarative languages tend to fade into the background of programming, in part because they’re closer to how we naturally interact with people. If you’re talking with a friend and you want a sandwich, you don’t typically give your friend step-by-step instructions on how to make the sandwich. If you did, it would feel like programming your friend. Instead, you’re far more likely to talk about the result you want, such as “Please make me a sandwich” (or, perhaps, “Sudo make me a sandwich”). If your friend is willing and able to follow this instruction, then they would translate the phrase “Make me a sandwich” into a series of steps, such as finding a loaf of bread, removing two slices, applying toppings, etc.
This type of result-focused instruction is how declarative programming works, by leaving the logic of how to implement requests to the system that is interpreting the language (for example, your friend). When we want an image in an HTML document, for example, we simply include an <img>
tag, and then the system that interprets the HTML (typically, a browser) would handle all of the steps needed to display that image, such as fetching it from a server, determining where exactly to render it, decoding the binary data, scaling the image and rendering it to the screen. We don’t have to explain any of this, so we often forget that it’s all happening and that someone programmed both how it happens and how that complex process is derived from a simple <img>
.
Another factor that makes declarative programming hard to see as programming on the web is that it “just works.” A lot of work went into making languages like HTML, CSS and SQL capable of providing enough clarity on what needs to be accomplished that the steps required to achieve a result can be determined without detailed instruction. But most web developers began using these declarative languages long after the hard work of building them was complete, so we just see them as normal and ordinary and just a natural part of the way the web works.
When web developers do get involved in declarative programming before the interesting work is done, it’s typically while developing an API for a website. Most APIs are implemented via declarative programming. Rather than provide a way to give a website step-by-step instructions, APIs usually have a simple language that can be used to express the desired result. When we want to get some tweets from Twitter’s API, for example, we give a description of the tweets we want, such as “everything from @A_single_bear.” If the API is imperative, we would instead describe the specific steps we want Twitter to implement on our behalf, explaining how to load, format and return the tweets. Thankfully, the API hides all of that logic behind a simple declarative language, so we only need to describe what we want, not how to get it.
Two Paths Forward
Once we realize how widespread declarative programming languages are on the web, it’s hard to imagine the web without them. Hard, but not impossible. As JavaScript has grown to be ubiquitous, the tools we would need for an imperative-only web are easy to find. We could swap out HTML and CSS for rendering directly in JavaScript. We could swap out SQL for a JavaScript-native database (or two). And we could swap out calls to declarative web APIs with imperative calls to JavaScript functions, even across the gap between client and server.
We could put all of this together and entirely stop using declarative languages on the web, even before we get into more advanced technologies heading in our direction, like asm.js. We can, now, build the web equivalent of mainframes: large, powerful systems built not as a collection of disparate parts but as a cohesive whole. We can now JavaScript all the things. We’ve tried this before, with technologies like Java and ActiveX. And some organizations, such as AOL, have even had success building a less messy web-like stack. The difference this time is that the technology available to build these “mainframes” is part of the open web stack, so that anyone can now make their own self-contained web-like stack.
An imperative-only JavaScript web is enticing if we understand the web as open technologies and connected documents. But if we expand our understanding of the web to include connected systems, then declarative programming is a key part of how we connect those systems. With that understanding, we should be heading in another direction. Rather building more complex systems by replacing declarative programming languages with imperative programming, we should be wrapping more and more of our imperative code in more and better declarative languages, so that we can build future complex systems on top of our current work. Rather than looking at JavaScript as the modern Java or C++, we should be treating it as the modern shell script, a powerful tool for connecting other tools.
By defining the implementation details in the language itself, declarative programming allows imperative languages such as JavaScript, PHP and Ruby to use the results as steps in more complex behaviors. This has the advantage of making a behavior available to a variety of languages, including languages that don’t exist yet, and it also gives us a solid foundation on which to build higher. While we could build our own document-rendering system in JavaScript or Python, we don’t need to because HTML has already solved that problem. And we can reuse that solution in any imperative language, freeing us to solve new, larger problems. JavaScript can draw an image on a canvas and place it into a document with HTML. Your friend can make you a sandwich and a fresh lemonade. But we’ll get to this future web only by valuing declarative programming as an approach worth maintaining, now that it’s no longer the only option.
Declarative First
When we start building a tool on the web, we often jump right into making it do what we want it to do. A declarative-first approach would instead start with defining a language to succinctly describe the results we want. Before we build a new JavaScript library for building sandwiches (or, of course, another), let’s consider how we might describe the results in a declarative programming language. One option would look something like {“bread”: “rye”, “cheese”: “cheddar”}
, while another would look more like <sandwich><cheddar /><rye /></sandwich>
. There are many choices to make when designing a declarative language, from high-level format choices (JSON? XML? YAML?) to details of data structure (is cheese an attribute of a sandwich entity or an entity in the sandwich’s toppings list?). Making these decisions early could improve the organization of your later imperative implementation. And in the long run, the declarative language might prove to be more important than the amazing sandwich-making implementation, because a declarative language can be used far beyond an individual implementation.
We can see some of the advantages of a declarative-first approach in public projects that have taken both approaches. For example, three years ago the Sunlight Foundation started working on a project to make it easier for people to contact members of the US Congress. They began with a Ruby app to automate the submission of contact forms, Formageddon. This year, they launched a new declarative-first effort toward the same goal, the Contact Congress project, starting with a declarative language that describes contact forms.
The activity graphs and timelines of the two projects make it clear which approach won out, but the benefits of the declarative approach go beyond the direct results. The YAML files produced by the declarative approach can be used to build apps like Formageddon, but they can also be used for other purposes, ones not even intended by their creators. For example, you could build an app to analyze the descriptions of contact forms to see which topics members of Congress expect to hear about from their constituents.
Successful declarative programming is in some ways more challenging than imperative programming. It requires clear descriptions, but also requires knowing enough about imperative implementation to know what needs describing. You can’t just tell an app where a form is and expect a valid submission, nor can you say <greatwebsite>
to a browser and get what you want. But if you’re up for the challenge, the rewards of a declarative-first approach are also greater. Declarative languages often outlive their imperative implementations.
Try starting your next web project with declarative programming. See what languages you can find that already describe what you’re making, and write your own simple languages when you can’t find anything. Once you have a declarative definition of what you want to make, build it with an interpreter of those languages. You’ll probably end up making something more useful than you would have with an imperative-first approach to the same problem, and you’ll improve your imperative approach in the process.