Haskell records have lots of problems. Here's one that came up for me today.
You are allowed to export record members without exporting the constructor,
for example, if you want to ensure some property is true of the constructed
values. In the following example, the field `isNeg` is effectively a function
of the field `num`:
module Foo(mkRec, num, isNeg) where
data Rec = Rec
{ num :: Int
, isNeg :: Bool
}
mkRec :: Int -> Rec
mkRec n = Rec n (n < 0)
Another module can't use the `Rec` constructor, but can observe the values
using the exported accessors
module Bar where
addRecs :: Rec -> Rec -> Rec
addRecs r1 r2 = mkRec (num r1 + num r2)
Unfortunately, there's a hole here, which is that exporing the accessors
allows us to use record update syntax, which means that we can now construct
arbitrary values:
constructAnyRec :: Int -> Bool -> Rec
constructAnyRec n b = mkRec 0 { num = n, isNeg = b }
There is a way around this, namely, by rewriting the original module with
manual accessors for `num` and `isNeg`:
module Foo2(mkRec, num, isNeg) where
data Rec = Rec
{ _num :: Int
, _isNeg :: Bool
}
num :: Rec -> Int
num = _num
isNeg :: Rec -> Bool
isNeg = _isNeg
mkRec :: Int -> Rec
mkRec n = Rec n (n < 0)
However, I'd assert that, morally, the _correct_ thing to do would be to
disallow record update at all if the constructor is not in-scope. The
purpose of hiding the constructor at all is to ensure that a programmer
must perform certain computations in order to construct a valid value,
e.g. to enforce invariants on constructed data (as I'm doing here), or
to avoid the possibility of pattern-matching on data. If you a programmer
hides a constructor but exports its accessors, then generally I'd assert
it's because of the former reason, so it would be sensible to prevent
record update, as you could always write your own updates, if you so
desire.
Of course, pointing out this flaw in light of the other problems with the
Haskell record system is like complaining about the in-flight movie on
a crashing plane, but still.