Why not allow empty record updates?

Edward Kmett ekmett at gmail.com
Wed Nov 16 05:09:16 CET 2011

Sent from my iPad

On Nov 15, 2011, at 7:18 PM, wren ng thornton <wren at freegeek.org> wrote:

> On 11/15/11 12:33 PM, Yitzchak Gale wrote:
>> Simon Peyton-Jones wrote:
>>>>>> Trouble is, what type does this have?
>>>>>>   f x = x {}
>> Malcolm Wallace wrote:
>>>>> f :: a ->  a
>> Ian Lynagh wrote:
>>>> That wouldn't help the original poster, as it is incompatible with
>>>> f :: Foo Clean ->  Foo Dirty
>> Only because in that expression the type of x is not known.
>>>>>> ...the whole feature of type-changing update is (as you know)
>>>>>> a bit obscure and not widely used, so it'd be adding
>>>>>> complexity to an already-dark corner.
>> To me, at least, that is surprising. The report implies that
>> record updates are just sugar for the given case expression.
>> Whether or not it changes a type parameter seems
>> unimportant.
>> In fact, I would even advocate adding a line of explanation
>> in the Report that this is a convenient way of copying
>> a value from an ADT to itself with a different type
>> as its parameter. I agree with Malcolm that this is
>> analogous to using empty record syntax in a pattern
>> to avoid hard-coding the number of parameter to
>> a constructor.
>> I usually avoid using the combination of type parameters and
>> record syntax altogether, mainly because this obvious syntax
>> doesn't work. Perhaps that's the reason why type-changing
>> update is not widely used.
>> (Admittedly, I didn't think of Herbert's trick. But doesn't
>> that seem like somewhat of an ugly hack?)
>> Are you hesitant because of implementation difficulty,
>> or only because you are worried about the semantics
>> being confusing? In my opinion, it's more confusing
>> the way it is now.
> For what it's worth, I do the exact same thing in the project I've been working on. The phantom type is a clean/dirty bit even :)
> It's an incredibly helpful thing to have for records. Especially for the context I'm in: I'm generating summary data over gobs of input, but the input can come incrementally. So long as the core of the summary is correct, then I don't care about maintaining the cache fields while I'm just shoveling data in; but I do want to make sure the caches are valid before I try to get any information out. This is exactly the sort of type-level hackery which makes Haskell a joy to work in and other languages such a pain.
> So far I've just defined helper functions to adjust the phantom type[1], each of which is implemented by (\x -> x { foo = foo x }). It's a horrible hack, but at least it's hidden away in library functions instead of something I have to look at. The annoying part is that when I adjust the members of the records, if I remove or rename foo then I have to fix all those coercion functions too.

My biggest issue is loss of sharing, but you could always use

castFoo = asTypeOf unsafeCoerce $ \x -> x { foo = foo x }

to maximize sharing, but that doesn't help with the code rewriting,

Or less horrifically just carry the phantom in a newtype wrapper wrapped around your record, and cast by putting it on and taking it off, which also maximizes sharing in exchange for newtype noise on access.

More information about the Glasgow-haskell-users mailing list