Search code examples
haskellrecord

Disallow record updates for a given type in Haskell


Let's say I have some types:

data Person = Person
  { worksFor :: String
  , ...
  }

data Company = Company
  { companyId :: String
  , address :: String
  ,...
  }

companies :: [Company]
people :: [Person]

worksFor in Person references a given Company by way of companyId. Let's then say I tie the knot and end up with:

data Person' = Person'
  { worksFor :: Company'
  , ...
  }

data Company' = Company'
  { ...
  }

Obviously, multiple Persons can reference the same Company, but in Person' this sharing is lost. Also obviously, if I update a given Company' this update will not be reflected in the values of type Person' that reference that Company'.

I'm aware of IxSet and the like, but I would prefer to keep it simple. I'd like to disallow any kind of record updates of the Person' and Company' records, while still being able to access their fields.

One solution would be to define Company' and Person' in a separate module with NoTraditionalRecordSyntax allowed (and thus defining the datatypes without record syntax, e.g. data Company' = Company' String String ...), define selector functions mimicking the field selectors generated by GHC, and not export any constructors, but that seems like overkill.

Another one would be to wrap every field in the ...' data types with e.g. data Gettable a = Gettable { get :: a } and not export the Gettable constructor, but then every field access must be preceded with get, which is pretty ugly.

I'm wondering if there's another way to achieve this which I'm not seeing?


Solution

  • One solution would be to define Company' and Person' in a separate module with NoTraditionalRecordSyntax allowed (and thus defining the datatypes without record syntax, e.g. data Company' = Company' String String ...), define selector functions mimicking the field selectors generated by GHC, and not export any constructors, but that seems like overkill.

    This sounds exactly right to me. The purpose of record syntax is to declare that your type is just a boring concrete tuple type, with any values allowed for all of its fields. You don't want this; you want an abstract type with access controls. So, don't use a record, don't export the constructor, and define selector functions by hand. A bit of a hassle, but sometimes that's the price of the right API.