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 Nov 4, 2024
1 parent db5d4b1 commit 26eef4c
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 14 deletions.
2 changes: 1 addition & 1 deletion _posts/2021-02-22-pendulum-swings.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h3 id="36d029a90bfa4d35a7e8fc10048b8bcc">
<li><a href="/2021/03/01/pendulum-swing-internal-by-default">Pendulum swing: internal by default</a></li>
<li><a href="/2021/03/08/pendulum-swing-sealed-by-default">Pendulum swing: sealed by default</a></li>
<li><a href="/2021/03/15/pendulum-swing-pure-by-default">Pendulum swing: pure by default</a></li>
<li>Pendulum swing: no Haskell type annotation by default</li>
<li><a href="/2024/11/04/pendulum-swing-no-haskell-type-annotation-by-default">Pendulum swing: no Haskell type annotation by default</a></li>
</ul>
I'd be naive if I believed these to be my final words on any of these topics. I'm currently trying them out for size; in a few decades I'll know more about how it all turns out.
</p>
Expand Down
2 changes: 1 addition & 1 deletion _posts/2021-03-15-pendulum-swing-pure-by-default.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ <h3 id="bb9264be0f1648b48f6572e1054fab95">
From having favoured fine-grained Dependency Injection, I now write all decision logic as pure functions by default. These only need to implement interfaces if you need the <em>logic</em> of the system to be interchangeable, which isn't that often. I do still use Dependency Injection for the impure dependencies of the system. There's usually only a handful of those.
</p>
<p>
<strong>Next:</strong> Pendulum swing: no Haskell type annotation by default.
<strong>Next:</strong> <a href="/2024/11/04/pendulum-swing-no-haskell-type-annotation-by-default">Pendulum swing: no Haskell type annotation by default</a>.
</p>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
layout: post
title: "Pendulum swing: no Haskell type annotation by default"
description: "Are Haskell IDE plugins now good enough that you don't need explicit type annotations?"
date: 2024-10-16 17:12 UTC
date: 2024-11-04 7:45 UTC
tags: [Haskell]
image: "/content/binary/haskell-code-with-inferred-type-displayed-by-vs-code.png"
image_alt: "Screen shot of a Haskell function in Visual Studio Code with the function's type automatically displayed above it by the Haskell extension."
Expand All @@ -20,7 +20,7 @@
Here, I consider using fewer <a href="https://www.haskell.org/">Haskell</a> type annotations, following a practice that I've always followed in <a href="https://fsharp.org/">F#</a>.
</p>
<p>
To be honest, though, it's not that I've already followed the following practice for a long time, and only now write about it. It's rather that I feel the need to write this article to kick an old habit and start a new.
To be honest, though, it's not that I've already applied the following practice for a long time, and only now write about it. It's rather that I feel the need to write this article to kick an old habit and start a new.
</p>
<h3 id="227874a509f24b93b9a091429b9ad03e">
Inertia <a href="#227874a509f24b93b9a091429b9ad03e">#</a>
Expand Down Expand Up @@ -123,7 +123,7 @@ <h3 id="cf16318003ef46ed8c67d81217e56011">
&nbsp;&nbsp;&nbsp;&nbsp;|&gt;&nbsp;<span style="color:#2b91af;">TimeSpan</span>.<span style="font-weight:bold;color:#74531f;">FromTicks</span></pre>
</p>
<p>
Even so, I follow the rule of minimal annotations: Only add the type information required to compile. Let the compiler infer the rest. For example, the above <a href="/2024/05/06/conservative-codomain-conjecture">average function</a> has the inferred type <code><span style="color:#2b91af;">NonEmpty</span><span style="color:#2b91af;">&lt;</span><span style="color:#2b91af;">TimeSpan</span><span style="color:#2b91af;">&gt;</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">TimeSpan</span></code>. While I had to specify the input type in order to be able to use the <a href="https://learn.microsoft.com/dotnet/api/system.datetime.ticks">Ticks property</a>, I didn't have to specify the return type. So I didn't.
Even so, I follow the rule of minimal annotations: Only add the type information required to compile, and let the compiler infer the rest. For example, the above <a href="/2024/05/06/conservative-codomain-conjecture">average function</a> has the inferred type <code><span style="color:#2b91af;">NonEmpty</span><span style="color:#2b91af;">&lt;</span><span style="color:#2b91af;">TimeSpan</span><span style="color:#2b91af;">&gt;</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;<span style="color:#2b91af;">TimeSpan</span></code>. While I had to specify the input type in order to be able to use the <a href="https://learn.microsoft.com/dotnet/api/system.datetime.ticks">Ticks property</a>, I didn't have to specify the return type. So I didn't.
</p>
<p>
My impression from reading other people's F# code is that this is a common, albeit not universal, approach to type annotation.
Expand All @@ -135,7 +135,7 @@ <h3 id="fdd9161164f64f438aa0bedf5ff6f9a8">
Motivation for explicit type definitions <a href="#fdd9161164f64f438aa0bedf5ff6f9a8">#</a>
</h3>
<p>
When I extol the merits of static types, proponents of dynamically typed languages often argue that the types are in the way. Granted, this is <a href="/2021/08/09/am-i-stuck-in-a-local-maximum">a discussion that I still struggle with</a>, but based on my understanding of the argument, it seems entirely reasonable. After all, if you have to spend time to declare the type of each and every parameter, as well as a function's return type, it does seem to be in the way. This is only exacerbated if you later change your mind.
When I extol the merits of static types, proponents of dynamically typed languages often argue that the types are in the way. Granted, this is <a href="/2021/08/09/am-i-stuck-in-a-local-maximum">a discussion that I still struggle with</a>, but based on my understanding of the argument, it seems entirely reasonable. After all, if you have to spend time declaring the type of each and every parameter, as well as a function's return type, it does seem to be in the way. This is only exacerbated if you later change your mind.
</p>
<p>
Programming is, to a large extend, an explorative activity. You start with one notion of how your code should be structured, but as you progress, you learn. You'll often have to go back and change existing code. This, as far as I can tell, is much easier in, say, <a href="https://www.python.org/">Python</a> or <a href="https://clojure.org/">Clojure</a> than in C# or <a href="https://www.java.com/">Java</a>.
Expand All @@ -144,7 +144,7 @@ <h3 id="fdd9161164f64f438aa0bedf5ff6f9a8">
If, however, one extrapolates from the experience with Java or C# to all statically typed languages, that would be a logical fallacy. My point with <a href="/2019/12/16/zone-of-ceremony">Zone of Ceremony</a> was exactly that there's a group of languages 'to the right' of high-ceremony languages with low levels of ceremony. Even though they're statically typed.
</p>
<p>
I have to admit, however, that I cheated a little in order to drive home a point. While you <em>can</em> write Haskell code in a low-ceremony style, the tooling (in the form of the <code>all</code> warning set, at least) encourages a high-ceremony style. Add those type definitions, even thought they're redundant.
I have to admit, however, that in that article I cheated a little in order to drive home a point. While you <em>can</em> write Haskell code in a low-ceremony style, the tooling (in the form of the <code>all</code> warning set, at least) encourages a high-ceremony style. Add those type definitions, even thought they're redundant.
</p>
<p>
It's not that I don't understand some of the underlying motivation behind that rule. <a href="http://dmwit.com/">Daniel Wagner</a> enumerated several reasons in <a href="https://stackoverflow.com/a/19626857/126014">a 2013 Stack Overflow answer</a>. Some of the reasons still apply, but on the other hand, the world has also moved on in the intervening decade.
Expand All @@ -171,10 +171,10 @@ <h3 id="367135868de54bcb8eebd2d9bc9a0f8c">
Ceremony example <a href="#367135868de54bcb8eebd2d9bc9a0f8c">#</a>
</h3>
<p>
In order to explain what I mean by <em>the types being in the way</em>, I'll give an example. Consider the code example from the article <a href="">Legacy Security Manager in Haskell</a>. In it, I described how every time I made a change to the <code>createUser</code> action, I had to effectively remove and re-add the type declaration.
In order to explain what I mean by <em>the types being in the way</em>, I'll give an example. Consider the code example from the article <a href="/2024/10/21/legacy-security-manager-in-haskell">Legacy Security Manager in Haskell</a>. In it, I described how every time I made a change to the <code>createUser</code> action, I had to effectively remove and re-add the type declaration.
</p>
<p>
It doesn't have to be like that. If instead I'd started without type annotations, I could have moved forward without being slowed down by having to edit type definitions. Take the first edit, breaking the dependency on the console, as an example. Without type annotations, the <code>createUser</code> action would looke exactly as before, just without the type declaration. Its type would still be <code>IO ()</code>.
It doesn't have to be like that. If instead I'd started without type annotations, I could have moved forward without being slowed down by having to edit type definitions. Take the first edit, breaking the dependency on the console, as an example. Without type annotations, the <code>createUser</code> action would look exactly as before, just without the type declaration. Its type would still be <code>IO ()</code>.
</p>
<p>
After the first edit, the first lines of the action now look like this:
Expand Down Expand Up @@ -226,7 +226,7 @@ <h3 id="367135868de54bcb8eebd2d9bc9a0f8c">
it impacts none of the existing code. Again, the types aren't in the way, and no ceremony is required.
</p>
<p>
Compare that inferred type signature with the explicit final type annotation in <a href="">the previous article</a>. The inferred type is much more abstract and permissive than the explicit declaration, although I also grant that Daniel Wagner had a point that you can make explicit type definitions more reader-friendly.
Compare that inferred type signature with the explicit final type annotation in <a href="/2024/10/21/legacy-security-manager-in-haskell">the previous article</a>. The inferred type is much more abstract and permissive than the explicit declaration, although I also grant that Daniel Wagner had a point that you can make explicit type definitions more reader-friendly.
</p>
<h3 id="d4469073def54f289edb56d1ca8417ee">
Flies in the ointment <a href="#d4469073def54f289edb56d1ca8417ee">#</a>
Expand Down Expand Up @@ -270,7 +270,7 @@ <h3 id="d4469073def54f289edb56d1ca8417ee">
&nbsp;&nbsp;&nbsp;&nbsp;(validatePassword&nbsp;=&lt;&lt;&nbsp;comparePasswords&nbsp;password&nbsp;confirmPassword)</pre>
</p>
<p>
If I do this, the type also changes:
If I do that, the type also changes:
</p>
<p>
<pre><span style="color:blue;">Monad</span>&nbsp;m&nbsp;<span style="color:blue;">=&gt;</span>&nbsp;(<span style="color:#2b91af;">String</span>&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;m&nbsp;())&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;m&nbsp;[<span style="color:#2b91af;">Char</span>]&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;([<span style="color:#2b91af;">Char</span>]&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;[<span style="color:#2b91af;">Char</span>])&nbsp;<span style="color:blue;">-&gt;</span>&nbsp;m&nbsp;()</pre>
Expand Down Expand Up @@ -327,13 +327,13 @@ <h3 id="42ffe5249c7542809ca55a95a8f15f6c">
I'll forgo type annotations as long as I explore a problem space. For internal application use, this may effectively mean forever, in the sense that how you compose an application from smaller building blocks is likely to be in permanent flux. Here I have in mind your average web asset or other public-facing service that's in constant development. You keep adding new features, or changing domain logic as the overall business evolves.
</p>
<p>
As I've also recently discussed, <a href="/2024/02/05/statically-and-dynamically-typed-scripts">Haskell is a great scripting language</a>, and I think that here, too, I'll dial the type definitions down.
As I've also recently discussed, <a href="/2024/02/05/statically-and-dynamically-typed-scripts">Haskell is a great scripting language</a>, and I think that here, too, I'll dial down the type definitions.
</p>
<p>
If I ever do another <a href="https://adventofcode.com/">Advent of Code</a> in Haskell, I think I'll also eschew explicit type annotations.
</p>
<p>
On the other hand, I can see that once an API stabilizes, you may want to lock it down. This may apply to internal abstractions if you're working in a team and you explicitly want to communicate what the contract is.
On the other hand, I can see that once an API stabilizes, you may want to lock it down. This may also apply to internal abstractions if you're working in a team and you explicitly want to communicate what a contract is.
</p>
<p>
If the code is a reusable library, I think that explicit type definitions are still required. Both for the reasons outlined by Daniel Wagner, and also to avoid being the victim of <a href="https://www.hyrumslaw.com/">Hyrum's law</a>.
Expand All @@ -357,7 +357,7 @@ <h3 id="36e2b141fff548678e34d24eda5a3e03">
I've always appreciated the F# compiler's ability to infer types and just let type changes automatically ripple through the code base. For that reason, the Haskell norm of explicitly adding a (redundant) type annotation has always vexed me.
</p>
<p>
It often takes me a long time to reach seemingly obvious conclusions, such as: Don't add type definitions to Haskell functions. Let the type inference engine do its job.
It often takes me a long time to reach seemingly obvious conclusions, such as: Don't always add type definitions to Haskell functions. Let the type inference engine do its job.
</p>
<p>
The reason it takes me so long to take such a small step is that I want to follow 'best practice'; I want to write idiomatic code. When the standard compiler-warning set complains about missing type definitions, it takes me significant deliberation to discard such advice. I could imagine other programmers being in the same situation, which is one reason I wrote this article.
Expand Down

0 comments on commit 26eef4c

Please sign in to comment.