From 26eef4c3779f4d408a6c289cbf7635aa9d4b69d3 Mon Sep 17 00:00:00 2001
From: Mark Seemann
Date: Mon, 4 Nov 2024 08:45:49 +0100
Subject: [PATCH] Prepare article for publication
---
_posts/2021-02-22-pendulum-swings.html | 2 +-
...-03-15-pendulum-swing-pure-by-default.html | 2 +-
...o-haskell-type-annotation-by-default.html} | 24 +++++++++----------
3 files changed, 14 insertions(+), 14 deletions(-)
rename _posts/{2024-10-16-pendulum-swing-no-haskell-type-annotation-by-default.html => 2024-11-04-pendulum-swing-no-haskell-type-annotation-by-default.html} (92%)
diff --git a/_posts/2021-02-22-pendulum-swings.html b/_posts/2021-02-22-pendulum-swings.html
index 0892a9b6..16033a75 100644
--- a/_posts/2021-02-22-pendulum-swings.html
+++ b/_posts/2021-02-22-pendulum-swings.html
@@ -74,7 +74,7 @@
- Next: Pendulum swing: no Haskell type annotation by default. + Next: Pendulum swing: no Haskell type annotation by default.
\ No newline at end of file diff --git a/_posts/2024-10-16-pendulum-swing-no-haskell-type-annotation-by-default.html b/_posts/2024-11-04-pendulum-swing-no-haskell-type-annotation-by-default.html similarity index 92% rename from _posts/2024-10-16-pendulum-swing-no-haskell-type-annotation-by-default.html rename to _posts/2024-11-04-pendulum-swing-no-haskell-type-annotation-by-default.html index d8023c56..d954e617 100644 --- a/_posts/2024-10-16-pendulum-swing-no-haskell-type-annotation-by-default.html +++ b/_posts/2024-11-04-pendulum-swing-no-haskell-type-annotation-by-default.html @@ -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." @@ -20,7 +20,7 @@ Here, I consider using fewer Haskell type annotations, following a practice that I've always followed in F#.- 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.
- 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 average function has the inferred type NonEmpty<TimeSpan> -> TimeSpan
. While I had to specify the input type in order to be able to use the Ticks property, 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 average function has the inferred type NonEmpty<TimeSpan> -> TimeSpan
. While I had to specify the input type in order to be able to use the Ticks property, I didn't have to specify the return type. So I didn't.
My impression from reading other people's F# code is that this is a common, albeit not universal, approach to type annotation. @@ -135,7 +135,7 @@
- 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 discussion that I still struggle with, 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 discussion that I still struggle with, 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.
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, Python or Clojure than in C# or Java. @@ -144,7 +144,7 @@
- I have to admit, however, that I cheated a little in order to drive home a point. While you can write Haskell code in a low-ceremony style, the tooling (in the form of the all
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 can write Haskell code in a low-ceremony style, the tooling (in the form of the all
warning set, at least) encourages a high-ceremony style. Add those type definitions, even thought they're redundant.
It's not that I don't understand some of the underlying motivation behind that rule. Daniel Wagner enumerated several reasons in a 2013 Stack Overflow answer. Some of the reasons still apply, but on the other hand, the world has also moved on in the intervening decade. @@ -171,10 +171,10 @@
- In order to explain what I mean by the types being in the way, I'll give an example. Consider the code example from the article Legacy Security Manager in Haskell. In it, I described how every time I made a change to the createUser
action, I had to effectively remove and re-add the type declaration.
+ In order to explain what I mean by the types being in the way, I'll give an example. Consider the code example from the article Legacy Security Manager in Haskell. In it, I described how every time I made a change to the createUser
action, I had to effectively remove and re-add the type declaration.
- 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 createUser
action would looke exactly as before, just without the type declaration. Its type would still be IO ()
.
+ 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 createUser
action would look exactly as before, just without the type declaration. Its type would still be IO ()
.
After the first edit, the first lines of the action now look like this: @@ -226,7 +226,7 @@
- Compare that inferred type signature with the explicit final type annotation in the previous article. 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 the previous article. 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.
- If I do this, the type also changes: + If I do that, the type also changes:
Monad m => (String -> m ()) -> m [Char] -> ([Char] -> [Char]) -> m ()@@ -327,13 +327,13 @@
- As I've also recently discussed, Haskell is a great scripting language, and I think that here, too, I'll dial the type definitions down. + As I've also recently discussed, Haskell is a great scripting language, and I think that here, too, I'll dial down the type definitions.
If I ever do another Advent of Code in Haskell, I think I'll also eschew explicit type annotations.
- 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.
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 Hyrum's law. @@ -357,7 +357,7 @@
- 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.
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.