Records in Haskell: Type-Indexed Records (another proposal)

Hello all. I wrote a new proposal for the Haskell record system. It can be found at http://hackage.haskell.org/trac/ghc/wiki/Records/TypeIndexedRecords Records are indexed by arbitrary Haskell types. Scope is controlled as scope of key types. No fieldLabel declarations needed (as in DORF). Cheers, strake

Matthew Farkas-Dyck
Hello all.
I wrote a new proposal for the Haskell record system. It can be found at http://hackage.haskell.org/trac/ghc/wiki/Records/TypeIndexedRecords
Records are indexed by arbitrary Haskell types. Scope is controlled as scope of key types. No fieldLabel declarations needed (as in DORF).
Cheers, strake
Thanks Matthew, It's good to explore the design space. Apart from the Quasifunctor bit, I think you'll find your proposal is a rather cut-down version of DORF, just using different syntactic sugar. (Oh, and with the arguments to Has in a different order, just to be confusing.) You do have the equivalent of fieldLabel decls. Those are all your type indexes: data X = X, etc. And you suggest defining x = X Which is equivalent to DORF mapping from field name `x` to phantom type Proxy_x, (but DORF keeps `x` as a field selector function, similar to H98). To make `x` a selector function instead, you'd go: x = (.) X -- or probably x = get X, see below Which is exactly the same as DORF (after adjusting for the different order of arguments). And presumably instead of X you'd want a LongandMeaningfulLabel? And if your data Customer_id = Customer_id was always an Int field, wouldn't it help the reader and the compiler to say that? (That's the main extra part in fieldLabels.) I think you don't want all those type vars in your record decls -- but only vars for the mutatable types, like this: type R c = { X ::. Int, Y::. String, Z ::. c, ... } Then you don't need a Quasifunctor instance for every field, only the mutatable ones. Oh, and how do you deal with multiple record constructors as in H98: data T a = T1 { x :: a, y :: Bool } | T2 { x :: a } It wouldn't work to have a different record type for each constructor, 'cos you'd turn functions that use them from mono to polymorphic (overloaded -- needing a class and instances). You don't give full details for your Has instances, but presumably you'd do the same equality constraint style as SORF and DORF. I think you still need method get and sugar to turn the dot notation into a call to get. Having method (.) will usurp altogether dot as function composition -- you'll make a lot of enemies! And we need tight binding for dot notation, so we might as well treat it as special syntax. You don't show how you'd do record update. The litmus test is what is the type for: r{ X = True } That is: update record r, set its X field to True. AntC

On 03/03/2012, AntC
Apart from the Quasifunctor bit, I think you'll find your proposal is a rather cut-down version of DORF, just using different syntactic sugar.
(Oh, and with the arguments to Has in a different order, just to be confusing.)
Not so. I chose this order to make it easier to curry.
You do have the equivalent of fieldLabel decls. Those are all your type indexes: data X = X, etc.
True, but data is not a new keyword.
And you suggest defining x = X
We can define x = X, if we wish, but we need not; we could rather define x as a selector. It's just that lower-case labels are customary in Haskell.
Which is equivalent to DORF mapping from field name `x` to phantom type Proxy_x, (but DORF keeps `x` as a field selector function, similar to H98).
Ah, not quite. In DORF, the phantom type is an implicit, magical type, but in TIR it's an explicit, declared type. In DORF, either the magical type is in scope, or not; in the former case, it might clash with a user-defined type, and in the latter, if I wish to call set, how shall I type its argument? In TIR, the key type is user-defined, so if there be a clash, then the user is at fault.
To make `x` a selector function instead, you'd go: x = (.) X -- or probably x = get X, see below Which is exactly the same as DORF (after adjusting for the different order of arguments).
True.
And presumably instead of X you'd want a LongandMeaningfulLabel?
No! Real Programmers never choose such names! I jest. Yes, plainly, I would. X is just an example.
And if your data Customer_id = Customer_id was always an Int field, wouldn't it help the reader and the compiler to say that? (That's the main extra part in fieldLabels.)
It might help the reader, but so would a simple comment. Nevertheless, this is fair. It might help the compiler, but that's an argument by premature optimization, I think (^_~)
I think you don't want all those type vars in your record decls -- but only vars for the mutatable types, like this:
type R c = { X ::. Int, Y::. String, Z ::. c, ... }
Then you don't need a Quasifunctor instance for every field, only the mutatable ones.
Yes, I know. That is just a very general example.
Oh, and how do you deal with multiple record constructors as in H98: data T a = T1 { x :: a, y :: Bool } | T2 { x :: a }
It wouldn't work to have a different record type for each constructor, 'cos you'd turn functions that use them from mono to polymorphic (overloaded -- needing a class and instances).
Not sure what you mean. With an argument of such a multiconstructed type, I would do as ever in Haskell: pattern-match.
You don't give full details for your Has instances, but presumably you'd do the same equality constraint style as SORF and DORF.
I assume you mean instance (v~a) => Has k v (R a) where ... I'm not sure why we need this, but I assume that we do, since it was written by SPJ, so yes.
I think you still need method get and sugar to turn the dot notation into a call to get. Having method (.) will usurp altogether dot as function composition -- you'll make a lot of enemies! And we need tight binding for dot notation, so we might as well treat it as special syntax.
Not need. (.) is quite a valid name. Nevertheless, this is fair. I meant dot as an example (though one that might ultimately be chosen). I like bang, myself; others seem to favour get. The trouble is, in the latter case, that we'd need to change certain widely-used libraries...
You don't show how you'd do record update.
Yep. It's on the wiki. "qfmap X f r is r mutated by f at X"
The litmus test is what is the type for: r{ X = True } That is: update record r, set its X field to True.
This is written as qfmap X (const True) (r :: r) :: Quasifunctor X a Bool r s => s;
AntC
Cheers, strake

