A Petty And Insignificant Complaint About Haskell Records
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.