diff --git a/_posts/2024-04-01-conservative-codomain-conjecture.html b/_posts/2024-05-06-conservative-codomain-conjecture.html similarity index 97% rename from _posts/2024-04-01-conservative-codomain-conjecture.html rename to _posts/2024-05-06-conservative-codomain-conjecture.html index a3ec77ba..1dcf570e 100644 --- a/_posts/2024-04-01-conservative-codomain-conjecture.html +++ b/_posts/2024-05-06-conservative-codomain-conjecture.html @@ -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." @@ -14,13 +14,13 @@ {{ page.description }}
- For a while now, I've been wondering whether, in the language of Postel's law, 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 Postel's law, 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.
- Good API design explicitly considers contracts. 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 equally important in Functional Programming, as well as in service-oriented design. + Good API design explicitly considers contracts. 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 equally important in Functional Programming, as well as in service-oriented design.
- 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?
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. @@ -29,7 +29,7 @@
- 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 good old example of calculating an average value. 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 average. 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 good old example of calculating an average value. 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 average. 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.
In short, what is the contract of the following method? @@ -95,7 +95,7 @@
- Sometimes there's no good way to to model a precondition with a type, or perhaps it's just too awkward. 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 Average function, change the type so that it takes some finite collection 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 perhaps it's just too awkward. 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 average function, change the type so that it takes some finite collection instead. That's not what this article is about, though.
Assuming that you've already dealt with the infinite-sequence issue, how do you address the other precondition? @@ -213,7 +213,7 @@
- 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 covered that alternative already, 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 covered that alternative already, so I'm just going to repeat the C# code here without further comments:
public static TimeSpan Average(this NotEmptyCollection<TimeSpan> timeSpans)