Matthew Farkas-Dyck
I think you don't want all those type vars in your record decls -- but only vars for the mutatable types, like this:
type R c = { X ::. Int, Y::. String, Z ::. c, ... }
Then you don't need a Quasifunctor instance for every field, only the mutatable ones.
Yes, I know. That is just a very general example.
Oh, and how do you deal with multiple record constructors as in H98: data T a = T1 { x :: a, y :: Bool } | T2 { x :: a }
Not sure what you mean. With an argument of such a multiconstructed type, I would do as ever in Haskell: pattern-match.
So please show what record declarations look like. And how they get turned into Has instances. Your example decl on the wiki for R a b c is not valid Haskell. What's more the wiki hss it as a `type`. Did you mean a `data`? I'm confused.
You don't give full details for your Has instances, but presumably you'd do the same equality constraint style as SORF and DORF.
I assume you mean instance (v~a) => Has k v (R a) where ...
I'm not sure why we need this, but I assume that we do, since it was written by SPJ, so yes.
Matthew, you really, really need to understand why SPJ put it that way, if you want your proposal to be taken seriously. He wasn't just making it up.
You don't show how you'd do record update.
Yep. It's on the wiki. "qfmap X f r is r mutated by f at X"
So do you mean this is what developers put in the code?
what is the type for: r{ X = True } That is: update record r, set its X field to True.
This is written as qfmap X (const True) (r :: r) :: Quasifunctor X a Bool r s => s;
You mean this is what to put in the code? DORF is getting beaten up for the amount of boilerplate the programmer is expected to add (for fieldLabels, etc.) I can't compare apples with apples for your proposal, because I can't see what the code looks like that would appear in the program. So far (apart from Quasifunctor) all I can see is that you're varying the sugar, without adding anything to the semantics -- except you've not given the surface syntax, so I'm only guessing. AntC

On 06/03/2012, AntC
Matthew Farkas-Dyck
writes: Oh, and how do you deal with multiple record constructors as in H98: data T a = T1 { x :: a, y :: Bool } | T2 { x :: a }
Not sure what you mean. With an argument of such a multiconstructed type, I would do as ever in Haskell: pattern-match.
So please show what record declarations look like. And how they get turned into Has instances. Your example decl on the wiki for R a b c is not valid Haskell.
Yet. That's what this all is about.
What's more the wiki hss it as a `type`. Did you mean a `data`? I'm confused.
Nope. The record type in this case is { X ::. a, Y ::. b, Z ::. c, ... } A value of this type is a record.
You don't give full details for your Has instances, but presumably you'd do the same equality constraint style as SORF and DORF.
I assume you mean instance (v~a) => Has k v (R a) where ...
I'm not sure why we need this, but I assume that we do, since it was written by SPJ, so yes.
Matthew, you really, really need to understand why SPJ put it that way, if you want your proposal to be taken seriously. He wasn't just making it up.
Sorry. I am no Haskell wizard, and it wasn't very clear on the wiki. "Improves" the type of the result...?
You don't show how you'd do record update.
Yep. It's on the wiki. "qfmap X f r is r mutated by f at X"
So do you mean this is what developers put in the code?
what is the type for: r{ X = True } That is: update record r, set its X field to True.
This is written as qfmap X (const True) (r :: r) :: Quasifunctor X a Bool r s => s;
You mean this is what to put in the code?
Well, one could, but as I said, we might (rather, ought to) define some sugar, since it's ugly (>_<)
DORF is getting beaten up for the amount of boilerplate the programmer is expected to add (for fieldLabels, etc.) I can't compare apples with apples for your proposal, because I can't see what the code looks like that would appear in the program.
So far (apart from Quasifunctor) all I can see is that you're varying the sugar, without adding anything to the semantics -- except you've not given the surface syntax, so I'm only guessing.
I have given the surface syntax, actually, just with no sugar. I meant not to add to the semantics. Rather, I meant to simplify them. Anyhow, since I read DORF a few more times, my proposal seems more alike than I thought, when I wrote it. Nevertheless, the magic types bother me, for the aforesaid reasons.
AntC
Cheers, strake
participants (2)
-
AntC
-
Matthew Farkas-Dyck