Fri, 29 Dec 2000 00:37:45 -0800, John Meacham <john@foo.net> pisze:
I've read it and posted some comments in February 2000. There was no answer AFAIR. Here are they again, slightly edited and extended: I don't understand why to separate kinds of rows and record types, instead of having "a type which is known to be a record type", at least on the level visible for the programmer. So instead of type Point r = (r | x::Int, y::Int) type Colored r = (r | c::Color) type ColoredPoint r = Point (Colored r) p :: {ColoredPoint()} -- Point, Colored, ColoredPoint :: row -> row it would be type Point r = {r | x::Int, y::Int} type Colored r = {r | c::Color} type ColoredPoint r = Point (Colored r) p :: ColoredPoint() -- Point, Colored, ColoredPoint :: recordType -> recordType -- where recordType is something like a subkind of *. -------- It is bad to require the programmers to think in advance that a type is going to be subtyped, and write elaborated type Point r = (r | x::Int, y::Int) ... {Point()} ... instead of simpler type Point = {x::Int, y::Int} ... Point ... which is not extensible. -------- I got used to () as a unit type. It would be a pity to lose it. -------- A minor problem. If tuples are records, field names should be such that alphabetic order gives the sequential order of fields, or have a special rule of field ordering for names of tuple fields... -------- In general I don't quite like the fact that records are getting more anonymous. Magical instances of basic classes? How inelegant. If I want the record type to have an identity, it will have to be wrapped in a newtype, so I must think at the beginning if I will ever want to write specialized insances for it and then all the code will depend on the decision. Currently a datatype with named fields has both an identity and convenient syntax of field access. (And why newtype is not mentioned in section 5.1?) I like name equivalence where it increases type safety. Extensible records promote structural equivalence. Unfortunately the proposal seems to increase the number of irregularities and inelegant rules... If expr.Constructor for a multiparameter constructor yields a tuple, then for an unary constructor it should give a 1-tuple, no? I know it would be extremely inconvenient, especially as newtypes are more used, so I don't propose it, but it is getting less regular. What about nullary constructors - empty tuple? :-) I don't say that I don't like the proposal at all, or that I never wanted to have several types with the same field names. But it is not clean for me, it's a compromise between usability and elegance, and from the elegance point of view I like current records more. Maybe it would be helpful to show how to translate a program with extensible records to a program without them (I guess it's possible in a quite natural way, but requires global transformation of the whole program). -------- Extensible records makes a syntactic difference between field access and function call. So if one wants to export a type abstractly or simply to provide functions operating on it without fixing the fact that they are physically fields, he ends in writing functions like size:: MyRecord -> Int size x = x.MyRecord.size which are unnecessary now, even if size is simply a field. It reminds me of C++ which wants us to provide methods for accessing data fields (for allowing them to be later redefined as methods, and for allowing everything to be uniformly used with "()" after the feature name). Ugh. -------- My new record scheme proposal does not provide such lightweight extensibility, but fields can be added and deleted in a controlled way if the right types and instances are made. The distinction between having a field and having a supertype is blurred. Similarly between having itself a field called foo and having a supertype which has a field called foo. Similarly between creating a record by adding fields to another record and creating a record by putting another record as one of fields. Similarly between casting to a supertype by removing some fields and extracting the supertype represented by a field. An advantage is that the interface of records does not constrain the representation in any way. It's up to how instances are defined, with the provision of natural definitions for records implemented physically as product types. For example supplying a color for a colorless point and the reverse operation can be written thus: addColor :: (Record cp, cp.point :: p, cp.color :: Color) => p -> Color -> cp addColor p c = record point = p; color = c removeColor :: (cp.point :: p) => cp -> p removeColor = (.point) When the following definitions are present: data Point = record x, y :: Int data ColoredPoint = record point :: Point point (x, y) color :: Color these functions can be used as of types addColor :: Point -> Color -> ColoredPoint removeColor :: ColoredPoint -> Point A colored point can be constructed either as in addColor, from a point and a color, or thus: record x = ... y = ... color = ... If ColoredPoint were defined directly as data ColoredPoint = record x, y :: Int color :: Color the previous interface could be *retroactively* reconstructed thus: instance (ColoredPoint).point :: Point where cp.point = record x = cp.x; y = cp.y cp.record {point = p} = cp.record x = p.x; y = p.y Multiple inheritance can be modelled as well. And field renaming during inheritance. -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK