Without the convoluted type system of Haskell, you have to do this more, and it is a lot simpler to understand and maintain. You are never going to be able to use the type system to guarantee everything about your data, so let your runtime code check it, add lots of tests and keep it isolated (Elm calls them "Opaque Types") so it is easy to reason about.
The equivalent is possible in OO with classes, but with the caveat that only if you make everything immutable and ideally have a layer of compile time null checking on top.
In short, with Haskell I am learning and relearning language features constantly and banging my head. With Elm I grok the language and I am focused on solving the problem.
That said, I do think what you’re describing is different from what is described in the blog post. Opaque/abstract types reduce the trusted code significantly, which is fantastic, but the example provided in the blog post is actually even better than that: because NonEmpty is correct by construction, the amount of trusted code goes down to zero. A lot of the tools Haskell provides, like GADTs and type families, can be used to capture more invariants in a correct-by-construction way, rather than relying on abstraction boundaries. There are advantages and disadvantages to both.
I certainly don’t have any ill will toward anyone who finds the complexity of Haskell not worth the extra benefits, so to be quite clear, I’m not trying to sell you on Haskell instead of Elm! I do think being aware of the distinction is still useful, though.
But as a hobby FP'er looking from the 'outside' Elm is more suited to me. So the point is my Haskell vs Elm preference is somewhat circumstantial.
It's like the home DIY person preferring to order a kitchen from IKEA and install it, instead of making their own cabinetry.
It would be wonderful if all programing language comparisons were this clear and this calm.
I find it funny that one of the most advanced programming languages of our day can't do what Pascal did back in 1980 :-)
http://stackoverflow.com/q/7302735
Also, Pascal's ranged integers are only for literals; the are not thoroughly enforced by the compiler. They are trivial to circumvent:
var
y,z: 5..10;
Begin
Read(y); Writeln(y); // 60 works here
y := 10;
z := 2*y; Writeln(z);
https://tio.run/##y0/KSk0u0S1ILE5OzNFNK0j@/z@gKD@9KDFXISU1N9...
> Sometimes, making an illegal state truly unrepresentable is just plain impractical given the tools Haskell provides, such as ensuring an integer is in a particular range. In that case, use an abstract newtype with a smart constructor to “fake” a parser from a validator.
For instance, few type systems will let you encode prime numbers as datatypes, so you'd have to do something in your language like
which is parsing!