The big payoff with functional programming

A couple months ago I stated the big payoff with functional programming comes during maintenance – life is much easier when you modify an existing program. Recently I had to move the location of a sort algorithm in a tree, and this change was super simple once I figured out how to make it. This is likely a typical experience for most of you, but pretty exciting for those of us who are still relatively new to FP.


I love this about it. It’s easy to take for granted now after working in Elm for several years. When the elm-ts-interop library came out, I decided to switch the non-Elm parts of the front-end in a project I’m working on from Flow.js to TypeScript and to adapt my Elm code to use elm-ts-interop-style of interop. Then in the middle of that change, I decided to change my app flags to use elm-graphql instead of regular decoders as well. As a result, I’m making massive structural changes to a project with five different Elm apps with a lot of shared code and like…it’s taking a lot of work, because the changes are huge, but at this point I take for granted that it’ll eventually be fine and everything will work again.


What is the observed / experienced benefit of using graphql in your projects ?

I’ve watched many talks and blogs about it but feel it’s overkill (I’ve not used graphql in production yet)

I would not be using it at all if not for the elm-graphql library. By itself, it doesn’t help me all that much, other than that I was getting a bit tired of writing so many individual endpoints for tiny amounts of data in this particular project.

With elm-graphql, though, it achieves a degree of type safety between the front-end and back-end that I really appreciate. If the format of any of my graphql queries/responses doesn’t match up with the generated elm-graphql code on the front-end, either my test suite or my deploy script will fail, and I don’t even have to explicitly add tests for each endpoint for it to do so. I test the endpoints anyway — but if I were to miss one or something the tests would still fail out based on the shape of the data being returned.

Once I’m done with my latest changes, it will error if the shape of the app flags data that is sent from the back-end doesn’t match the front-end as well. My app flags are pretty big and complex bags of data in this project, so that’s a big deal for my case. I’m hoping to do a write-up of it once it’s done. It’s pretty neat!


That is neat – another example where good tooling makes something much more practical to implement.

I made another observation today – it seems Elm code gets cleaner with time rather than messier. As I’m adding new features, I often see areas that can be simplified and cleaned up – like this:


Totally. I feel like I’m refactoring almost constantly because of how easy it is to do.


How does that compares to development in typescript ?
You can also do refactoring with confident with the helps from compiler and IDE. With less verbose codebase to sterilize and parse data for IO

My app is one third TypeScript (in workers), the rest being Elm and a few JavaScript files.

TS dev experience is so much better than JS with automatic suggestions and error detection in my IDE.
But TS is not sound (any type, <unknown> casting) and as it is a superset of JS you’re never sure the object you’re handling hasn’t been mutated.

For me, Elm dev experience is so much better than TS. Custom types are a killer feature.


@beenotung in the following episode, there is some discussion of how using Elm and Typescript compare:

Another thing @dillonkearns said on a recent podcast (can’t remember which one) is (I’m paraphrasing) a little boilerplate and extra typing is not the hard part – what is hard is finding tricky bugs in your code. Elm eliminates most debugging (once you get it to compile :slight_smile: ). With experience, you learn to take smaller steps so as to not upset the compiler too badly and make it easier to fix compile errors.

Another observation I’ve made recently: Not being able to create new components with local state easily seems pretty annoying initially – especially for those of us coming from React. In Elm, you end up having to pass in messages to functions in different modules as the following guide suggests:

This works pretty well in practice, and generally ends up being much simpler and easier to maintain that more state scattered throughout your program. I still use elm-spa to give me local state for each page – that seems to be a pretty good boundary for state, and elm-spa does all the heavy lifting there. Once again, Elm steers you toward good patterns – not sure if this was intentional or just worked out this way.

As the Simple IoT app becomes larger and I am doing more refactoring – Elm feels like an excellent choice for this project.

1 Like

Soundness argument is often thrown without proper understanding. Yes, TypeScript has not a sound type system but that’s because of subtyping and these problems are very rare and specific. And most important they occur in non idiomatic code.

any doesn’t have anything to do with soundness. It’s separate problem, easily solved with tslint. There’s no any in modern typescript codebase.

And there’s no casting. Casting is runtime operation. Maybe you’re thinking about type assertions?

Yes, Elm type system is simpler, but “soundness” start to lose its meaning like “declarative”.

TypeScript has those, albeit with more boilerplate - Discriminated Unions.

The soundness is definitely overplayed, but I do still encounter any in lots of brand new (less than 1 year old) TS code bases.

I think this is more than just TS vs Elm though. TS still requires a framework for front end or back end development while Elm doesn’t. When you’re discussing FE development you’re never talking about just TS, but TS + , and that framework also has a huge influence on the patterns you use and how you go about refactoring.

The same applies to BE. For example, TS+Deno is a different experience than TS+Node. Within that you have various different frameworks for servers, building CLIs, and more. Each bring their own nuances and guide you towards different solutions.

Explicit and implicit any can be avoided easily in your own code with tsconfig.json settings and linting. But it’s not accurate that modern TypeScript is free of any types. Only direct user code. any types are very common in core libraries, and those any types then flow in to user code through calling these core libraries (or 3rd party libraries). The most common example would be JSON.parse.

I wrote about some of these details in this article.