Something to watch for while using record updating syntax
Let’s pop open GHCi and construct a record with a type tag.
We’ll want to be able to add some extra type annotations (
thing :: type) in places we can’t normally, so let’s turn on
We want to make a type
X with a tag
t lets us differentiate between
Xs by keeping information in the type, but without having to store any additional values.
X also has a record with some stuff inside it, but the kind of stuff in
X isn’t super relevant.
Now that we have an
X type, let’s make a value of type
Double-check the type of
myX. Looks like it’s still hanging on to the
However, what happens when we try to update the record? Looks like
Int has gone away, leading to the much-less-informative polymorphic
We lost our tag due to the record update! This might be confusing in real code, since we can do things like assign what was just previously an
X Int to an
X String. Because the record update doesn’t keep the tag around, the tag’s usefulness diminishes completely in this case.
To understand why this happens, let’s write a similar updater function ourselves.
replaceC is a function that takes an
X and replaces the
c inside. It acts just like a record update.
Also just like a record update,
replaceC doesn’t ensure that the tag sticks around.
Looking at its type, we can see why:
The most general type for
t2 to be different (and GHC loves to be as general as it can). To say that they should be the same is an additional restriction. However, in the case of keeping tags around, it’s really nice not to lose the tag just because you updated some other part of the data structure.
By adding a type signature specifying the restriction that
replaceC should preserve
t, we get to keep tags even when we replace bits of the record.
However, I wasn’t able to find a great way to do this tag preservation while still using the record update syntax (which is essentially just some syntactic sugar over a function kind of like
replaceC). Hopefully someone knows a good trick for this.