Comment by darkkindness

6 years ago

Yes, yes yes! Encode invariants in your data, don't enforce invariants on your data. I particularly think this point needs to be stressed more, because practicably speaking, not every language has the typechecking faculties of Haskell:

> 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

    newtype Prime = Integer
    mkPrime :: Integer -> Maybe Prime
    mkPrime | isPrime p = Just (Prime p)
            | otherwise = Nothing

which is parsing!

This is why learning Elm has been valuable for me.

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.

  • I think it’s fantastic that Elm works well for you; it’s a wonderful language and there’s no doubt that it is much simpler than Haskell. I agree with you that, in many cases, the technique you’re describing is sufficient, and it requires much less sophisticated machinery than some of the fancier Haskell techniques.

    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.

    • Good reply. My view of Haskell is if I had to do it 40 hours a week in a team for 50 weeks, I'd get use to it and would see the benefits and get itchy using any language without them. I'd have wiser colleagues to help me learn and I'd see the patterns in an existing code base.

      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.

    • Well said!

      It would be wonderful if all programing language comparisons were this clear and this calm.

  • Yes, being able to make trivial value types is quite valuable. Despite being a rather different language, this is something Go gets right as well.

> such as ensuring an integer is in a particular range

I find it funny that one of the most advanced programming languages of our day can't do what Pascal did back in 1980 :-)

In short, "algorithms are for things we are able to encode in the data structures". Or "Types first, Logic second, if at all."