
On Wed, Jan 21, 2015 at 4:36 AM, Simon Marlow
On 20/01/2015 23:07, Edward Kmett wrote:
It is a long trek from "this is plausible" to "hey, let's bet the
future of records and bunch of syntax in the language on this".
Absolutely. On the other hand, this is the first proposal I've seen that really hits (for me) a point in the design space that has an acceptable power to weight ratio. Yes there are some corners cut, and it remains to be seen whether, after we've decided which corners we want to uncut, the design retains the same P2W ratio.
A couple of answers to specific points:
Re #1
The main term and type level bits of syntax that could be coopted that aren't already in use are @ and (~ at the term level) and things like banana brackets (| ... |), while that already has some other, unrelated, connotations for folks, something related like {| ... |}. We use such bananas for our row types in Ermine to good effect.
The latter {| ... |} might serve as a solid syntax suggestion for the anonymous row type syntax.
Why not just use { ... } ?
Mostly because it would conflict with the existing record syntax when used as a member of a data type. Using { ... } would break all existing code, while {| ... |} could peacefully co-exist. data Foo = Foo { bar :: Bar } vs. data Foo = Foo {| bar :: Bar |} You could, I suppose manually distinguish them using ()'s data Foo = Foo ({bar :: Bar }) might be something folks could grow to accept. Another reason that comes to mind is that it causes a further divergence between the way terms and types behave/look, complicated stuff like Richard Eisenberg's work on giving us something closer to real dependent types. Re #2
That leaves the means for how to talk about a lens for a given field open. Under Adam's proposal that had evolved into making a really complicated instance that we could extract a lens from. This had the benefit over the current state of the `record` package that we could support full type changing lenses. Losing type-changing assignment would be a big step back from the previous proposal / the current state of development for folks who just use makeClassy or custom lens production rules with lens to get something similar, though.
But the thing we never found was a nice short syntax for talking about the lens you get from a given field (or possibly chain of fields); Gundry's solution was 90% library and almost no syntax. On the other hand Adam was shackled by having to let the accessor be used as a normal function as well as a lens. Nikita's records don't have that problem.
Having no syntax at all for extracting the lens from a field accessor, but rather to having it just be the lens, could directly address that concern. This raises some questions about scope, where do these names live? What happens when you have a module A that defines a record with a field, and a module B that does the same for a different record, and a module C that imports both, but, really, we had those before with Adam's proposal, so there is nothing new there.
Right. So either (a) A field name is a bare identifier that is bound to the lens, or (b) There is special syntax for the lens of a field name
If (a) there needs to be a declaration of the name in order that we can talk about scoping. That makes (b) a lot more attractive; and if you really find the syntax awkward then you can always bind a local variable to the lens, or export the names from your library.
Alternately (c) we could play games with ensuring the "name" is shared despite coming from different fields. As a half-baked idea, if we pretended all field accessors were names from some magic internal GHC.Record.Fields module, so that using data Foo = Foo {| bar :: Bar, baz :: Baz |} would add an `import GHC.Record.Fields (bar, baz)` to the module. These would all expand to the same Symbol-based representation, behind the scenes, so that if two record types were used that used the same names, they'd just work together, with no scoping issues. This has the benefit that users could write such import statements by hand to use fields themselves, no sigils get used up, and the resulting code is the cleanest it can be. -Edward