Proposal: Allow "\=" for field update in record update syntax
I don't know what it takes to get this sort of change into circulation, but I assume it starts with a clear and specific description of the problem, exploration of alternatives, and a specific proposed solution. So here goes: ---------- Proposal: Allow "\=" for field update in record update syntax Specifically, intepret rec {field\=fn} as rec {field = fn (field rec)} Jusitification: It is extremely common when updating records to be updating a field value based on its prior value rather than simply assigning a new value from nothing. Unforunately the syntax supports the later clearly but makes the former awkward to read, understand, and debug: myFun db myType1 = db { tMyType1 = insert myType1 $ tMyType1 db nextId = 1 + (nextId db) } One solution to this problem that does not involve changing syntax is to generate field update functions for every field of a type e.g.: update_tMyType1 f s = s {tMyType1= f $ myType1 s} update_nextId f s = s {nextId = f $ nextId s} Once you have those functions, myFun looks much better: myFun db myType = update_nextId (+1) $ update_tMyType1 f db where f= insert myType1 But, generating the update_ functions is not programming; its just manual syntax generation. And, even if you have template haskell to do it, * it is annoying to have to find/write the TH code to implement * update_ is incredibly verbose * shortening it risks proliferation of psuedo-syntax: u_tMyType, u__tMyType, or utMyType? * it adds clutter to have to call the TH for each record type, and * it pollutes the name space/increases risk of name collision Rather than force the user to jump through these hoops, why not make the syntax flow naturally from the existing record update syntax? It makes sense to use \= for this task as it is the functional generalization of C or Java's += or *= . Then myFun would have the elegant and clear syntax: myFun db myType1 =db {tMyType1\=insert myType1, nextId\=(+1)} -Alex- ______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com
There was a long discussion of these issues a while back under the title 'updating labelled fields'. The most recent message was from SPJ: http://www.haskell.org//pipermail/glasgow-haskell-users/2002-May/003374.html You should probably take a look at the whole thread... On Thu, 17 Feb 2005, S. Alexander Jacobson wrote:
I don't know what it takes to get this sort of change into circulation, but I assume it starts with a clear and specific description of the problem, exploration of alternatives, and a specific proposed solution. So here goes:
---------- Proposal: Allow "\=" for field update in record update syntax
Specifically, intepret
rec {field\=fn}
as
rec {field = fn (field rec)}
Jusitification:
It is extremely common when updating records to be updating a field value based on its prior value rather than simply assigning a new value from nothing. Unforunately the syntax supports the later clearly but makes the former awkward to read, understand, and debug:
myFun db myType1 = db { tMyType1 = insert myType1 $ tMyType1 db nextId = 1 + (nextId db) }
One solution to this problem that does not involve changing syntax is to generate field update functions for every field of a type e.g.:
update_tMyType1 f s = s {tMyType1= f $ myType1 s} update_nextId f s = s {nextId = f $ nextId s}
Once you have those functions, myFun looks much better:
myFun db myType = update_nextId (+1) $ update_tMyType1 f db where f= insert myType1
But, generating the update_ functions is not programming; its just manual syntax generation. And, even if you have template haskell to do it,
* it is annoying to have to find/write the TH code to implement * update_ is incredibly verbose * shortening it risks proliferation of psuedo-syntax: u_tMyType, u__tMyType, or utMyType? * it adds clutter to have to call the TH for each record type, and * it pollutes the name space/increases risk of name collision
Rather than force the user to jump through these hoops, why not make the syntax flow naturally from the existing record update syntax? It makes sense to use \= for this task as it is the functional generalization of C or Java's += or *= . Then myFun would have the elegant and clear syntax:
myFun db myType1 =db {tMyType1\=insert myType1, nextId\=(+1)}
-Alex-
______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
-- Hal Daume III | hdaume@isi.edu "Arrest this man, he talks in maths." | www.isi.edu/~hdaume
Its a pity template haskell cannot define infix operators, but you could use TH like: $update rec field fn which would expand to: rec { field = fn (rec field) } Keean. S. Alexander Jacobson wrote:
I don't know what it takes to get this sort of change into circulation, but I assume it starts with a clear and specific description of the problem, exploration of alternatives, and a specific proposed solution. So here goes:
---------- Proposal: Allow "\=" for field update in record update syntax
Specifically, intepret
rec {field\=fn}
as
rec {field = fn (field rec)}
Jusitification:
It is extremely common when updating records to be updating a field value based on its prior value rather than simply assigning a new value from nothing. Unforunately the syntax supports the later clearly but makes the former awkward to read, understand, and debug:
myFun db myType1 = db { tMyType1 = insert myType1 $ tMyType1 db nextId = 1 + (nextId db) }
One solution to this problem that does not involve changing syntax is to generate field update functions for every field of a type e.g.:
update_tMyType1 f s = s {tMyType1= f $ myType1 s} update_nextId f s = s {nextId = f $ nextId s}
Once you have those functions, myFun looks much better:
myFun db myType = update_nextId (+1) $ update_tMyType1 f db where f= insert myType1
But, generating the update_ functions is not programming; its just manual syntax generation. And, even if you have template haskell to do it,
* it is annoying to have to find/write the TH code to implement * update_ is incredibly verbose * shortening it risks proliferation of psuedo-syntax: u_tMyType, u__tMyType, or utMyType? * it adds clutter to have to call the TH for each record type, and * it pollutes the name space/increases risk of name collision
Rather than force the user to jump through these hoops, why not make the syntax flow naturally from the existing record update syntax? It makes sense to use \= for this task as it is the functional generalization of C or Java's += or *= . Then myFun would have the elegant and clear syntax:
myFun db myType1 =db {tMyType1\=insert myType1, nextId\=(+1)}
-Alex-
______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Fri, 18 Feb 2005, Keean Schupke wrote:
Its a pity template haskell cannot define infix operators, but you could use TH like:
$update rec field fn
which would expand to:
rec { field = fn (rec field) }
That doesn't help you so much if you want to update more than one field at a time. I think the best case syntax in this spirit is to define: x // f = f x infixl 6 // $(a field val) => \rec -> rec {field = val} $(u field fn) => \rec -> rec {field = fn (field rec)} Which we would then be able to use like this: fun rec = rec // $(u field1 fn) . $(a field2 val) But, I'm not sure that is superior to having the user explicitly derive u_ and a_ functions on the rectype which looks like this: $(deriveUpdate RecType) fun rec = rec // u_field1 fn . a_field2 val Aside: Why doesn't TH allow infix? Haskell doesn't allow operators that start with ':' so it would seem natural for TH to use those for infix. Then we could have e.g. (:=) and (:\=) and end up with a syntax that looks like this: rec // field :\= fn . field2 := val And that is BETTER than the Haskell syntax that I originally proposed. Are TH infix operators on the agenda? -Alex- ______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com
Keean. S. Alexander Jacobson wrote:
I don't know what it takes to get this sort of change into circulation, but I assume it starts with a clear and specific description of the problem, exploration of alternatives, and a specific proposed solution. So here goes:
---------- Proposal: Allow "\=" for field update in record update syntax
Specifically, intepret
rec {field\=fn}
as
rec {field = fn (field rec)}
Jusitification:
It is extremely common when updating records to be updating a field value based on its prior value rather than simply assigning a new value from nothing. Unforunately the syntax supports the later clearly but makes the former awkward to read, understand, and debug:
myFun db myType1 = db { tMyType1 = insert myType1 $ tMyType1 db nextId = 1 + (nextId db) }
One solution to this problem that does not involve changing syntax is to generate field update functions for every field of a type e.g.:
update_tMyType1 f s = s {tMyType1= f $ myType1 s} update_nextId f s = s {nextId = f $ nextId s}
Once you have those functions, myFun looks much better:
myFun db myType = update_nextId (+1) $ update_tMyType1 f db where f= insert myType1
But, generating the update_ functions is not programming; its just manual syntax generation. And, even if you have template haskell to do it,
* it is annoying to have to find/write the TH code to implement * update_ is incredibly verbose * shortening it risks proliferation of psuedo-syntax: u_tMyType, u__tMyType, or utMyType? * it adds clutter to have to call the TH for each record type, and * it pollutes the name space/increases risk of name collision
Rather than force the user to jump through these hoops, why not make the syntax flow naturally from the existing record update syntax? It makes sense to use \= for this task as it is the functional generalization of C or Java's += or *= . Then myFun would have the elegant and clear syntax:
myFun db myType1 =db {tMyType1\=insert myType1, nextId\=(+1)}
-Alex-
______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Yes, your idea is much nicer... I was just suggesting TH as a way to implement custom syntax... It would be nice if come sort of standard existed for template-haskell so that other compilers/interpreters could adopt it. A portable template would be a cool thing. Keean. S. Alexander Jacobson wrote:
On Fri, 18 Feb 2005, Keean Schupke wrote:
Its a pity template haskell cannot define infix operators, but you could use TH like:
$update rec field fn
which would expand to:
rec { field = fn (rec field) }
That doesn't help you so much if you want to update more than one field at a time. I think the best case syntax in this spirit is to define:
x // f = f x infixl 6 // $(a field val) => \rec -> rec {field = val} $(u field fn) => \rec -> rec {field = fn (field rec)}
Which we would then be able to use like this:
fun rec = rec // $(u field1 fn) . $(a field2 val)
But, I'm not sure that is superior to having the user explicitly derive u_ and a_ functions on the rectype which looks like this:
$(deriveUpdate RecType)
fun rec = rec // u_field1 fn . a_field2 val
Aside: Why doesn't TH allow infix? Haskell doesn't allow operators that start with ':' so it would seem natural for TH to use those for infix. Then we could have e.g. (:=) and (:\=) and end up with a syntax that looks like this:
rec // field :\= fn . field2 := val
And that is BETTER than the Haskell syntax that I originally proposed. Are TH infix operators on the agenda?
-Alex-
______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com
Keean. S. Alexander Jacobson wrote:
I don't know what it takes to get this sort of change into circulation, but I assume it starts with a clear and specific description of the problem, exploration of alternatives, and a specific proposed solution. So here goes:
---------- Proposal: Allow "\=" for field update in record update syntax
Specifically, intepret
rec {field\=fn}
as
rec {field = fn (field rec)}
Jusitification:
It is extremely common when updating records to be updating a field value based on its prior value rather than simply assigning a new value from nothing. Unforunately the syntax supports the later clearly but makes the former awkward to read, understand, and debug:
myFun db myType1 = db { tMyType1 = insert myType1 $ tMyType1 db nextId = 1 + (nextId db) }
One solution to this problem that does not involve changing syntax is to generate field update functions for every field of a type e.g.:
update_tMyType1 f s = s {tMyType1= f $ myType1 s} update_nextId f s = s {nextId = f $ nextId s}
Once you have those functions, myFun looks much better:
myFun db myType = update_nextId (+1) $ update_tMyType1 f db where f= insert myType1
But, generating the update_ functions is not programming; its just manual syntax generation. And, even if you have template haskell to do it,
* it is annoying to have to find/write the TH code to implement * update_ is incredibly verbose * shortening it risks proliferation of psuedo-syntax: u_tMyType, u__tMyType, or utMyType? * it adds clutter to have to call the TH for each record type, and * it pollutes the name space/increases risk of name collision
Rather than force the user to jump through these hoops, why not make the syntax flow naturally from the existing record update syntax? It makes sense to use \= for this task as it is the functional generalization of C or Java's += or *= . Then myFun would have the elegant and clear syntax:
myFun db myType1 =db {tMyType1\=insert myType1, nextId\=(+1)}
-Alex-
______________________________________________________________ S. Alexander Jacobson tel:917-770-6565 http://alexjacobson.com _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On 19/02/2005, at 5:20 AM, Keean Schupke wrote:
Yes, your idea is much nicer... I was just suggesting TH as a way to implement custom syntax... It would be nice if come sort of standard existed for template-haskell so that other compilers/interpreters could adopt it. A portable template would be a cool thing.
The $(...) syntax that you need at the splice site greatly diminishes the potential for Template Haskell to be used as syntax, unfortunately. The interesting (or frustrating?) thing is that Template Haskell implements the majority of the hard work needed for a proper hygienic macro system, but unfortunately doesn't go quite the extra mile to enable you to actually define new syntax. I suspect all you'd need is a way to annotate the function you wanted to use as a macro, so that the compiler knows that the function should always be spliced in, rather than be explicitly spliced in at the call site. (People who know better, shoot me down if I'm wrong ...) I'd actually go so far blame the lack of macro support in TH on the Haskell community as a whole, since the general consensus I get from e.g. the mailing lists is that "syntactic sugar is bad". -- % Andre Pang : trust.in.love.to.save <http://www.algorithm.com.au/>
I think that the best solution is to define record labels as types, or rather type proxies, like for instance in the HList library. This fixes the most important deficiencies of Haskell98 records: - labels are now first class values - labels no longer need to be globally unique, but only unique per record - operations to get or set a field are normal (overloaded) functions (and can be given operator aliases, if desired) This library class defines the operations on a record: class RecordField r l t | r l -> t where getField :: l -> r -> t putField :: l -> t -> r -> r updateField :: (Field r l t) => l -> (t -> t) -> r -> r updateField lbl fun rec = putField lbl (fun $ getField lbl rec) rec The record declaration data R = R { field1 :: T1, field2 :: T2 } would be syntactic sugar for data R = R T1 T2 data Label_field1 field1 :: Label_field1 field1 = undefined instance RecordField R Label_field1 T1 where getField (Rec x _) _ = x putField (Rec _ y) _ v = Rec v x -- analogous definitions for field2 left out Note that the compiler would leave out the definition of Label_field1 and field1 if these are already in scope. Alexanders example
fun rec = rec // $(u field1 fn) . $(a field2 val)
resp.
fun rec = rec // u_field1 fn . a_field2 val
could now be written thus fun = updateField field1 fn . putField field2 val without any need for additional syntax or infix operator splices. I wonder if something similar could be done with TH. The labels would need to have a different name (e.g. l_field1, l_field2), so they don't collide with their Haskell98 definitions, but otherwise everything should be as above. I am thinking of something like $(generateLabels R) (I am not very familiar with TH, so this could be wrong syntax or otherwise impossible to do.) Ben
Two clarifications: On Saturday 19 February 2005 22:33, Benjamin Franksen wrote:
instance RecordField R Label_field1 T1 where getField (Rec x _) _ = x putField (Rec _ y) _ v = Rec v x
s/Rec/R/
I wonder if something similar could be done with TH. The labels would need to have a different name (e.g. l_field1, l_field2), so they don't collide with their Haskell98 definitions, but otherwise everything should be as above.
What I mean is: keep the record syntax and everything as it is now, but additionally use TH in order derive the corresponding first class labels as indicated above. The question is if a function 'generateLabels' can be defined with TH. I remember darkly that TH is (or once was) restricted to Haskell98, and so cannot be used to generate multi parameter class instances. Ben
TH has supported multi-parameter classes for a while... new in 6.4 is support for fundeps. Keean. Benjamin Franksen wrote:
Two clarifications:
On Saturday 19 February 2005 22:33, Benjamin Franksen wrote:
instance RecordField R Label_field1 T1 where getField (Rec x _) _ = x putField (Rec _ y) _ v = Rec v x
s/Rec/R/
I wonder if something similar could be done with TH. The labels would need to have a different name (e.g. l_field1, l_field2), so they don't collide with their Haskell98 definitions, but otherwise everything should be as above.
What I mean is: keep the record syntax and everything as it is now, but additionally use TH in order derive the corresponding first class labels as indicated above. The question is if a function 'generateLabels' can be defined with TH. I remember darkly that TH is (or once was) restricted to Haskell98, and so cannot be used to generate multi parameter class instances.
Ben _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Sunday 20 February 2005 14:19, Keean Schupke wrote:
TH has supported multi-parameter classes for a while... new in 6.4 is support for fundeps.
Yes, but unfortunately TH cannot create instances for them which is usually the boilerplate you want to avoid. From Language.Haskell.TH: data Dec = ... ClassD Cxt Name [Name] [FunDep] [Dec] InstanceD Cxt Type [Dec] ... Only one 'Type' can be given for an instance. Ben
On Fri, 4 Mar 2005 15:26:52 +0100, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Sunday 20 February 2005 14:19, Keean Schupke wrote:
TH has supported multi-parameter classes for a while... new in 6.4 is support for fundeps.
Yes, but unfortunately TH cannot create instances for them which is usually the boilerplate you want to avoid. From Language.Haskell.TH:
data Dec = ... ClassD Cxt Name [Name] [FunDep] [Dec] InstanceD Cxt Type [Dec] ...
Only one 'Type' can be given for an instance.
And there _should_ only be one type. Checkout 'appT'. Some real documentation for TH would clear misconceptions such as this. -- Friendly, Lemmih
Lemmih wrote:
On Fri, 4 Mar 2005 15:26:52 +0100, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Sunday 20 February 2005 14:19, Keean Schupke wrote:
TH has supported multi-parameter classes for a while... new in 6.4 is support for fundeps.
Yes, but unfortunately TH cannot create instances for them which is usually the boilerplate you want to avoid. From Language.Haskell.TH:
data Dec = ... ClassD Cxt Name [Name] [FunDep] [Dec] InstanceD Cxt Type [Dec] ...
Only one 'Type' can be given for an instance.
And there _should_ only be one type. Checkout 'appT'. Some real documentation for TH would clear misconceptions such as this.
Is that really how this is done? That doesn't seem like it can be right: instance X (a b) -- single parameter class where 'a' has an arrow kind is very different from: instance X a b -- multiple parameter class I would expect a type constructed with 'appT' to correspond to the first declaration, and not to the second.
robert dockins wrote:
Is that really how this is done? That doesn't seem like it can be right:
instance X (a b) -- single parameter class where 'a' has an arrow kind
is very different from:
instance X a b -- multiple parameter class
I would expect a type constructed with 'appT' to correspond to the first declaration, and not to the second.
Yup, thats how it is done, I have some complex working TH that generates multi parameter classes with fundeps instances etc... and I can say for definite it all works fine: For the above examples appT X (appT a b) -- X is applied once (to a applied to b) appT (appT X a) b -- X is applied twice first to a then to b Keean.
On Friday 04 March 2005 16:32, Keean Schupke wrote:
robert dockins wrote:
Is that really how this is done? That doesn't seem like it can be right:
instance X (a b) -- single parameter class where 'a' has an arrow kind
is very different from:
instance X a b -- multiple parameter class
I would expect a type constructed with 'appT' to correspond to the first declaration, and not to the second.
Yup, thats how it is done, I have some complex working TH that generates multi parameter classes with fundeps instances etc... and I can say for definite it all works fine:
For the above examples
appT X (appT a b) -- X is applied once (to a applied to b)
appT (appT X a) b -- X is applied twice first to a then to b
But this has nothing to do with the instance question. I agree with Robert that AppT a b definitely sounds like type constructor application. How can this help with multi parameter class instances? Consider: class Bogus a b instance Bogus Int Char How do you express the /instance/ in TH? Using AppT? AppT would make sense for instance Show a => Show [a] where ... where one would express the '[a]' in TH as AppT ListT (VarT mkName "a") Ben
Benjamin Franksen wrote:
On Friday 04 March 2005 16:32, Keean Schupke wrote:
robert dockins wrote:
Is that really how this is done? That doesn't seem like it can be right:
instance X (a b) -- single parameter class where 'a' has an arrow kind
is very different from:
instance X a b -- multiple parameter class
I would expect a type constructed with 'appT' to correspond to the first declaration, and not to the second.
Yup, thats how it is done, I have some complex working TH that generates multi parameter classes with fundeps instances etc... and I can say for definite it all works fine:
For the above examples
appT X (appT a b) -- X is applied once (to a applied to b)
appT (appT X a) b -- X is applied twice first to a then to b
But this has nothing to do with the instance question. I agree with Robert that
AppT a b
definitely sounds like type constructor application. How can this help with multi parameter class instances? Consider:
class Bogus a b instance Bogus Int Char
How do you express the /instance/ in TH? Using AppT? AppT would make sense for
instance Show a => Show [a] where ...
where one would express the '[a]' in TH as
AppT LiftT (VarT mkName "a")
That would be: (using 6.4 syntax) AppT (AppT (ConT (mkName "Bogus")) (ConT ''Int)) (ConT ''Char) If the instance is using type variables: instance Bogus Int a you get: AppT (AppT (ConT (mkName "Bogus")) (ConT ''Int)) (VarT (mkName "a")) Keean.
On Friday 04 March 2005 23:44, Keean Schupke wrote:
Benjamin Franksen wrote:
Consider:
class Bogus a b instance Bogus Int Char
How do you express the /instance/ in TH? Using AppT?
That would be:
(using 6.4 syntax)
AppT (AppT (ConT (mkName "Bogus")) (ConT ''Int)) (ConT ''Char)
If the instance is using type variables:
instance Bogus Int a
you get:
AppT (AppT (ConT (mkName "Bogus")) (ConT ''Int)) (VarT (mkName "a"))
OK, I can see now that this makes sense syntactically. Still, it is strange that the class name is handled as if it were a type constructor. Anyway, thanks. Ben
On Sat, 2005-03-05 at 00:25 +0100, Benjamin Franksen wrote:
OK, I can see now that this makes sense syntactically. Still, it is strange that the class name is handled as if it were a type constructor.
Yes. It makes a weird sort of sense if you pretend type classes are actually type constructors and instance declarations are inhabited by types in a new "type class kind". Say we use the symbol 'C' for the kind of type class types. Then the usual single parameter type class constructors would have kind (* -> C) and multiparameter classes just have more arrows, al la (* -> * -> C) for 2 parameters. I can't decide if I like this idea or not. Does it have any validity in the associated type or category theories?
Well it does have some validity, but I am not sure its from category theory... A type is a set of values (constructors): data Type = Constr1 | Constr2 | Constr3 likewise a class is a set of types (IE we lift one level) class Class instance Class Type1 instance Class Type2 instance Class Type3 So types are to classes as values are to types... this makes classes roughly equivalent to type-families. We can extend this indefinitely as long as we observe the russell-paradox (that is a set can only refer to values at a lower level...). So we could have a set of classes (which I am not aware of a name for...) Of course this makes the naming etc. used in Haskell look very ad-hoc, wouldn't it be nice if you could freely mix constructs from different levels, so for example you could have a function that takes a class and a value and returns a type... Keean. Robert Dockins wrote:
On Sat, 2005-03-05 at 00:25 +0100, Benjamin Franksen wrote:
OK, I can see now that this makes sense syntactically. Still, it is strange that the class name is handled as if it were a type constructor.
Yes. It makes a weird sort of sense if you pretend type classes are actually type constructors and instance declarations are inhabited by types in a new "type class kind". Say we use the symbol 'C' for the kind of type class types. Then the usual single parameter type class constructors would have kind (* -> C) and multiparameter classes just have more arrows, al la (* -> * -> C) for 2 parameters.
I can't decide if I like this idea or not. Does it have any validity in the associated type or category theories? _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Benjamin Franksen wrote:
This library class defines the operations on a record:
class RecordField r l t | r l -> t where getField :: l -> r -> t putField :: l -> t -> r -> r
I have once written a short note about how Haskell'98 records could be made more useful using a conservative extensions. The suggested implementation method corresponds quite closely to what you sketch here. Here is the url: <http://www.cs.uu.nl/~daan/download/papers/records.pdf> It should be interesting to read about the different tradeoffs of extending the current record system, but keep in mind that this is a just a quick writeup of ideas (and written two years ago!) -- Daan.
On Sunday 20 February 2005 10:16, Daan Leijen wrote:
Benjamin Franksen wrote:
This library class defines the operations on a record:
class RecordField r l t | r l -> t where getField :: l -> r -> t putField :: l -> t -> r -> r
I have once written a short note about how Haskell'98 records could be made more useful using a conservative extensions. The suggested implementation method corresponds quite closely to what you sketch here. Here is the url:
<http://www.cs.uu.nl/~daan/download/papers/records.pdf>
It should be interesting to read about the different tradeoffs of extending the current record system, but keep in mind that this is a just a quick writeup of ideas (and written two years ago!)
Yes, quite interesting, indeed. "My" sketch (I don't claim any originality) differs from yours mostly in that mine has one additional argument, namely the label type, which results in labels becoming first class values. I really like first class record labels! You mentioned that higher-ranked types are not allowed in instance declarations and that this limits the usefulness of your translation. This is unfortunate and applies to my translation too. From what I read elsewhere, I guess the standard workaround is to wrap such types in a newtype. The problem is that this newtype wrapping and unwrapping cannot be made transparent (at least I don't see a way to do this). Keean, how do you solve this problem in your TH code? Ben
Benjamin Franksen wrote:
On Sunday 20 February 2005 10:16, Daan Leijen wrote:
Benjamin Franksen wrote:
This library class defines the operations on a record:
class RecordField r l t | r l -> t where getField :: l -> r -> t putField :: l -> t -> r -> r
I have once written a short note about how Haskell'98 records could be made more useful using a conservative extensions. The suggested implementation method corresponds quite closely to what you sketch here. Here is the url:
<http://www.cs.uu.nl/~daan/download/papers/records.pdf>
It should be interesting to read about the different tradeoffs of extending the current record system, but keep in mind that this is a just a quick writeup of ideas (and written two years ago!)
Yes, quite interesting, indeed.
"My" sketch (I don't claim any originality) differs from yours mostly in that mine has one additional argument, namely the label type, which results in labels becoming first class values. I really like first class record labels!
You mentioned that higher-ranked types are not allowed in instance declarations and that this limits the usefulness of your translation. This is unfortunate and applies to my translation too. From what I read elsewhere, I guess the standard workaround is to wrap such types in a newtype. The problem is that this newtype wrapping and unwrapping cannot be made transparent (at least I don't see a way to do this).
Keean, how do you solve this problem in your TH code?
Ben _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Can you think of an example where a higher ranked label would be useful? Lookups are normally done with values. Firstly a type variable must have a kind. So for a type variable of kind '*' the only possible family of higher ranked types are: (forall a . Contraint a => a) We certainly could use a newtype to represent this, but what would it mean in the context of a label? Here's an example of a higher ranked type used as a non-label which works fine: -------------------------------------------------------------------------------------------- --{-# OPTIONS -fglasgow-exts #-} module Main where class Test a b | a -> b where test :: a -> b -> Bool newtype I = I (forall a . Integral a => a) newtype S = S (forall a . Show a => a) instance Test Int I where test _ _ = True instance Test String S where test _ _ = False main = do putStrLn $ show $ test (1::Int) (I undefined) putStrLn $ show $ test ("a"::String) (S undefined) -------------------------------------------------------------------------------- Which shows that even though you cannot use higher ranked types as labels, you can use them in other fields... Effectively they cannot be on the LHS of a functional dependancy (for obvious reasons if you think about it). Keean.
Keean Schupke wrote: Hmm... actually they can be used on the LHS... {-# OPTIONS -fglasgow-exts #-} module Main where class Test a b | a -> b where test :: a -> b newtype I = I (forall a . Integral a => a) newtype S = S (forall a . Show a => a) instance Test I Int where test _ = 7 instance Test S String where test _ = "TEST" main = do putStrLn $ show $ test (I undefined) putStrLn $ show $ test (S undefined) Not sure how you would use them, but you can wrap a higher ranked type in a newtype and use it as a label. It is actually the head of the type (I or S) that selects the instance, the rest is not useful. This raises the question of how you get your higher ranked type into the right wrapper... the answer is (I think) you can't... it must already be in the wrapper to differentiate it. This reduces to the same situation as before and you might as well have I and S as first class labels and drop the higher-ranked type. Keean.
On Thursday 24 February 2005 11:56, Keean Schupke wrote:
Benjamin Franksen wrote:
You mentioned that higher-ranked types are not allowed in instance declarations and that this limits the usefulness of your translation. This is unfortunate and applies to my translation too. From what I read elsewhere, I guess the standard workaround is to wrap such types in a newtype. The problem is that this newtype wrapping and unwrapping cannot be made transparent (at least I don't see a way to do this).
Keean, how do you solve this problem in your TH code?
Can you think of an example where a higher ranked label would be useful? Lookups are normally done with values.
Dear Keean, you should read more carefully what people write. Nowhere have I stated that I want higher-ranked *labels*. In fact, in my translation labels always have the value bottom. My concern is with higher-ranked record fields. Stupid example: data R = R { f :: (forall a. a -> a) } My translation doesn't work in this case, because the compiler doesn't accept instance RecordField R Label_f (forall a. a->a) where ...
Here's an example of a higher ranked type used as a non-label which works fine:
--------------------------------------------------------------------- ----------------------- --{-# OPTIONS -fglasgow-exts #-}
module Main where
class Test a b | a -> b where test :: a -> b -> Bool
newtype I = I (forall a . Integral a => a) newtype S = S (forall a . Show a => a)
instance Test Int I where test _ _ = True
instance Test String S where test _ _ = False
main = do putStrLn $ show $ test (1::Int) (I undefined) putStrLn $ show $ test ("a"::String) (S undefined)
--------------------------------------------------------------------- -----------
Which shows that even though you cannot use higher ranked types as labels, you can use them in other fields... Effectively they cannot be on the LHS of a functional dependancy (for obvious reasons if you think about it).
Yes, you can wrap higher-ranked types into a newtype and then you can define instances for them. Again, that is what I already wrote in my previous message. With the above stupid example: newtype Wrap_f = Wrap_f (forall a. a->a) unWrap_f (Wrap_f x) = x However, the result of getField Label_f now has type Wrap_f and not (forall a. a->a). To really get the field, I have to unwrap the newtype constructor manually: get_f :: R -> (forall a. a->a) get_f = unWrap_f . getField Label_f This means that a translation as proposed by Daan (i.e. without first-class labels) is feasible even with higher-ranked field types, but not my version. Ben
Benjamin Franksen wrote: I haven't read Daan's paper yet, but I think his translation is similar to the TIR (type indexed row) part of the HList library... Keean.
Dear Keean,
you should read more carefully what people write. Nowhere have I stated that I want higher-ranked *labels*. In fact, in my translation labels always have the value bottom.
My concern is with higher-ranked record fields. Stupid example:
data R = R { f :: (forall a. a -> a) }
My translation doesn't work in this case, because the compiler doesn't accept
instance RecordField R Label_f (forall a. a->a) where ...
Here's an example of a higher ranked type used as a non-label which works fine:
--------------------------------------------------------------------- ----------------------- --{-# OPTIONS -fglasgow-exts #-}
module Main where
class Test a b | a -> b where test :: a -> b -> Bool
newtype I = I (forall a . Integral a => a) newtype S = S (forall a . Show a => a)
instance Test Int I where test _ _ = True
instance Test String S where test _ _ = False
main = do putStrLn $ show $ test (1::Int) (I undefined) putStrLn $ show $ test ("a"::String) (S undefined)
--------------------------------------------------------------------- -----------
Which shows that even though you cannot use higher ranked types as labels, you can use them in other fields... Effectively they cannot be on the LHS of a functional dependancy (for obvious reasons if you think about it).
Yes, you can wrap higher-ranked types into a newtype and then you can define instances for them.
Again, that is what I already wrote in my previous message. With the above stupid example:
newtype Wrap_f = Wrap_f (forall a. a->a)
unWrap_f (Wrap_f x) = x
However, the result of
getField Label_f
now has type Wrap_f and not (forall a. a->a). To really get the field, I have to unwrap the newtype constructor manually:
get_f :: R -> (forall a. a->a) get_f = unWrap_f . getField Label_f
This means that a translation as proposed by Daan (i.e. without first-class labels) is feasible even with higher-ranked field types, but not my version.
Ben _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Having looked at the translation on page 10 of Daan's paper, I can see no advantage in this encoding, nor does it look like it supports higher ranked types in any way... (Infact it has the disadvantage of requiring a class per record, whereas the records in the HList paper require only a class per function). class Name r a | r -> a where get_name :: r -> a set_name :: r -> a -> r Perhaps you can explain how this does support higher ranked fields? Keean.
On Thursday 24 February 2005 15:01, Keean Schupke wrote:
Having looked at the translation on page 10 of Daan's paper, I can see no advantage in this encoding, nor does it look like it supports higher ranked types in any way... (Infact it has the disadvantage of requiring a class per record, whereas the records in the HList paper require only a class per function).
class Name r a | r -> a where get_name :: r -> a set_name :: r -> a -> r
Perhaps you can explain how this does support higher ranked fields?
Sorry, I jumped to conclusions a bit too fast. I thought one could get rid of the newtype unwrapper if one "applied it away". But this is nonsense because one still has the class constraint involving the newtype. It just doesn't work. I still wonder if your TH generated code can handle higher ranked field types; i.e. can I write $(ttypelift [| data Record = Record { field1 :: Int, field2 :: (forall a. a-> a) } |] ) or does ghc give me an error? Ben
Benjamin Franksen wrote:
Sorry, I jumped to conclusions a bit too fast. I thought one could get rid of the newtype unwrapper if one "applied it away". But this is nonsense because one still has the class constraint involving the newtype. It just doesn't work.
I still wonder if your TH generated code can handle higher ranked field types; i.e. can I write
$(ttypelift [| data Record = Record { field1 :: Int, field2 :: (forall a. a-> a) } |] )
or does ghc give me an error?
Well at the moment this would give an error, but remember the list is heterogeneous, so you can just not give the list a type, and simply append the specific function... admitedly this is not as type-safe. hUpdateAtLabel field2 someFunction myRecord Of course if the type of the function is unknown until runtime, we have to use existential quantification. It might be possible to automatically wrap such types, but my feeling at the moment is that it is best left to the programmer. Keean.
Ben _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Thursday 24 February 2005 19:51, Keean Schupke wrote:
Benjamin Franksen wrote:
I still wonder if your TH generated code can handle higher ranked field types; i.e. can I write
$(ttypelift [| data Record = Record { field1 :: Int, field2 :: (forall a. a-> a) } |] )
or does ghc give me an error?
Well at the moment this would give an error, but remember the list is heterogeneous, so you can just not give the list a type, and simply append the specific function... admitedly this is not as type-safe.
hUpdateAtLabel field2 someFunction myRecord
That is an advantage of hLists as compared to normal records. A disadvantage is that each field access needs to traverse the list. I wonder if this isn't rather less efficient than the random access provided by normal records.
Of course if the type of the function is unknown until runtime, we have to use existential quantification.
It might be possible to automatically wrap such types, but my feeling at the moment is that it is best left to the programmer.
Automatic wrapping is easy. What is not so easy and I think not even possible, is automatic _un_wrapping, so that the wrapping isn't exposed at all to the programmer. I am not a type system guru, so can anyone enlighten me on why higher ranked types are not allowed in instances? Is there a principle problem, or is it just difficult to implement? Does this have to do with this "higher order unification" that Haskell is alleged not to have? Ben
Benjamin Franksen wrote:
Well at the moment this would give an error, but remember the list is heterogeneous, so you can just not give the list a type, and simply append the specific function... admitedly this is not as type-safe.
hUpdateAtLabel field2 someFunction myRecord
That is an advantage of hLists as compared to normal records.
A disadvantage is that each field access needs to traverse the list. I wonder if this isn't rather less efficient than the random access provided by normal records.
Well, not quite true, because the type of the label is used to index the value, the selection happens at compile time. So at run time there is no instance selection left... it is simply the value. At least in theory! whether the particular compiler/interpreter does this is implementation dependant. This is why we decided that the simpler to implement list was better than a more complex tree structure.
Automatic wrapping is easy. What is not so easy and I think not even possible, is automatic _un_wrapping, so that the wrapping isn't exposed at all to the programmer.
One way to do this is to replace the type with a pair: data Wrap a = Wrap {unwrapA :: a} wrap a = (unwrapA,Wrap a) so to unwrap do: unwrap a = (fst a) (snd a) But without working through an example it is difficault to see how this would work with the template-haskell.
I am not a type system guru, so can anyone enlighten me on why higher ranked types are not allowed in instances? Is there a principle problem, or is it just difficult to implement? Does this have to do with this "higher order unification" that Haskell is alleged not to have?
No idea, but it sounds like a good idea. I suspect this has to do with the definition of type-equality/unification used to match instances. Keean.
On Thursday 24 February 2005 23:27, Keean Schupke wrote:
Benjamin Franksen wrote:
Well at the moment this would give an error, but remember the list is heterogeneous, so you can just not give the list a type, and simply append the specific function... admitedly this is not as type-safe.
hUpdateAtLabel field2 someFunction myRecord
That is an advantage of hLists as compared to normal records.
A disadvantage is that each field access needs to traverse the list. I wonder if this isn't rather less efficient than the random access provided by normal records.
Well, not quite true, because the type of the label is used to index the value, the selection happens at compile time. So at run time there is no instance selection left... it is simply the value. At least in theory! whether the particular compiler/interpreter does this is implementation dependant. This is why we decided that the simpler to implement list was better than a more complex tree structure.
Hmm. I haven't seen it from this perspective, yet! At first reading, I thought this is simply too good to be true. I mean, there is some sort of list structured thing representing the whole record, right? Then how can the function that selects an element *not* traverse through the list? After thinking for some time about this, my head begins to spin badly! I tend to believe now, that it could indeed be possible that the compiler performs the traversal at compile time, but the thought still gives me headaches. Ben
Benjamin Franksen wrote:
On Thursday 24 February 2005 23:27, Keean Schupke wrote:
Well, not quite true, because the type of the label is used to index the value, the selection happens at compile time. So at run time there is no instance selection left... it is simply the value. At least in theory!
Hmm. I haven't seen it from this perspective, yet! At first reading, I thought this is simply too good to be true. I mean, there is some sort of list structured thing representing the whole record, right? Then how can the function that selects an element *not* traverse through the list?
It does. An HList of Int,Bool,Char is isomorphic to the type (Int,(Bool,(Char,()))), and selecting the Bool element will ultimately compile to code like this: case list of (_,(x,_)) -> ... It doesn't need to search for the right element at runtime, and it doesn't need to check for end-of-list at runtime, but it does need to deconstruct O(n) pairs at runtime. A statically balanced tree structure would reduce that the O(log n), but I doubt it would improve performance for typical record sizes. Of course, inlining may well lead to something like case (a,(b,(c,()))) of (_,(x,_)) -> ... which can be optimized away. -- Ben
Ben Rudiak-Gould wrote:
It does. An HList of Int,Bool,Char is isomorphic to the type (Int,(Bool,(Char,()))), and selecting the Bool element will ultimately compile to code like this:
case list of (_,(x,_)) -> ...
It doesn't need to search for the right element at runtime, and it doesn't need to check for end-of-list at runtime, but it does need to deconstruct O(n) pairs at runtime. A statically balanced tree structure would reduce that the O(log n), but I doubt it would improve performance for typical record sizes.
Of course, inlining may well lead to something like
case (a,(b,(c,()))) of (_,(x,_)) -> ...
which can be optimized away.
You are right, but opening a tuple is quite cheap I would hope. I have coded arbitrary binary products, which could be used to write a tree, but the classes would be more complex. The interface would be the same though, so we could swap the implementation in future to a more efficient one. Infact there is a tradeoff. Records with faster read times (ie offset tables) have slower write times as the table needs to be copied and expanded. The list based records have the fastest extention time (for the tree we must search for the next free node, with the list we just apply one tuple). Keean.
On Wednesday 02 March 2005 19:51, Keean Schupke wrote:
Infact there is a tradeoff. Records with faster read times (ie offset tables) have slower write times as the table needs to be copied and expanded.
Which is of course the reason why extension for records is not a very common language feature. BTW, records in C don't even have offset tables, instead the offsets get directly compiled into the machine code. Ben
On Thursday 24 February 2005 23:27, Keean Schupke wrote:
Benjamin Franksen wrote:
Automatic wrapping is easy. What is not so easy and I think not even possible, is automatic _un_wrapping, so that the wrapping isn't exposed at all to the programmer.
One way to do this is to replace the type with a pair:
data Wrap a = Wrap {unwrapA :: a}
wrap a = (unwrapA,Wrap a)
so to unwrap do:
unwrap a = (fst a) (snd a)
This is extremely cool. The type of unwrap is indeed general enough. Unfortunately, it doesn't help, because the result type of wrap Wrap (forall a. a -> a) still isn't accepted in an instance declaration. Neither is the pair (unwrap, Wrap (forall a. a -> a)) Or maybe I have not quite understood what you proposed to do with these definitions. Ben
Benjamin Franksen wrote:
This is extremely cool. The type of unwrap is indeed general enough. Unfortunately, it doesn't help, because the result type of wrap
Wrap (forall a. a -> a)
still isn't accepted in an instance declaration. Neither is the pair
(unwrap, Wrap (forall a. a -> a))
Or maybe I have not quite understood what you proposed to do with these definitions.
No, you're right, cool but useless! I think I lost sight of the problem... we want to wrap higher order types like: (forall a . a -> a) "Wrap a" makes the higher order type a parameter - which is still not "wrapped"... Template-haskell would need to generate a new unique type: newtype ForallAzdAzaA = ForallAzdAzaA { forallAzdAzaA : forall a . a -> a } Then all occurances of the plain type (forall a . a -> a) would need replacing with ForallAzdAzaA. The tricky bit would be determining where in the code to put the wrap and unwrap bits. But the point is moot anyway. Without first class labels you still need to write the newtype declaration... Keean.
Is the behavior of evaluating z unspecified? z = f (0, z) f x = case x of (1,1) -> z _ -> 0 Hugs and GHC agree that z evaluates to 0. However, if the first line is changed to z = f (z,0) then both implementations loop. In other words, the behavior depends on order of evaluation, which AFAIK is not specified.
Scott Turner <p.turner@computer.org> writes:
Is the behavior of evaluating z unspecified? z = f (0, z) f x = case x of (1,1) -> z _ -> 0 Hugs and GHC agree that z evaluates to 0. However, if the first line is changed to z = f (z,0) then both implementations loop. In other words, the behavior depends on order of evaluation, which AFAIK is not specified.
Patterns are matched left-to-right, which fully explains the behaviour you see. Haskell Report section 3.17.2, Informal Semantics of Pattern Matching Regards, Malcolm
Evaluation is driven by pattern matching, and the order of pattern matching is specified. The order is left to right, outside to inside. See section 3.17 of the Haskell Report: http://www.haskell.org/onlinereport/exps.html#sect3.17 Arthur On 3-mrt-05, at 15:39, Scott Turner wrote:
Is the behavior of evaluating z unspecified? z = f (0, z) f x = case x of (1,1) -> z _ -> 0 Hugs and GHC agree that z evaluates to 0. However, if the first line is changed to z = f (z,0) then both implementations loop. In other words, the behavior depends on order of evaluation, which AFAIK is not specified. _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Yes, I have unreleased (yet) TH code for generating globally unique labels, and lifting records.... you can do: $(ttypelift [| data Record = Record { field1 :: Int, field2 :: String } |] ) and it lifts this to an HList style record with labels field1 field2 Labels are assigned unique type level values by converting the string of the label name to a numeric value which is encoded by type level natural numbers. Keean. Benjamin Franksen wrote:
I think that the best solution is to define record labels as types, or rather type proxies, like for instance in the HList library. This fixes the most important deficiencies of Haskell98 records:
- labels are now first class values - labels no longer need to be globally unique, but only unique per record - operations to get or set a field are normal (overloaded) functions (and can be given operator aliases, if desired)
This library class defines the operations on a record:
class RecordField r l t | r l -> t where getField :: l -> r -> t putField :: l -> t -> r -> r
updateField :: (Field r l t) => l -> (t -> t) -> r -> r updateField lbl fun rec = putField lbl (fun $ getField lbl rec) rec
The record declaration
data R = R { field1 :: T1, field2 :: T2 }
would be syntactic sugar for
data R = R T1 T2
data Label_field1
field1 :: Label_field1 field1 = undefined
instance RecordField R Label_field1 T1 where getField (Rec x _) _ = x putField (Rec _ y) _ v = Rec v x
-- analogous definitions for field2 left out
Note that the compiler would leave out the definition of Label_field1 and field1 if these are already in scope.
Alexanders example
fun rec = rec // $(u field1 fn) . $(a field2 val)
resp.
fun rec = rec // u_field1 fn . a_field2 val
could now be written thus
fun = updateField field1 fn . putField field2 val
without any need for additional syntax or infix operator splices.
I wonder if something similar could be done with TH. The labels would need to have a different name (e.g. l_field1, l_field2), so they don't collide with their Haskell98 definitions, but otherwise everything should be as above. I am thinking of something like
$(generateLabels R)
(I am not very familiar with TH, so this could be wrong syntax or otherwise impossible to do.)
Ben _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
participants (13)
-
Andre Pang -
Arthur Baars -
Ben Rudiak-Gould -
Benjamin Franksen -
Daan Leijen -
Hal Daume III -
Keean Schupke -
Lemmih -
Malcolm Wallace -
robert dockins -
Robert Dockins -
S. Alexander Jacobson -
Scott Turner