theor

solutions looking for problems

Choosing a language to replace Javascript (and why it's F#)


This is an opinion piece. YMMV

Once in a while, I start a side project whose ideal format is a website. This article is about how I eventually settled on F# and Fable.

I have a strong dislike for weakly typed languages. I’ve always thought that typing should be a compiler problem. If you’re paying people to write unit tests to do a compiler’s job, you’re wasting time and money. At work, I code in C#, a perfectly reasonable language full of quality-of-life features; I could not convince myself to use JS. I’m also reluctant to use an overcomplicated build pipeline.

My requirements are quite simple:

I first started by listing candidate stacks I could use:

I’m also following Blazor closely, but it seems to be a bit early to use it. The WebAssembly build size is huge, as it needs to ship an entire .net VM. I’ll probably revisit this option once WebAssembly AOT lands.

I had a look at ReScript, which is another transpiler, but the renaming/merging of BuckleScript/Reason was still ongoing and the doc was messy.

Typescript

TS is gaining quite a lot of traction, and that’s justified: the tooling is great, the language allows progressive migrations of a JS codebase to TS, and its type system is designed to handle idiomatic JS, however crazy that sounds. More and more JS libs are now written in TS (that’s the case of Tone.JS for example), and it supports untyped code via its any type as an escape hatch.

The typical MVU implementation in JS/TS is React+Redux. I am still surprised how inelegant Redux becomes with typescript there are way too many ways to create Redux actions, when it should simply be the outcome of the framework relying on a sound type system.

I’m still confused by the fact that, by default, TS can output JS even if you have compiler errors.

Also, way too much webpack/babel magic. This one is not against TS, but for some reason, I find it arcane.

My conclusion is “it works, I don’t like it”. I see TS as what JS should have been: a common target for better ecosystems, but with a reasonable type system.

Elm

I’m a big fan of functional programming, OCaml being the first language I’ve learned. Elm has a syntax that looks like a simpler haskell, it is pure, and has amazing tooling. However:

On the other hand, the compiler messages are fantastic - I think Elm influenced Rust more recently, which has the best errors I’ve ever seen. Also, the Elm Architecture Guide is a fantastic intro to the MVU pattern and excellent documentation in general.

F#/Fable

I’ve used F# multiple times in the past in a bunch of projects I should write about one day - a toy compiler for a ML-like language emitting IL, a simple Amazon S3 backed dropbox-like, etc. F# fixes most issues I have with OCaml (syntax, tooling, ecosystem) at the cost of a few powerful features I’ve never used.

Fable takes F# code and outputs JS - meaning, unlike the current Blazor WASM implementation, it doesn’t rely on running a full mono VM in the browser to interpret IL. It’s faster, but that means Fable needs to implement the base layer of .net in JS; it hasn’t been an issue to date for me.

The tooling (especially since Fable 3 Nagareyama) is great - a getting started guide comes down to dotnet tool install fable && dotnet fable src. It’s reasonably fast and doesn’t force the user to use webpack or parcel or any of those overcomplicated build systems. The IDE experience, in Rider or VSCode+Ionide, is good. It could be better, but I still prefer it to the elm experience.

Interop (see the fable doc section) can be done multiple ways:

It does mean that you can trigger those disgusting side-effects straight from Fable code, which is alright in my case - great for prototyping, and I tend to eventually refactor those using some kind of Command/Effect pattern.

I did find a few issues with that pipeline, but nothing that annoying to me or that I found straight-up disgusting:

Fable it is

One of my most recent side-projects was a networked dice roller for my remote table-top sessions (Roll20 does way too much for my use case… and it was a perfect test case). I wrote it both with Elm and Fable: both are quite similar in terms of LoC. Refactoring the network flow (host/join a room, dispatch messages, etc) was way simpler in F# in the end, as the entire code section was typed instead of relying on JS code to use socket.io in the case of Elm.

I’m now using Fable to write a graph editor with a textual renderer. At work, I spend a lot of time drawing graphs in the comments of my unit tests (more on that later) and I’m still very happy with the entire pipeline. I like the iteration speed, type safety, UI code. And given how picky I can be with my tech stacks, that’s a lot.