{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreihyhwgm2id5wzj3ka3hybf4guqgs76zhny2siaaoahxxqtsgqub74",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mipcm7wf7t72"
  },
  "path": "/t/rfc-mutable-records-as-a-ghc-extension/13886#post_1",
  "publishedAt": "2026-04-04T10:04:31.000Z",
  "site": "https://discourse.haskell.org",
  "tags": [
    "Reddit",
    "OverloadedRecordUpdate",
    "GHC supports these directly",
    "primitive#SmallArray",
    "rather simple",
    "3.15.3"
  ],
  "textContent": "For clarity I won’t be using the phrase “immutable records” in this topic. Only “ADT products”, “field labels” and “record syntax”.\n\n## 1. Status quo\n\nThrough the years there have been numerous attempts at implementing mutable records separately from GHC (see e.g. this Reddit thread from 2018). All of them have ultimately failed, as Haskell’s type system on its own is too verbose to make such a feature look good and too weak to support it properly.\n\nThe need for mutable records has however never left. The community chose to emulate them using ADT products, a task I argue they’re not fit for, leading to several curious outcomes:\n\n  1. Complaints about laziness as a norm.\n\nThe complaints are never about it being hard to use laziness, they’re about not being able to escape it. Everyone is signed up to the notion that they have to put their application state into an ADT product, at which point a single failure to fully evaluate the next edition of their entire state snowballs into a memory leak.\n\n  2. Partial ADT product updates as a feature.\n\nADTs cannot be updated in place, they’re constructed anew each time, right? Why do we then use `x { a = b }` and not `C { a = b, .. = x }`? I have no idea how to reconcile nested updates in OverloadedRecordUpdate with this, and this is currently the community-consensus solution to proper record syntax.\n\n  3. Allocation cost for copies of ADTs as the cost of doing business.\n\nYes, I get it, it’s a bump-allocating garbage collector, allocations are dirt-cheap; no, I shouldn’t have to waste space and time duplicating references to things I’m not interacting with in the moment.\n\n\n\n\n## 2. IORefs\n\nThe closest we can get to mutable records in current Haskell is an ADT product of `IORef`s. This allows accessing each element independently, with addresses each of the problems outlined above. It’s also obviously a terrible approach, as it requires allocating a number of `IORef`s linear to the number of values.\n\nThe documentation to `MutVar#`, which backs `IORef`, says\n\n> A `MutVar#` behaves like a single-element mutable array.\n\nA lot of people know about mutable arrays of values through `vector`, but in fact GHC supports these directly. There is however no nice way to access such arrays heterogeneously in today’s Haskell.\n\n## 3. Mutable records\n\nConsider the following module, taking both function names and behaviors from primitive#SmallArray (which in turn took from `GHC.Exts`):\n\n\n    module Data.Mutable where\n\n      -- Kind of mutable records\n      data MutableLayout\n\n      -- Instances for this type class are only created by the compiler.\n      class HasMutableField (x :: Symbol) (r :: MutableLayout) a | x r -> a\n\n      data Immutable (r :: MutableLayout)\n      index :: HasMutableField x r a => Immutable r -> a\n\n      data Mutable (r :: MutableLayout)\n      read :: HasMutableField x r a => Mutable r -> IO a\n      write :: HasMutableField x r a => Mutable r -> a -> IO ()\n      thaw :: Immutable r -> IO (Mutable r)\n      freeze :: Mutable r -> IO (Immutable r)\n\n      -- More variants, e.g. MutableST, can be added below.\n\n\nWe can then write a declaration like the following, mirroring a declaration of an ADT product with field labels:\n\n\n    {-# LANGUAGE MutableRecords #-}\n\n    mutable Con a b =\n      Con\n        { a :: a\n        , b :: !(IntMap b)\n        , c :: {-# UNPACK #-} !Int\n        , d :: Mutable Foo\n        }\n\n\nThis declaration would:\n\n  * Add type `Con :: Type -> Type -> MutableLayout` to the type-level;\n\n  * Add a constructor `Con` that serves as a record pattern synonym for\n`Immutable (Con a b)`;\n\n  * Have GHC infer the internal data representation for this mutable record;\n\n  * Create a `HasMutableField` instance for each field, describing how each field is to be accessed.\n\n\n\n\nUsing the constructor and `thaw` is thus the only way to create a new mutable record, which can be read from and written to in `IO`, and converted back to immutable using `freeze`.\n\nCaveats:\n\n  * To support unpacking fields `[Im]mutable` will have to be backed by a special array that combines `[Mutable]ByteArray#` and `Small[Mutable]Array#`. The data layouts for both are rather simple, this shouldn’t be a problem;\n\n  * Using record syntax to partially update an `Immutable` should not be allowed, as this can be done directly using mutation. This means that `x { a = b }` can never refer to an `Immutable` pattern synonym, simplifying implementation;\n\n  * Record pattern synonyms are not a GHC feature at the moment, but I assume making GHC create special synonyms for this occasion is a separate, simpler, topic;\n\n  * Nested record update would require a separate type class\n(e.g. `nestedWrite @'[\"foo\", \"bar\", \"baz\"]`), which could be simplified further with a magic type family that breaks `Symbol`s on periods\n(`nestedWrite @\"foo.bar.baz\"`);\n\n  * How GHC chooses to store the fields in a mutable record is an implementation detail that should not be exposed to the user. This feature is not here to solve FFI interoperability;\n\n  * These records are not extensible, I’m not opening that can of worms.\n\n\n\n\nAnd that’s it. Every part of this seems to already exist in GHC today, doesn’t seem like it would require any massive changes, it’s well-contained in a module and an extension.\n\n## 4. GHC proposal\n\nThere’s only one issue here: the claims I’m making in the first section are quite outlandish. Virtually every real-world program out there is structured as described and the conclusion I’m ultimately drawing towards is that the syntax for `x { a = b }` should be deprecated (it’s in the Haskell98 report, section 3.15.3).\n\nIt is thus important for me to see what community’s views on this are and whether mutable records are viewed as a necessary, or at least interesting, feature. Should this gain enough traction I’ll expand it into a GHC proposal.",
  "title": "[RFC] Mutable records as a GHC extension"
}