OverloadedRecordFields #6
Conversation
| OverloadedLabels extension | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| The ``IsLabel`` class defined in ``GHC.OverloadedLabels`` is changed |
Gabriel439
Sep 5, 2016
Minor suggestion: perhaps also include the old definition for IsLabel inline for ease of comparison in the proposal
Minor suggestion: perhaps also include the old definition for IsLabel inline for ease of comparison in the proposal
adamgundry
Sep 5, 2016
Author
Contributor
Done, thanks.
Done, thanks.
|
May I suggest making |
|
@int-index The idea to have a data structure containing two fields of the same name but from different modules seems like a use-case that needs motivating, as by itself just seems potential for confusing people. |
|
One library defines a function:
Another library defines a function:
There's no guarantee that both of those Using symbols as field names introduces spurious equalities (like above, Being poly-kinded would allow using both |
|
If I'm reading this right, code using the new syntax would everywhere use Also to be really nitpicky, if you must use the |
The overloaded part is that
The ship has already sailed on the |
"We already have the ugly thing, so we should double down on it" is not a great argument. |
Let me be polite and just state that not everybody shares your opinion on If you want a piece of advice, try to care less about syntax and more about semantics. The color of the bike shed will disappoint someone out there regardless of the choice. |
|
The whole point of overloaded record fields is just to be able to avoid having to add prefixes to record field names, so this is all bikeshedding already. |
This is not true, however, since |
|
@Hrothen Is there a way to do this without some sort of reserved prefix? How would the compiler know that some token like |
|
One could define
for all fields in the module where |
|
I don't know. I wouldn't even have commented if it weren't for the worry that I'll end up having to read code using this extension in the future. The compiler should already know if something is a function or a record field, but obviously if that were actually true Adam wouldn't have had to do all this work. |
|
|
|
Thanks everyone for your feedback so far!
I appreciate your concerns about the dangers of semantically-empty strings, but I would suggest the proper solution for that is for libraries not to expose interfaces that use Unfortunately I don't see how making
We did consider something like this previously, requiring no new syntax and interpreting occurrences of field names as overloaded fields automatically. The trouble is:
In contrast, the overloaded labels approach is simple: Given that we want a syntactic distinction, the question is what syntax to use. The |
I doubt you can stop library authors from doing so given the convenience
That another class would benefit from being poly-kinded as well, in case someone wants to use a closed kind for field types:
At this point If you doubt anyone would want that, someone is already doing exactly that, e.g. VinylRecords/Vinyl#80 |
|
I have a wrapper library for demonstration purposes based on the {-# LANGUAGE TemplateHaskell, OverloadedStrings, ScopedTypeVariables,
OverloadedLabels, TypeOperators, DataKinds, FlexibleContexts #-}
import Data.ByteString (ByteString)import Labels.Explore
main = do
count <-
explore $
fileSource "demo.csv" .|
fromCsvConduit .|
filterConduit
((row :: ("ORIGIN" := ByteString)) ->
get $("ORIGIN") row == "RKS") .>
countSink
print (count :: Int)(The library I whipped up making is like this: You can read it as a UNIX pipeline:
This outputs:
11MB total memory usage is not bad. That’s because of the constant However, like Iavor demonstrated; it’s easy to optimize. Writing a |
|
|
||
| Users may define their own instances of ``HasField``, subject to the | ||
| usual rules about overlapping and incoherent instances. This allows | ||
| "virtual" record fields to be defined for datatypes that do not |
mpickering
Sep 5, 2016
Contributor
But you couldn't use the virtual name in record update syntax for example?
But you couldn't use the virtual name in record update syntax for example?
PkmX
Sep 6, 2016
I suppose you can also have:
class SetField (l :: Symbol) s t b | s l b -> t, t l -> b where
setField :: s -> b -> t
so that record updates like
f r = r { #a = True }
would be translated to:
f :: SetField "a" s t Bool => s -> t
f r = setField @"a" r True
I suppose you can also have:
class SetField (l :: Symbol) s t b | s l b -> t, t l -> b where
setField :: s -> b -> tso that record updates like
f r = r { #a = True }would be translated to:
f :: SetField "a" s t Bool => s -> t
f r = setField @"a" r True
adamgundry
Sep 7, 2016
Author
Contributor
Right, this proposal doesn't cover updates, but something along the lines of SetField is a plausible next step. "Virtual" fields would have to define get and set behaviour separately.
We could introduce syntactic sugar like r { #a = True }, but it's not clear to me what the translation should be for multiple updates. They could be desugared to a series of single updates, but that's less expressive than traditional Haskell record update in the unambiguous case.
For example, this is well-typed only if you apply both updates at once:
data T a = MkT { x :: a, y :: a }
f :: T a -> T Bool
f t = t { x = True, y = True }
Right, this proposal doesn't cover updates, but something along the lines of SetField is a plausible next step. "Virtual" fields would have to define get and set behaviour separately.
We could introduce syntactic sugar like r { #a = True }, but it's not clear to me what the translation should be for multiple updates. They could be desugared to a series of single updates, but that's less expressive than traditional Haskell record update in the unambiguous case.
For example, this is well-typed only if you apply both updates at once:
data T a = MkT { x :: a, y :: a }
f :: T a -> T Bool
f t = t { x = True, y = True }
PkmX
Sep 9, 2016
•
I think you should just ask users to handle multiple updates explicitly:
data HList (xs :: [*]) where
HNil :: List '[]
HCons :: x -> HList xs -> HList (x ': xs)
class SetFields (ls :: [Symbol]) s t (bs :: [*]) | s ls bs -> t, t ls -> bs where
setFields :: s -> HList bs -> t
f t = t { x = True, y = False }
will desugar to:
f t :: SetFields '["a", "b"] (T a) (T bool) '[Bool, Bool] => T a -> T Bool
f t = setFields @'["a", "b"] t (True `HCons` (False `HCons` HNil))
For reference, this is very similar what my rawr library does (:<= is the record update operator):
type T a = R ( "x" := a, "y" := a )
f :: T a -> T Bool
f t = t :<= R ( #x := True, #y := True )
g :: T a -> T Bool
g t = t :<= R ( #x := True ) -- type error
I think you should just ask users to handle multiple updates explicitly:
data HList (xs :: [*]) where
HNil :: List '[]
HCons :: x -> HList xs -> HList (x ': xs)
class SetFields (ls :: [Symbol]) s t (bs :: [*]) | s ls bs -> t, t ls -> bs where
setFields :: s -> HList bs -> tf t = t { x = True, y = False }will desugar to:
f t :: SetFields '["a", "b"] (T a) (T bool) '[Bool, Bool] => T a -> T Bool
f t = setFields @'["a", "b"] t (True `HCons` (False `HCons` HNil))For reference, this is very similar what my rawr library does (:<= is the record update operator):
type T a = R ( "x" := a, "y" := a )
f :: T a -> T Bool
f t = t :<= R ( #x := True, #y := True )
g :: T a -> T Bool
g t = t :<= R ( #x := True ) -- type error
adamgundry
Sep 13, 2016
Author
Contributor
Thanks for pointing me to rawr, I wasn't aware of it before. I'm not sure how easy it will be to specify and implement the constraint solver behaviour for SetFields. Checking traditional record update is hard enough! Anyway, this is beyond the scope of the current proposal.
Thanks for pointing me to rawr, I wasn't aware of it before. I'm not sure how easy it will be to specify and implement the constraint solver behaviour for SetFields. Checking traditional record update is hard enough! Anyway, this is beyond the scope of the current proposal.
|
@int-index Library developers working independently are very likely to come up with the same field labels for unrelated purposes. @adamgundry suggesting those interfaces be hidden seems to rather defeat the purpose of having interfaces and/or require too much co-ordination between library writers. I see the overall effect as not exactly breaking existing code; but meaning that existing libraries with their current 'private' labels can never be upgraded to OverloadedRecordFields for fear that will introduce a clash with some other library's labels. |
|
We are losing a 'featurette' of H98 field labels: all fields with a given label must be the same type. (This is of limited usefulness, because it only applies within the fields of the data constructors within a given type.) For example my library wants all I really feel that rather than an evolution from H98 records we're getting an orthogonal records system. So people who want tight name/scope control over their labels will continue to use their favourite record-like system. (I see the promos above.) And we'll continue with a fragmented ecosystem. |
I'm not sure I understand this argument against a poly-kinded You say that “
Is there a slight contradiction here? I could imagine a library with a common set of “virtual” record field data types, instead of So, assuming that (1) we are not restricted from writing I see the gain, but I'm not sure what the harm is. |
|
@adamgundry This The proposal seems to be saying that Part 2: "OverloadedLabels" is now not the best way to go(?) I won't shed a tear over losing the I'm finding the write-up for https://github.com/adamgundry/ghc-proposals/blob/overloaded-record-fields/proposals/0000-overloaded-record-fields.rst#multiple-versions-of-fromlabel quite confusing. Could I suggest you include a table:
And if I set one valid combination in this module, but it's imported into that module where a different combination is set (possibly for same-named fields in different record types), does it just work? Or do I get perplexing type errors? |
| A serious limitation of the Haskell record system is the inability to | ||
| overload field names in record types: for example, if the data types | ||
|
|
||
| :: |
adamgundry
Sep 12, 2016
Author
Contributor
Use .. code-block:: haskell syntax for highlighting code blocks!
Use .. code-block:: haskell syntax for highlighting code blocks!
| this leads to less clear code and obfuscates relationships between | ||
| fields of different records. Qualified names can be used to | ||
| distinguish record selectors from different modules, but using one | ||
| module per record is often impractical. |
My point was that none of the built-in machinery would get any benefit from the poly-kinded version, so the poly-kinded version could live in a package other than |
I guess you mean that since the type of |
| ======================== | ||
|
|
||
| This is a proposal to introduce a new extension, | ||
| ``OverloadedRecordFields``, to make it easier to work with record |
adamgundry
Sep 13, 2016
Author
Contributor
See criticism of the extension name from @Hrothen. I'm rather attached to the current colour of the bikeshed, but perhaps there is an argument to change it.
See criticism of the extension name from @Hrothen. I'm rather attached to the current colour of the bikeshed, but perhaps there is an argument to change it.
Yes. Parts 1 and 2 are implemented. This is half of part 3; I left out updates in order to get something simpler and more manageable to implement first. I tried to make this proposal self-contained, rather than referring back to the (long and complicated) history. If there are places where you think knowledge of the history is needed to understand the proposal text, please flag them up so I can improve the proposal.
I'm proposing some changes to
You mean something like this?
All combinations are valid. Some flags override others, as shown above.
I originally proposed that
That's not how GHC flags work. Even if ORF implies DRF, it will be perfectly fine to say
We don't ever change what is generated for a data type decl from the normal selector functions. The extensions are purely about how names and labels are resolved in expressions.
Sorry, I don't understand the question.
The extensions affect only a single module, and control how name/label resolution works in that module. It doesn't matter which extensions are in use in the defining module, only the client module. Imports are relevant only when using |
|
Let me just for the record register my vote against making raw van Laarhoven lenses the default in the core Haskell libraries. I understand that they are popular, but I personally consider them unidiomatic Haskell. I like Haskell because I like types. Types express intent, both to the compiler, automatically catching mistakes, but at least equally important they express intent to the human reader. Raw van Laarhoven lenses express no such intent; most of the types in the |
|
@bgamari I like to suggest that we make the issue wrt to lenses a separate proposal. After all, this discussion is still ongoing and it doesn't seem reasonable that such a rather important user-facing feature is accepted before that aspect has a fixed design. |
|
I think that argument is almost a straw man @edsko. Lenses obviously do not give up types, and the types given in van Laarhoven lenses express what they do. I'm assuming what you really mean is the sub-typing can sometimes be surprising - for example "viewing" a traversal actually takes a monoidal summary of all values reached in the traversal, rather than viewing a single value. I agree this is a subtly, but it's hardly comparable to dynamic typing - and it is in the types, as viewing requires |
Not really, since you also have additional lens laws not enforced by the type. |
|
I'd echo @edsko and @kosmikus's disagreement about adding unwrapped van Laarhoven lenses to the core Haskell libraries. The types of those form of lenses are difficult to follow. The type errors are confusing, type inference is disappointing (leading to helpers like As far as syntactic overhead goes: For the basic lens, I see two of the "killer features": first-class accessors, and nested updates. My use of them is:
|
|
@ocharles I think it's a bit unfair to accuse @edsko of using a straw man here. There's certainly an amount of subjectivity concerned regarding the lens types. Yes, of course, a type such as |
I would argue that it's a different style of functional programming that makes sense once you're exposed to it. Nothing in the type restricts you from composing or feeding it to a higher-order function. |
|
@mchakravarty, a new proposal for discussing the default instance is probably a good idea at this point. It seems unlikely that this point will be resolved in the immediate future. |
|
This is a LONG thread! Adam, it would be good to summarise and draw it together somehow. My (not very well informed) thoughts are:
I have always been unsettled by the super-polymorphic type for lenses. I would far prefer them to have an proper abstract type |
|
@adamgundry - do you have a pointer to any discussion of the performance implications of working with overloaded record fields? I'm wondering if there are any benchmarks of different record libraries floating around, and if so it would be nice to link to them from this (mega) thread. In fact, I'd rather link to something than extend this thread further ;-). It looks like an unoptimized implementation of these overloaded types would end up passing dictionaries around, and I'd like to know how well the optimizer is working for code like this (plus, the effect on compile time). |
|
@simonpj I think we're in agreement regarding the plan for 8.2. (If anyone disagrees, please shout!) The questions about lens representations are interesting, but I think the best way to follow up on them is to experiment in libraries outside of GHC, rather than anything involving the GHC proposal process. We can put together a proposal for the setter counterpart to @rrnewton unfortunately I'm not aware of such a discussion nor of benchmarks of record libraries. My uninformed presumption would be that provided code is not polymorphic in |
|
I'm not sure if it's been mentioned, but |
|
@ocharles thanks for flagging up the |
|
I have a doubt about
What happens if a module that doesn't "see" a certain |
|
@danidiaz good question! It isn't entirely clear from the proposal text, but the rules for legal |
|
@adamgundry Why should the instance in instance (r ~ IO ()) => IsLabel "show" (Widget -> r) where
fromLabel _ = widgetShowSo it defines a virtual record field of type I hadn't chimed in before because I thought from reading the proposal that what |
|
@garetxe it's clear that those instances will conflict with any
|
This implements automatic constraint solving for the new HasField class and modifies the existing OverloadedLabels extension, as described in the GHC proposal (ghc-proposals/ghc-proposals#6). Per the current form of the proposal, it does *not* currently introduce a separate `OverloadedRecordFields` extension. This replaces D1687. The users guide documentation still needs to be written, but I'll do that after the implementation is merged, in case there are further design changes. Test Plan: new and modified tests in overloadedrecflds Reviewers: simonpj, goldfire, dfeuer, bgamari, austin, hvr Reviewed By: bgamari Subscribers: maninalift, dfeuer, ysangkok, thomie, mpickering Differential Revision: https://phabricator.haskell.org/D2708
|
@adamgundry Yes, indeed, the current instances will certainly break. My worry was whether I would be able to modify Playing around with it today, it seems like adding But simply adding an instance {-# OVERLAPPING #-} (r ~ ...) => IsLabel (Widget -> r) where
...seems to work both with the proposed selector function and lens instances, since it is strictly more specific than either of the proposed alternatives. So either way the final decision goes I can easily keep the exposed interface in Anyway, thanks a lot for the work on the overloaded record fields! Whether the overloaded methods stuff in |
Update the LocalDo proposal to use a qualified do
The proposal has been accepted; the following discussion is mostly of historic interest.
This is a proposal to introduce a new extension,
OverloadedRecordFields, to make it easier to work with record datatypes that reuse the same field names. It also makes some changes to the existingOverloadedLabelsextension for consistency.Thanks to all those who have provided feedback on previous iterations of this work, in particular @ekmett, whose helpful criticism prompted some further refinements to the design. Further comments/complaints/sponsorship offers are welcome!
Rendered.