dburrows/ blog/ entry/ Abstracting over record fields in Haskell

It seems like I often run into situations in Haskell where it would be nice to be able to refer indirectly to a member of a record. For instance, say that I have a command-line program that can accept many options. I might choose to store its configuration in a large record:

data Settings = Settings { fld1 :: Type1, fld2 :: Type2, ... }

The intuitive way of describing command-line options for a Haskell programmer would be something like:

let options = [ BoolOption   (Just "--foo") (Just "-f") fld1,
                StringOption (Just "--bar") Nothing     fld2,
                ... ]

However, this doesn't work. Since the type of fld1 is Settings -> Type1, there's no way to use it to modify the value of fld1 in a Settings record (or rather, to produce a new Settings record with a different value of fld1). In other words, we need something like this:

let options = [ BoolOption (Just "--foo") (Just "-f") fld1 (\s val -> s { fld1 = val }),
                ... ]

As the number of setings increases, it becomes annoying and error-prone to type out a setter function for each field of the record. Moreover, it feels very out of place, in a language where we can generalize over almost everything else, that we can't generalize over record fields.

Other languages have solved this problem. Of course, in languages that aren't statically typed you just refer to fields using strings:

field_name = "name"
val = getattr(o, field_name)
setattr(o, field_name, val)

But you can do this in a statically typed language too. C++ has the concept of a pointer to member, which essentially stores the offset of a field within a structure:

struct X
  int a;
  int b;

// ...

int X::* p = &X::a;
X x;
x.*p = 5;

What we want, then, is a pointer to member for Haskell. Probably the easiest way to do this is something like:

data Member t rec = Member { get :: rec -> t;
                             set :: t -> rec -> rec }

Now you can just write:

data Settings = Settings { realFld1 :: Type1, ... }
fld1 = Member realFld1 (\x r -> r { realFld1 = x})
-- ...
let y = get s fld1 in
(y, set s (y - 1))

This still needs a lot of boilerplate code for every record, though, and of course there's no guarantee that accesses will be inlined (I would assume that compilers treat record accessors specially in current compilers). Ideally the field declarations would be written out by the compiler, but this would conflict with fields being functions in Haskell98; Haskell is a mature enough language that I assume it's not acceptable to break existing code.

Is there a nice way of fixing these problems and getting reasonable indirection to record members?