Hacker News new | ask | show | jobs
by asQuirreL 3631 days ago
The article seems to advocate type synonyms like the following:

    type Case = String -> String
    -- ...
    type Announcer = String -> IO String
I would argue that these are actually much worse than not having type synonyms at all.

(String -> String) functions could do anything to your query parameter and text, the type is too coarse, and the inhabitants too opaque for us to reason about them easily. Naming the type suggests the problem is solved without actually having solved it. It is like finding a hole in the ground, and covering it with leaves, so you don't have to look at it anymore. You are literally making a trap for the next person to come this way.

In an ideal world you would be able to use refinements to say that you want any (f :: String -> String) such that `toUpper . f = toUpper` but without such facilities, I think I may just settle for:

    newtype Case = CaseSensitive Bool
Sometimes, your type really does only have two inhabitants.
1 comments

    data Case = CaseSensitive | CaseInsensative
This is just as efficient as the newtype, and leads to clearer code when matching on the value.

Also, sometimes types you thought only had two inhabitants get a third one added later, which this facilitates.

Clarity is a bit subjective, I think. The difference between:

    CaseSensitive
    CaseInsensitive
Is harder to spot (for me) than between:

    CaseSensitive True
    CaseSensitive False
This is because the bit that is the same is all on one side, and the bit that is difference is all on the other side. Case in point, your data definition has a typo: `CaseInsensative`, which occurs after the `In` shifts it away from the bit it should be the same as in `CaseSensitive`. Every little bit helps.

What's more, while you may be right that at the surface, the two representations are equally performant, what the newtype has that the data declaration does not, is the Prelude's definitions of all the boolean operators. If you wish to perform any more complicated logic with your data declarations treating them as booleans, you must either cast them to booleans (which comes at a runtime cost), or you must replicate the functionality of the Prelude for your custom type (which comes at a development cost).

Your branching logic (which, let us suspend disbelief and say is "not so bad", just for now) may require the combination of multiple such booleans, which in your encoding scheme would each get a different type due to their semantics, then we can't even viably define our custom boolean operators, so are forced to cast everything to booleans.

The point I'm making here is that outwardly, you want the type to reflect the semantics of how its values are used, but inwardly, you want access to its representation in a way that makes it easy to combine (or put another way, depending on who's looking, the semantics of a value changes).

Also, there is nothing stopping you from changing code later to meet changing needs. Using a newtype now doesn't preclude you from ever using a data declaration in the future. Certainly, you will have to change the patterns and constructors used in a couple of places, but that is a matter of minutes: Time you have already spent weighing the future implications of this decision in your mind right now, so this sensation of time saved is a fallacy.