Skip to content

Commit

Permalink
Prepare article for publication
Browse files Browse the repository at this point in the history
  • Loading branch information
ploeh committed May 6, 2024
1 parent 80dc933 commit 139ed82
Showing 1 changed file with 7 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: post
title: "Conservative codomain conjecture"
description: "An API design heuristic."
date: 2024-04-01 15:09 UTC
date: 2024-05-06 6:35 UTC
tags: [Software Design, F#, Haskell]
image: "/content/binary/pipe-partially-filled-with-liquid.png"
image_alt: "Pipe partially filled with liquid."
Expand All @@ -14,13 +14,13 @@
<em>{{ page.description }}</em>
</p>
<p>
For a while now, I've been wondering whether, in the language of <a href="https://en.wikipedia.org/wiki/Robustness_principle">Postel's law</a>, one should favour being liberal in what one accepts over being conservative in what one sends. I've recently, however, reached the tentative conclusion that it may be a good idea favouring being conservative in what one sends.
For a while now, I've been wondering whether, in the language of <a href="https://en.wikipedia.org/wiki/Robustness_principle">Postel's law</a>, one should favour being liberal in what one accepts over being conservative in what one sends. Yes, according to the design principle, a protocol or API should do both, but sometimes, you can't do that. Instead, you'll have to choose. I've recently reached the tentative conclusion that it may be a good idea favouring being conservative in what one sends.
</p>
<p>
Good API design explicitly considers <em>contracts</em>. What are the preconditions for invoking an operation? What are the postconditions? Are there any invariants? These questions are relevant far beyond object-oriented design. They are <a href="https://blog.ploeh.dk/2022/10/24/encapsulation-in-functional-programming/">equally important in Functional Programming</a>, as well as <a href="">in service-oriented design</a>.
Good API design explicitly considers <em>contracts</em>. What are the preconditions for invoking an operation? What are the postconditions? Are there any invariants? These questions are relevant far beyond object-oriented design. They are <a href="/2022/10/24/encapsulation-in-functional-programming">equally important in Functional Programming</a>, as well as <a href="/2024/04/15/services-share-schema-and-contract-not-class">in service-oriented design</a>.
</p>
<p>
If you have a type system at your disposal, you can often model pre- and postconditions as types. In practice, however, it often turns out that there's more than one way of doing that. You can model an additional precondition with an input type, but you can also model potential errors as a return type. Which option is best?
If you have a type system at your disposal, you can often model pre- and postconditions as types. In practice, however, it frequently turns out that there's more than one way of doing that. You can model an additional precondition with an input type, but you can also model potential errors as a return type. Which option is best?
</p>
<p>
That's what this article is about, and my conjecture is that constraining the input type may be preferable, thus being conservative about what is returned.
Expand All @@ -29,7 +29,7 @@ <h3 id="7ef0610940fb4670b7cf12a21bdd725f">
An average example <a href="#7ef0610940fb4670b7cf12a21bdd725f">#</a>
</h3>
<p>
That's all quite abstract, so for the rest of this article, I'll discuss this kind of problem in the context of an example. We'll revisit the <a href="/2020/02/03/non-exceptional-averages">good old example of calculating an average value</a>. This example, however, is only a placeholder for any kind of API design problem. This article is only superficially about designing an API for calculating an <a href="https://en.wikipedia.org/wiki/Average">average</a>. More generally, this is about API design. I like the average example because it's easy to follow, and it does exhibit some characteristics that you can hopefully extrapolate from.
That's all quite abstract, so for the rest of this article, I'll discuss this kind of problem in the context of an example. We'll revisit the <a href="/2020/02/03/non-exceptional-averages">good old example of calculating an average value</a>. This example, however, is only a placeholder for any kind of API design problem. This article is only superficially about designing an API for calculating an <a href="https://en.wikipedia.org/wiki/Average">average</a>. More generally, this is about API design. I like the <em>average</em> example because it's easy to follow, and it does exhibit some characteristics that you can hopefully extrapolate from.
</p>
<p>
In short, what is the contract of the following method?
Expand Down Expand Up @@ -95,7 +95,7 @@ <h3 id="7922b269c9924877abe993cb282440a8">
It's difficult work to keep track of all those contracts. As I argue in <a href="/2021/06/14/new-book-code-that-fits-in-your-head">Code That Fits in Your Head</a>, it helps if you can automate away some of that work. One way is having good test coverage. Another is to leverage a static type system, if you're fortunate enough to work in a language that has one. As I've <em>also</em> already covered, <a href="/2022/08/22/can-types-replace-validation">you can't replace all rules with types</a>, but it doesn't mean that using the type system is ineffectual. Quite the contrary. Every part of a contract that you can offload to the type system frees up your brain to think about something else - something more important, hopefully.
</p>
<p>
Sometimes there's no good way to to model a precondition with a type, or <a href="https://buttondown.email/hillelwayne/archive/making-illegal-states-unrepresentable/">perhaps it's just too awkward</a>. At other times, there's really only a single way to address a concern. When it comes to the precondition that you can't pass an infinite sequence to the <em>Average</em> function, <a href="/2020/02/03/non-exceptional-averages">change the type so that it takes some finite collection</a> instead. That's not what this article is about, though.
Sometimes there's no good way to to model a precondition with a type, or <a href="https://buttondown.email/hillelwayne/archive/making-illegal-states-unrepresentable/">perhaps it's just too awkward</a>. At other times, there's really only a single way to address a concern. When it comes to the precondition that you can't pass an infinite sequence to the <em>average</em> function, <a href="/2020/02/03/non-exceptional-averages">change the type so that it takes some finite collection</a> instead. That's not what this article is about, though.
</p>
<p>
Assuming that you've already dealt with the infinite-sequence issue, how do you address the other precondition?
Expand Down Expand Up @@ -213,7 +213,7 @@ <h3 id="4fb2cc5775c44f80965cacbc37825f27">
Conservative codomain <a href="#4fb2cc5775c44f80965cacbc37825f27">#</a>
</h3>
<p>
Is it possible to instead design the API in such a way that it's conservative in what it returns? Ideally, we'd like it to guarantee that it returns a value. This is possible by making the preconditions even more explicit. I've also <a href="/2020/02/03/non-exceptional-averages">covered that alternative already</a>, so I'm just going to repeat the C# code here without further comments:
Is it possible to instead design the API in such a way that it's conservative in what it returns? Ideally, we'd like it to guarantee that it returns a number. This is possible by making the preconditions even more explicit. I've also <a href="/2020/02/03/non-exceptional-averages">covered that alternative already</a>, so I'm just going to repeat the C# code here without further comments:
</p>
<p>
<pre><span style="color:blue;">public</span>&nbsp;<span style="color:blue;">static</span>&nbsp;<span style="color:#2b91af;">TimeSpan</span>&nbsp;<span style="color:#74531f;">Average</span>(<span style="color:blue;">this</span>&nbsp;<span style="color:#2b91af;">NotEmptyCollection</span>&lt;<span style="color:#2b91af;">TimeSpan</span>&gt;&nbsp;<span style="font-weight:bold;color:#1f377f;">timeSpans</span>)
Expand Down

0 comments on commit 139ed82

Please sign in to comment.