I know the differences between data, newtype and type very well. I am writing a small script that will build some sort of syntax tree. Almost all types have one constructor. I am avoiding type to enforce safety (multiple “different” types might end up having the same type in Haskell). I don’t care about laziness/strictness in this case, nor do I care about performance (this part is by no means performance critical). I am mainly focused on style. I have three options:
- Use only
data. This feels OK, except that I have many types with only one constructor with one argument. The code looks some how wasteful… Although I don’t care about the performance gain, but it just does not feel right. - Use only
newtype. This leads to a lot of ugliness with tuples in the case of multiple parameters. - Mix
dataandnewtypewhich somewhat look non-uniform and slightly annoying.. I’d rather have all types declared in a single consistent way.
I am in a dilemma of choosing between 1 and 3.
In this case, I would use
datauniversally, for a couple of reasons. Firstly, for consistency with the multiple-argument cases (which should definitely bedata, notnewtype).Secondly, and most importantly,
newtypehas different semantics todata! The constructor of anewtypeis strict, as opposed to those ofdata, which are non-strict unless you explicitly use strict fields. Even if you don’t care about strictness, or all the fields of yourdatas are strict, there are still some subtle differences.I don’t think one-constructor, one-argument
datatypes are wasteful — syntactically, they’re just as light as anewtype, and semantically, seems more important to me.You said you’re not concerned about performance, but if the runtime boxing overhead of a
datawas really inconvenient, then you could mix them, as long as you’re aware of the semantic differences. However, if you use-funbox-strict-fields, then GHC might be able to optimise away the single-constructor, single-argumentdatas for you, if they occur as strict fields in other data types.Generally, you should use
newtypewhen you’re wrapping an existing type, for the purposes of compile-time safety/abstraction, or to define your own instances, and usedatawhenever the type just happens to be composed of a single field, rather than being a wrapper.