I’m going to argue that declarative style programming is going to be the future.
When I say declarative programming, I’m specifically talking about a code style in which you specify what you want, as opposed to what you want to do. The most popular example of a declarative language today is SQL; you don’t tell the database how to filter and join and group and order and what-have-you-not, you just YELL at the database what you want.
Fans of declarative programming have been saying it’s the future ever since it was a thing. The traditional argument is because fans of the style view it as simpler; it’s cognitively easier to just think about what the end result looks like, as opposed to worrying about both what it looks like and how to get there. All we really care about is the result, and the time saved by not worrying about the implementation is more time focused on a better result.
The traditional argument isn’t wrong, it’s just not really coming any time soon, at least not in the way it’s usually envisioned. SQL might be great, but it’s still a leaky abstraction, and people still spend time optimizing their queries to be faster, learning all the subtleties of the planner, and even sending hints to the database on how to do its job better. All this is because while the declarative nature of SQL is great, the algorithms that come up with the how aren’t perfect. To take this notion of declarative programming and scale it up to an entire turing-complete language remains a challenge. Purely functional languages come pretty close (hi Haskell fans), but they too run into similar problems, where programmers are forced to learn about the underlying runtime mechanisms to properly optimize their code.
I digress; what I really want to talk about is declarative style programming. The functional style is very akin to declarative style, and there’s been a definite rise in the popularity and use of the functional style. One thing I haven’t seen a lot of though, is the killer feature of the declarative style: idempotency.
Yes, I know they aren’t the same thing, ones a programming style and the other has a more mathematical form. However, the fact remains that declarative code is much closer to idempotency than imperative. It’s actually exactly this difference that I want to explore.
Here’s the thing: idempotency is overpowered. It’s too good. It’s too useful. If you’re unfamiliar, an idempotent function is one that can be called multiple times, and the end result is exactly as if it had been called once. Almost all functional/declarative code is idempotent by default, due to its stateless nature. What usually breaks this idempotency is functional programmings old arch-nemesis: side effects, more commonly known as the ability to actually do anything useful (again, hello haskell fans).
It’s exactly in these side effects that I see the future rise of the declarative style.
Consider an API endpoint for your bank, in which you can transfer cash between accounts. You’ve designed it such that you POST to www.mybank.com/transfer with the data of how much money to send, and what account to send it too. So you go to transfer some money to your buddy, fire off a request, aannnddd… nothing. The request got dropped/lost/timed out. What do you do? You have no idea if the response was dropped after the server completed the transfer, or if the server never got it all. You can’t simply smash the API again, for fear of a double transfer. This is the exact scenario idempotency fixes. Had you designed your API such that it was idempotent, you could fire off a million of these transfer requests in full knowledge that, at most, only 1 transfer to your buddy will be executed (Check out https://stripe.com/docs/api/idempotent_requests to see an example of how to do this).
Idempotent API’s are hardly new, but I envision a world of entirely idempotent code. In this world of servers-as-cattle, triple redundancy, ephemeral VMs, interruptible compute environments, failure is everywhere. You cannot, at scale, count on your code “not crashing between these 2 lines”. People can’t even figure out how to get exactly-once message delivery, best thing is at-least-once. This means that, in certain situations, your event-driven architecture will have duplicate messages.
Again, none of this is new. People have been coding idempotent functions and APIs, databases have those nifty transactions, events have been de-dupped before. But it shocks me that there aren’t more primitives for this sort of guarantee.
I want to see better tooling around creating idempotent-by-default APIs.
Much how Rust has a concept of safe and unsafe code, at the compiler level, I’d love to see a separation of idempotent and non-idempotent code.
I don’t want to have to think about whether my code is idempotent or not, I want some sort of compiler warning when its not. For every async task where I remember to make it idempotent, there’s another one where I forgot. Maybe I’m lazy or forgetful, but I’m sure I’m not the only one who forgets what sort of runtime guarantees I’m currently working with when I’m dealing with a figurative mountain of abstraction.
Idempotency is too good of a guarantee to not strive to have as much as possible. There’s more software running at scale than ever before, and the proper tools to get the higher level of correctness that’s required are needed now more than ever. Declarative style induces itself to idempotency very nicely, and I look forward to better abstractions around the use of it.