The F# syntax is very close to the Elm one. Some people actually moved from Elm to F# after Elm v0.19. So I gave F# a try and was surprised by how close you can get to the Elm experience.
I created a little example GitHub repo with typical Elm features:
The goal of this example is to show how to get typical Elm features (see below) in F#. It uses Fable to transpile F# to JavaScript and Elmish to get The Elm Architecture (TEA), also known as the Model View Update (MVU) pattern.
Elmish normally uses React under the hood, but in this example React was seamlessly replaced by Preact to get performances similar to Elm, both in term of speed and bundle size.
Featuring
Startup flags
Getting a random number (easy!)
Routing
Subscription via a JavaScript custom event
Foreign Function Interface (FFI) using synchronous and asynchronous functions imported from JavaScript
JSON decoding
Keyed list
Unit tests
Hot Module Replacement (HMR)
Debugger (via Redux DevTools)
Note that F# is backed by Microsoft since 2005 and is updated regularly, unlike ahem, well some other languages
I have used F# professionally and I liked the book “for fun and profit” plus that F# is the nicest .Net language,
There are however things I didn’t appreciate:
interop with .Net means you can get null pointers in your code despite the language has no null concept.
single pass compiler (!) You need to declare things before using them. Haven’t seen a single pass compiler since the 80’s.
it has not this nice sense of simple and yet complete that Elm has. You sometimes need to reach for something written in C# and the impedance is significant.
it has this aurora of "designed by Microsoft " over it, hard to describe, the best I can is “paper cuts”. Overall nice idea but annoying things here and there.
Your experience may be different, of course. This is just me.
I find having the order of declarations enforced at compile time quite refreshing actually. I find that it makes reading code more predictable/enjoyable.
F# is an interesting language, but I find myself more interested by its ancestor OCaml
There is a section about this choice in FSFFAP. F# handles exceptions because it allows FFI. Elm evacuates the problem by using ports and custom elements. I would need to implement a non-trivial app in F# to be more assertive, but I think you can do the same in F# if you handle JS exceptions on the JS side before sending proper “Result” records to F#. I agree we are spoiled by Elm design choices that enforces cleanliness for us!
You’re right that it’s baked-in, and this does steer you into a pattern. I don’t think it would be hard to create say “Cmd.Result.ofAsync” , “Cmd.Either.ofAsync” etc. I hadn’t thought about this before, and may add it into my own project.
By the way I just listened to @rtfeldman’s podcast episode with the author of F# for fun and profit, and at minute 18 they start to talk about Result versus exceptions. They confirm that in a “pure Result environment” there is no need for exceptions but as long as you run code that can raise exceptions you have no choice but to handle exceptions. Obvioulsy.