Records in Haskell

Gábor Lehel illissius at gmail.com
Sat Feb 25 16:18:08 CET 2012


On Sat, Feb 25, 2012 at 3:54 PM, Barney Hilken <b.hilken at ntlworld.com> wrote:
> After more pondering, I finally think I understand what the DORFistas want. Here is an example:
>
> You want to define records which describe people, and include (among other things) a field called "name". There might be several different record types with a name field, depending on whether the record refers to a customer, an employee, a business contact etc., but in each case "name" is the name of the person to which the record refers. You then write various functions which assume this, such as
>
>>       spam :: Has r "name" String => r -> String
>>       spam r = "Dear " ++ r.name ++ "\nHave you heard..."
>
> Now I want to define records which describe products, and I also use a field "name" in the same way, except that it is the brand name of the product. I also define functions such as
>
>>       offer :: Has r "name" String => r -> String
>>       offer r = "Reduced! " ++ r.name ++ " 50% off!"
>
> It doesn't make any sense to apply your functions to my records or vice-versa, but because we both chose the same label, the compiler allows it. Putting the code in separate modules makes no difference, since labels are global.

Exactly!

>
>
> Here is a simple solution, using SORF:
>
> The real problem is that the polymorphism of spam and offer is too general. We should each define new classes
>
>>       class Has r "name" String => HasPersonalName r
>>       class Has r "name" String => HasBrandName r
>
> and make  each of our record types an instance of this class
>
>>       instance HasPersonalName EmployeeRecord
>>       instance HasPersonalName CustomerRecord
>>       instance HasBrandName FoodRecord
>
> then we can define functions with a more specific polymorphism
>
>>       spam :: HasPersonalName r => r -> String
>>       spam r = "Dear " ++ r.name ++ "\nHave you heard..."
>
>>       offer :: HasBrandName r => r -> String
>>       offer r = "Reduced! " ++ r.name ++ " 50% off!"
>
> Now there is no danger of confusing the two uses of "name", because my records are not instances of HasPersonalName, they are instances of HasBrandName. You only use the class Has if you really want things to be polymorphic over all records, otherwise you use the more specific class.
>
>
> This seems to me a much simpler approach than building the mechanism in to the language as DORF does, and it's also more general, because it isn't hard linked to the module system. Does it have any disadvantages?

I can't tell offhand whether it has any drawbacks with respect to
expressiveness. It seems to be a good solution to the stated problem,
so thank you for proposing it.

My objection is that I'm not sure if there is ever a case where "you
really want things to be polymorphic over all records". There is
nothing (as far as I know) analogous to this kind of implicit
name-based polymorphism anywhere in Haskell. It doesn't seem like the
Haskell way to have the less safe thing as the one that's default and
convenient, and to allow the programmer to layer a more-safe thing on
top of it if he or she wants to. It seems more like the Haskell way to
have the safer thing be the default and to require extra work if you
want to do something less safe*. In this specific case, is there any
actual use case for global implicitly-polymorphic-by-name record
fields, where that is actually what you want, and where the DORFish
way which is analogous to classes-and-instances wouldn't be
appropriate?

* (Now granted, if pure code versus unsafePerformIO is white versus
black, then this is shade-of-gray versus
slightly-darker-shade-of-gray, but the principle is the same.)



More information about the Glasgow-haskell-users mailing list