Yet another proposal for Haskell – the ever growing language

March 22, 2009

What Do You Want? There’s a Haskell extension for that

We have a pure, lazy, statically typed programming language. What should we do with it? Systems Haskell? Sure! Lets also add open type functions, for correctness and extra cool points! And while we are playing with correctness, how about invariants! If this hasn’t added plenty of new keywords/syntax then we’ll throw on class aliases.

How About A Proposal Without More Syntax?

All of the above are powerful and useful – but they make the language harder to learn as well. This proposal does not add any power to the language but removes a basic restriction (unless you’re writing the compiler).

Perhaps the most common complaint from new Haskell programmers is the inabillity to overlap data field names:

data Person = Per { name :: String, age :: Int }
data Pet = Pet {name :: String, age :: Float } | Rock

The proposal is simply to allow this – but how?

Right now, fields of different types with the same name must be in different modules so the fully qualified functions are, well, different (and well typed). Instead, lets treat fields as sugar for a hidden type class. I say ‘hidden’ because the programmer would not have access to it; the class exists only in an intermediate representation. Typically the field selector is a monomorphic funciton “name :: Person -> String” and can not addiitonally be “name :: Pet -> String” but it is proposed that the type be “name :: (NameField n a) => n -> a“.

The above example would have an intermediate representation of:

data Person = Per String Int
data Pet = Pet String Float | Rock

class NameField n a where
    name :: n -> a

class AgeField n a where
    age :: n -> a

instance NameField Person String where
    name (Per n _) = n

instance AgeField Person Int where
    age (Per _ a) = a

instance NameField Pet String where
    name (Pet n _) = n
    name Rock = error "No field 'name' for constructor 'Rock' of type 'Pet'"

instance AgeField Pet Float where
    age (Ped _ a) = a
    age Rock = error "No field 'age' for constructor 'Rock' of type 'Pet'"

In other words, fields would be a concise way to define a type class and instances for the given data type.

Remaining Monomorphic

Remaining monomorphic with respect to these classes is desireable in this case 1) to avoid extra dictionaries 2) because the programmer has no access to the hidden typeclasses and cannot specify complex types such as sumAges :: (Num a, AgeField n a) => [n] -> a. So how do we reasonably respond when someone writes a function hoping to get the hidden-typeclass polymophism inferred? While non-ideal, I propose this be rejected with an Ambiguous Type message. Its worth noting that to make a sumAges function requires an explicit type class and instances – which is no more than what is currently required.

Debugging

On IRC someone mentioned this would make the debugging messages ugly. We shouldn’t complain to the developer about type classes that don’t appear in the source code.

func :: Int -> Int
func i = age i
...
 No instance for (AgeField Int Int)
 arising from a use of `age' at [...]
 Possible fix: add an instance declaration for (AgeField Int Int)

But this isn’t that bad. What we currently get when abusing fields is:

Couldn't match expected type `Person'
against inferred type `[...]'

By keeping an extra bit of information, marking each field selector function, we could probably produce an even better error message:

Inferred type `[...]' does not have field selector `age'
In the first argument [...]
In the expression [...]
In the definition [...]
About these ads

8 Responses to “Yet another proposal for Haskell – the ever growing language”

  1. augustss Says:

    For completeness you need to talk about record update as well.


  2. lets treat fields as sugar for a hidden type class

    What is the motivation for hiding the type classes?
    What is the motivitation for disallowing them in signatures?

    These impose restrictions. You better get something in return.

  3. Brent Says:

    What would happen if I write this?

    data Person = Per { age :: Int }

    foo p = age p + 1

    Currently, Haskell will happily infer the type of foo for me. But under your proposal, it would have to be rejected with an Ambiguous Type message, since type classes are open, even though I only have one record with an ‘age’ field. It would be really annoying to have to add type annotations to lots of things that used to work without them.

  4. Chris Eidhof Says:

    Type classes are just a special instance of qualified types, maybe it would be interesting to see if there could be something similar to typeclasses but just for records. I’m not sure whether that makes sense, but it might be worth exploring.

  5. solrize Says:

    I thought there were already some GHC extensions for that

    8.3.11. Record field disambiguation

    in the GHC syntax extensions doc

    http://www.haskell.org/ghc/docs/6.10.1/html/users_guide/syntax-extns.html

  6. tommd Says:

    solrize: No, that still requires the data definitions to come from separate modules.

    Brent: Arrrgh, right you are – there are absurdly basic uses that break down with this change. Not sure if there’s anything that could be done about that.

    Tom: Hidding the type classes is debatable, I’ll agree. I was looking for something that would remove the restriction and not add any complexity (which I consider the less-objectable path). Allowing the programmer to access an implicitly declared and instanced type class could be annoying when analysing code because you wouldn’t always be able to find the classes and instances explicitly declared or derived.

  7. Luke Plant Says:

    solrize: It also only works when explicitly using constructors. In my experience that feature is of very limited use, and can sometimes make things worse e.g. if you use record field disambiguation and have:

    foo = Foo { name = “fred”, age = 0 }
    bar = Bar { name = “bob”, age = 0 }

    but then decide you also need to make some data structures based on foo and/or bar:

    older_foo = foo { age = 100 }
    older_bar = bar { age = 100 }

    That won’t work, and you’ll need to do a qualified import in addition to the normal import. You’d have been better off just doing the qualified import to start with.


  8. [...] Yet another proposal for Haskell – the ever growing language What Do You Want? There’s a Haskell extension for that We have a pure, lazy, statically typed programming [...] [...]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: