Records in Haskell

Greg Weber greg at gregweber.info
Wed Jan 18 06:28:21 CET 2012


On Fri, Jan 13, 2012 at 8:52 PM, Simon Peyton-Jones
<simonpj at microsoft.com>wrote:

>
> But, the Has constraints MUST exist, in full glory, in the constraint
> solver.  The only question is whether you can *abstract* over them.
> Imagine having a Num class that you could not abstract over. So you
> could write
>
>   k1 x = x + x :: Float
>   k2 x = x + x :: Integer
>   k3 x = x + x :: Int
>
> using the same '+' every time, which generates a Num constraint. The
> type signature fixes the type to Float, Integer, Int respectively, and
> tells you which '+' to use.  And that is exactly what ML does!
>
> But Haskell doesn't.  The Coolest Thing about Haskell is that you get
> to *abstract* over those Num constraints, so you can write
>
>  k :: Num a => a -> a
>  k x = x + x
>
> and now it works over *any* Num type.
>
> On reflection, it would be absurd not to do ths same thing for Has
> constraints.  If we are forced to have Has constraints internally, it
> woudl be criminal not to abstract over them.  And that is precisely
> what SORF is.
>

So I understand that internally a Has constraint is great for resolving the
type.
What is the use case for having the Has abstraction externally exposed?

I think there is a great temptation for this because we would have a
functionality we can point to that has some kind of extensible record
capability.

But I believe the Has abstraction to be a form of weak-typing more so than
a form of extensibility. Just because 2 records have a field with the same
name, even with the same type in no way signifies they are related. Instead
we need a formal contract for such a relation. We already have that in type
classes, and they can already be used for this very capability in a more
type-safe way.

Let me give an example use case that we would be tempted to use this for:
database record projections. Given a record representing a row of a
database table

    data Row = Row { smallName :: String, largeVideo :: ByteString }

We may have occasions in which we just use a subset of the database/record
fields.

    display :: Row -> String
    display r = smallName r

We can gain efficiency by just selecting the fields we need from the
database (smallName). But now I need to re-factor all my Haskell code to
have a projection of the original record with just the needed fields. I
could try to make a sum type with projections, but then I need to pattern
match on them.

This seems like a great use case for a generic Has abstraction, but that
broadens the types allowed to outside of just Record. Instead what I really
need is a more specific Has abstraction. I want to write just:

    display ::  RowSmallName r => r -> String

So instead I should use type classes, but not something generic for all
possible records, something for all possible Row projections. I do have to
name the different Row types, and deal with re-factoring when field usage
changes, but at least I have type-safety.

i would like to avoid re-factoring and have the compiler to generate this
constraint automatically based on what fields are required in the function.

    display :: RowProjection "smallName" -> String


Ideally this information is propogated back to the point where I create the
records. Using the Persistent library:

    records <- selectList [RecordSmallName .== "Bob"] []

Persistent uses Template Haskell to automatically define serializing to and
from records, including the full database query projection. We could
generate instances for the different available record projections
automatically ahead of time. With this kind of system, we would have an
amazing feature of automatic database projection for queries. But maybe
this kind of system would end up with horrible error messages or
have details that are very difficult to work out.

My point is that Has abstractions are weak types and that likely we should
be searching for something stronger or using type classes. If I am wrong
then we should have convincing use cases outlined before we make this a
goal of a records implementation, and still I don't see why it needs to be
a blocking requirement if we are just trying to solve the basic records
issue.


Greg Weber
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/glasgow-haskell-users/attachments/20120118/feab1937/attachment.htm>


More information about the Glasgow-haskell-users mailing list