Hi, I'm John, and a few months ago I decided that I've been doing this for too long to be this bad at it.
I've been an Elm developer since 2018, and the story is really only important to me1 (there's enough self-aggrandizing on the Internet2 these days) so I'll leave it out - but it's taken me a remarkably long time to feel as if I have any real command of the language and its tooling / ecosystem.
Some of the Best & Brightest on the Elm Slack have been kindly tolerating my ineptitude for some years now, and now that I know about classList and can hang out in #beginners
and field easy questions, it's time to take the next logical step: start a dev blog.
Learning by example is well and good, but most of the truly important things I've learned have been by counter-example. What-to-do is nowhere near as poignant on its own, as it is when contrasted with what-not-to-do. And I'm an expert at what-not-to-do. Unfortunately, I've had to find these poignant counter-examples on my own. I'm here today to 1) confess my sins, and 2) show you how not to be like me. I've got enough sins for which to seek absolution that this will likely be a series, but let's dive in now. If you want to learn Elm the wrong way, start by following the headline-sized point below, don't read anything else, and... I'll see you in #beginners
.
Ignore / Fear The Runtime
So you did https://guide.elm-lang.org/, and you're aware that "Elm is a functional language that transpiles to Javascript and has its own runtime". Great! Now, if you want to do things the wrong way: never ever think about the runtime, ever again.
To be fair, this isn't wholly advice without merit; the runtime stays out of your way. My background is in data / reporting - I thought, "Oh, good. A runtime. Like when I smash F5
on SQL Server and it runs my query. Like dotnet. Like the JVM."
But there's a difference that wasn't apparent to me when I got started - since Elm is event-driven, you don't call the runtime; the runtime calls YOU. I'll say it again:
The runtime calls you!
Remember when you copied and pasted this block of code into elm reactor
and fiddled with everything around it until your app compiled? I do!
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
"Ah. A main
function. Seems right."
The problem with main
is that by the numbers, it's not a function that you write often; you set it up, and it's good. The Code is Good, and you don't touch the Code when it is Good; and so you proceed to not think very hard about it for a few years.
At a high level, the difference between public static void main()
and main : Program () Model Msg
is that, in the case of the latter, you are telling a program that already exists which three specific functions it's going to call, to handle very specific parts of your application domain.
Elm's main
function has more in common with dependency injection - with services.AddSingleton<IEmailProxy, EmailProxy>();
, than it does with public static void main()
. Our old friend psvm()
will execute any old code that we sling in there; in contrast, Elm's runtime only knows that it's going to get messages (from the DOM, from ports, and from HTTP responses); that it's going to render HTML; and that it's going to have to start up your application for the first time. The particulars of what it does in those situations are what the code that you are writing actually does, and do those things and those things only it must (no side effects, remember?). And this brings me to my next point:
Msgs happened in the past; getting their names right is important.
Hardest two problems, cache invalidation, naming, blah blah blah
You've already heard the quote, you already know that.
But you need to realize that when you declare the variants for your Msg
type, that every time you process a Msg
in update
, you are processing the output of an action that already happened.
Since I came from an OO / imperative / procedural background, I brought what I had with me when I started learning this language. If I was writing a Windows Forms application, and I wanted btn_DoSomething
to do something, I'd wire its click event-handler to a function DoSomething
, and something would be done3. So in Elm:
button [ onClick DoSomething ] [ text "Do Something" ]
and
type Msg
= DoSomething
and
case msg of
DoSomething ->
( model, doSomething )
I regret to inform you that this is not the Way.
DoSomething
means, "perform an action", and tells you nothing about how you got to the point in your program's execution where something was done. So if you choose to DoSomething
, and something was done, what do you name your variant "for when something was done"?
Probably DoSomethingResult
. But this is a function, in a functional language! "DoSomethingResult" is a name, a noun; and their kind are not welcome here4. Banish from your mind any thoughts that this is a silly hill to die on; we are programmers, we are talking about a programming language, and if you don't spend quite a bit of time thinking about the structure and interpretation5 of the language that you speak every day - you're going to have a bad time. Get interested in linguistics.
I digress. This is a functional language; custom type variants are functions that are a constructor for their custom type; we need to give them names that are verbs. What's a good verb for receiving a result from a function yclept doSomething
? I don't know, how about, GetDoSomethingResult
? Close, but computer says "no"6.
The act of receiving a Msg
is not something that will be done by your program; it is something that was done, in the past, by the runtime. Your Msg
variants are event handlers for the return value of events that come from the runtime.
Who clicked that button? You did! (Who's a good doggy?) But the only way that the event "clicked a button" is any different from any of the other thousand events per second that you emit while using a web browser (scrolling, moving your mouse, clicking on a blank space) is that you told Elm's runtime to listen for that event, and that you could handle whatever result the runtime generated from processing that event.
So since the event that you want to handle happened in the past, and since it was done by somebody else - what should you call it? In this instance, I would call my variant GotDoSomethingResult
. It's perfect! It tells us that the runtime already received our result from the doSomething
function, and is ready to hand it over to us for further processing. Once you start reading packages, or other open-source projects, you'll see these patterns pop up - lots of Got
s and Clicked
s and Changed
s.
Let's take what we've learned, and selectively refactor our application that we wrote earlier to use our new knowledge:
button [ onClick ClickedBtnDoSomething ] [ text "Do Something" ]
and
type Msg
= ClickedBtnDoSomething
| GotDoSomethingResult Result (Http.Error ())
and
case msg of
ClickedBtnDoSomething ->
...
GotDoSomethingResult result ->
...
When you talk about "getting" something, the Thing that you will Get is not yet in your possession, no matter how near the future may be wherein you have that Thing; but when you "Got" something, you can only ever speak of the action of your coming into its possession, in the past-tense. I brought up linguistics because when you start naming your Msg
s (etc) in this way, it allows you to shift your thinking ever-so-slightly in a way that makes it a lot easier to reason about your application, its state, and why its behavior may or may not be as expected.
In conclusion, conclusions are hard and I could spill another thousand more keystrokes without actually adding anything else of substance to this post, so I'll stop now. If you're still here: thanks for reading along. If this helped you: there's lots more where this came from. Be sure to tune in next time (probably) for (a lot) more7.
-
The short version is, "I needed to learn Javascript but I really didn't want to do that thing" ↩
-
Are you supposed to capitalize "Internet"? I tried it both ways. Both feel weird. ↩
-
https://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html (talk about an evergreen blogpost) ↩
-
The structure and interpretation of computer programs is, at its core, the structure and interpretation of a bunch of thoughts, both yours and those of others. Language enables thought and gives rise to consciousness; the structure and interpretation of computer programs is the structure and interpretation of thought, which is the structure and interpretation of language. Mind that I'm above my pay grade talking about these things, so whether you agree with me or not, go learn about them for yourself; and then it won't matter if I'm right - you will know. ↩
-
And if this made you mad, @ me in the Elm Slack. Maybe calm down a little first, though. ↩
Top comments (1)
Thank you for sharing, if you like to learn Elm the right way, I'd recommend you this video tutorial udemy.com/course/elm-the-complete-...