New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add GHC.Variants module to mirror GHC.Records #510
base: master
Are you sure you want to change the base?
Conversation
Just to be clear, we would not be able to say both I ask because I am a big fan of I understand if there is a fundamental limitation here. I just want to make sure I am not missing something. |
This proposal only affects the If you want to re-use the existing auto-derived instance instead of declaring any yourself, then you will have to come up with some strategy for delegating between Personally I would prefer to go in a different direction that would give us
|
Interesting proposal. One aspect I like about this is that it uses a type family to retrieve the constructor type, instead of relying on a functional dependency. I'm not convinced about the |
% ghci-9.2.2
GHCi, version 9.2.2: https://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /home/phadej/.ghci
Prelude> :set -XOverloadedLabels
Prelude> :t #Foo
<interactive>:1:1: error: parse error on input ‘#’ Relaxing this would make sense, and probably can be justified standalone (i.e. |
I think this proposal is a step in a wrong direction. Look at class LabelOptic name k s a | name s -> k a where
... Here the structure I don't see any use case for allowing the same name to act as field and constructor, So I'd rather prefer seeing a proposal extending |
Personally I like TypeFamilies more than FunDeps in general, hence why I'm trying to make As for the As for two
Whereas before you'd need For the specific case of things like However if you look at the situations mentioned in the motivation section (or just at our company's codebase lol), there are a whole bunch of variant/enum-y types that would normally have short constructor/enum names ( |
I'll copy comment from here weighing those two options: It seems clear to me that overloaded product-types / fields / lenses are useful, it also seems clear to me that overloaded sum-types / constructors / prisms are useful. From there we have at least two choices: Unified hierarchyIn this case we have a single type family that is used for both constructors and fields:
One advantage of this is that it is more unified. Constructors are reversed fields and vice versa. So However the naming might be hard when something is both a field and a constructor (newtypes/isomorphisms), such as Stratified hierarchyIn this case we have two type families, one for constructors and one for fields:
One advantage of this is it allows for variants to define passthrough instances for fields, which would otherwise cause problems with overlapping type families: There is the following style of set-only fields that only set when the constructor matches:
There is also the following style with get-and-set fields that work when multiple constructors share a field:
You could also do the dual of the above (records that define pass through instances for constructors), although it doesn't seem as useful. Both of these options have significant merit, and it's not a trivial decision. I probably lean towards stratified, since most will probably consider the "foo field" and the "foo constructor" of a type to be different, and this is really all about naming and namespacing. Plus we have two different syntaxes for the two things, so two different hierarchies seems to fit pretty seamlessly into that, I would classify current Haskell as stratified, due to all constructors having an uppercase first letter and all fields having a lowercase first letter. |
My previous comment was misunderstood, but I don't know how to clarify. Feel free to ignore. The only clue I can add: think about setters, both prisms (constructors) and lens (fields) has an update operation. How (in either of approaches you propose) GHC could pick up which class to use (instance resolution doesn't backtrack). update :: (??? name s a) => (a -> a) -> s -> s |
In the case of the stratified hierarchy you wouldn't have The benefit you get from not allowing such an
Which you will not get without a manual instance in the unified hierarchy case, and I assume not in the case of whatever you had in mind (that seemingly isn't the same as the unified hierarchy?) You also avoid confusing things like As for the unified hierarchy that function is easy to give a type sig for:
I realize I used You would also get instances of |
How getField @"name" (Left $ Person "bob" :: Either Person Group) = "bob" would works? Is there some manual I'm still not convinced what you propose is in a right direction. |
I also think that open type families are a misfeature. If the choice is made to move to use TFs, they should be in a type-class. In stratified variant that is straightforward, in unified one there should be a superclass. If constrained type families paper were implemented in GHC, the type errors of undefined instance would mention the non-existing instance, and not stuck This is ATM my argument against type families: Open type families should always be constained, and GHC should be taught to report unresolved instance, and not mention the stuck type family. Otherwise the errors are terrible. |
Yeah it would involve a manual To be honest I'm flipping back and forth a bit on the unified vs stratified. Although I'd absolutely take either over nothing. The
My motivation on the stratified side is the idea that I was assuming people mentally think of constructors and fields in different namespaces, with different syntax I don't have a super strong preference on associated vs unassociated open type families, I just really don't like FunDeps because they make instance heads way too complex and require Were you thinking to more or less replicate a good chunk of the lens hierarchy with the unified route? That's more or less what I was thinking, although with some mostly-naming tweaks to make it more appropriate, for example I'd prefer |
Thanks for the proposal and for prompting this discussion @tysonzero! I've been thinking about
I don't yet feel that overloaded constructors are as compelling as overloaded fields, given that we don't have an equivalent of Prompted by this proposal, I sketched out the following design: class HasField x r (a :: TYPE l) | x r -> a l where
getField :: r -> a
class HasSetter x s t (a :: TYPE l) (b :: TYPE l) | x t -> s b l, x s -> t a l where
setter :: (a -> b) -> s -> t
class HasConstructor x t (b :: TYPE l) | x t -> b l where
construct :: b -> t
class HasMatcher x s t a | x s -> t a where
match :: s -> Either t a -- Or we could use (# t | a #) if we wanted to allow unlifted a... The idea is that GHC would solve |
@adamgundry Is it on purpose that the classes are not related. I can see a motivation, but this can be a source of incoherence, a e.g. If you going to propose that, I'd expect a rationale for that. (I think it's ok if only GHC would solve these instances, but if users are allowed to write them as well, that will be a potential source of mistakes) Note: that |
Note that
The type variables on the input of the TF / left hand of the FD are swapped. |
I think In general, I am inclined to worry less about the case of users defining their own instances, which I see as rare and for users who know what they are doing. That's another reason I favour fundep magic over type family magic: the complexity in defining the instances isn't usually visible, while the simplification affects use sites. |
Sorry, as written this makes no sense to me. Do I need to read a load of other proposals first? You've linked to some, but they're mostly still in motion. In particular, where does the
(Perhaps change this Proposal to status Draft until it is more coherently stand-alone?) |
The The two |
All this incoherence stuff is making me nervous, does this mean that doing anything It makes me particularly nervous because All the FD stuff I'll have to look into more, I don't know enough to have an opinion at this time. My one question would be if all that stuff enables me to do:
Without Ideally in a way such that accidentally flipping the last two
Part of the reason I like this is it finally gives a non-overlapping non-incoherent instance for There are really two key forms of type directed name resolution in Haskell right now, dots and labels, dots have field-like things handled, which leaves labels for constructor-like things. That way we don't need yet another TDNR syntax.
I'd take a look at the linked stripe code for a decent motivating example. It's the most heinous thing the world has ever seen, and it's truly embarrassing to have it in our codebase when onboarding new devs and trying to convince them that Haskell is better than what they're used to. This is half about aesthetics (the worst looking thing in our codebase right now is the prefixes from lack of dot-syntax, the second worst looking thing in our codebase right now is the prefixes from lack of something like this), and half about stock/generic generated instances (since I don't want those prefixes in the DB or in our publicly-available JSON api's or binary serializations or similar). We have a ton of types like Using qualified imports doesn't work super well because there are a variety of helper re-exporting modules to avoid lots of import noise, so we'd then have to define a bunch of constructor aliases in that re-exporting module with the prefixes added, which is possible but again gets back to the whole verbosity/ugliness thing. Also small thing but if we're doing
|
It's only used currently for record fields, which must be lower-case. I was rather hoping now we have So I think the Proposal should say you're blocking off other opportunities. It does seem weird to have Can I partially apply a hash-prefixed constructor? oneOfTwoFields :: Bool -> MyType
oneOfTwoFields = #TwoFields 5 If not, are you offering some other way to disambiguate multiply-defined constructors, so they can appear in all the other use-cases for constructors?
Yeah I'd guessed something like that. But the proposal doesn't actually say so. Having to put the fields inside a tuple is clunky. What if I want record syntax for my data constructor (and to use labels which might be same-named as other types) -- esp where there are heaps of fields of the same type: data MyType
= NoFields
| OneField Char
| TwoFields { theInt :: Int, theBool :: Bool }
twoOrOneFields = #TwoFields { theInt = 5 } -- ?? And does that create |
For the record (hah!) the reason this is already a mess in Haskell is lack of support for 'Row types' -- see for example the Introduction here, recently, and all its citations to the theory. (A Haskell with the beginnings of Row types was available from 1996.) Row types don't have 'private' labels/all labels are global; the label name alone doesn't tell anything about the field's type or the structure it's a field within. From a Row type you produce either a Sum aka Variant or Product aka anonymous Record. So your claim "Since Variants and Records are duals, ..." is true of Row types, not of Haskell ADTs. I fear this Proposal is making the already-mess into more of a mess, because it has to wrangle the existing namespace disciplines. Consider a hypothetical type PaymentMethod = Var{Card :: ..., Bank :: ..., ... } -- the braces { ... } has type-of-type Row
-- Var(iant) is a type constructor :: Row -> Type -- that is, produces a Sum type
-- note upper-case labels are allowed
data Customer = MkCust{ ..., payMeth :: PaymentMethod, ... }
myCust = MkCust{ ..., payMeth = {Card = ...}, ... } -- no constructor prefixing the {Card = ...},
-- so this is fixing which Variant
chargeTo MkCust{ ..., payMeth = {Card = cardDetails}, ...} = ... (At the point of parsing That example hasn't gone so far as making |
I don't really agree with that. In fact using it directly for record fields isn't even possible without defining an orphan instance for Going forward I imagine it will be used little for record fields outside of perhaps the
Most of the proposal is adding a new module and generation for instances of a class in that module. The only potential opportunity being taken off the table is having the
As per above it's currently not possible for it to mean "component-accessor" without dangerous orphans, you can have it be an optic/lens, but you can't have it be a component-accessor.
I mean the motivation talks about naming collisions in just about every paragraph, and the idea that collision aren't a problem is a direct consequence of the classes specified, but I guess I could have added an extra statement or two making that extra clear. I do more or less assume a fair amount of familiarity with
Note that once rows and anonymous records are a thing, the record examples can be made to work just fine, as a record within a variant would just be an anonymous record instead of the weird magic thing it is now. I currently am not really prioritizing working with records in variants as they are largely a wart in most current Haskell, with them generally generating partial functions outside of
Yes I am well aware of the a lot of the existing row type discussions and several of the papers relating to them, and this proposal is extremely future compatible with all of them. It's intentionally very simple and doesn't concern itself with some of the cases you just mentioned partly for that exact reason. Any future extensible rows/records/variants implementation would be able to easily define instances for the introduced classes. You are correct that the duality is stronger with row-based variants and records than with Haskell ADTs, but if you are a fan of row-types being the future then if anything that should make you support it more, as if anything it actively encourages moving in that direction, due to it being so fully future compatible. It's also worth noting that even with anonymous extensible rows records and variants, classes like this and the duals in I also know that realistically the timeline for extensible anonymous rows records and variants is probably on the order of several years, and I'd like to be able to improve the uglier parts of our company's codebase before then. |
Disconnection. Orphans also allow different people to define unlawful instances very much accidentally. If Haskell were able to encode the laws as members of the class(es), not in hierarchy laws would be out-of-place. |
I agree. There could be just getters, and just setters. But then there are issues I mention above. Therefore I like single-class-to-rule-them-all (like |
You're being wildly optimistic. It's been over seven years to get what little we have in 9.2, and more like twelve years since |
Aww everybody's baby is beautiful for them. I presumed the examples in the Proposal were made up. If your codebase is heavy on commercial data structures that (presumably) get held in a flattish database/not syntax trees or highly nested structures, I'm wondering why your company is using Haskell? I think it's not the right tool for that sort of application. |
Although I entirely disagree with your sentiment, I believe it also contains motivation for the proposal - maybe the author is pushing in the direction of making Haskell more pleasant to use for these scenarios. |
You are being polite. IMHO that instance is a show stopper. The https://hackage.haskell.org/package/generic-lens-2.2.1.0/docs/Data-Generics-Labels.html is an example of |
@AntC2 No wonder Haskell is less popular than most languages, one point of ugliness and people suggest you throw the whole language out. We still love having far more type safety, less verbosity, easier refactoring, and more composability than alternative languages. The impedance mismatch with relational databases is arguably less bad than it is in an OOP language anyway. @googleson78 I mean I want Haskell to be as pleasant to use in as many scenarios as possible without sacrificing it's core values, and a couple classes to make dealing with fairly boring situations like overlapping fields and records much less painful would be fantastic. @phadej That instance is completely insane to me. To me instances are about canonicity, otherwise you should be using just a function/value. Just because van Laarhoven lenses happen to have a |
(This is getting way off the point, I'll start a Discourse thread, but ...) Haskell's (lack of) a well-founded records/rows system is many points of ugliness. Which this message seems to confirm. If you look back at the history, the H98 design was always a stopgap, and there were many papers/proposals in the early 2000's; so nobody was proud of it then. But no proposal 'stuck', so starting ~2012, people started putting lipstick on the pig. There's by now many layers of lipstick. In nearly every other part of Haskell design, a proposal is not entertained unless it has a sound theoretical basis. (That's why Overlapping Instances or FunDeps don't get much love at GHC HQ.) For records/rows there are theoretically sound approaches; purescript for example never went near H98-style records; and already has a Variants module built on better-founded rows/records. But Haskell/GHC stumbles on with the lipstick. In my experience, database-intensive commercial applications with mostly flattish files aren't using so much higher-order fandango/don't need to push the limits of a language's type system. (In the applications @tysonzero is talking about, perhaps the commercial-looking bits are a thin layer on top of other complexity.) Haskell programmers are hard to find, hard to retain; it's lengthy to cross-train programmers from other languages. If your application mostly uses familiar commercial data processing, use a familiar language. |
I'm sorry but all of this unsolicited advice about how our company should be run is really not even slightly relevant nor is it particularly appreciated. We are very happy with the language we have chosen, we just want to clean up some of the ugly bits. Particularly when such cleanup fits extremely naturally with the existing I appreciate other discussion about unified vs stratified and so on, and I could definitely see some of these counterproposals being improvements on what I have, but saying this code should just keep being ugly and you should switch languages is just about the least useful thing possible. Particularly since just about any large software project will have a variety of different things going on, so throwing up your hands the second you have a boring business-y enum or have to interact with stripe is ridiculous. I agree with the lipstick on a pig stuff, but that's why I like this proposal (and most of the similar counterproposals), because it is fully future compatible with every reasonable implementation of anonymous extensible rows/records/variants. For now I'm mostly curious to see what @adamgundry says to my questions, as it seems like he has made some promising developments in this area, but I also do have some skepticism about certain aspects of it. |
@phadej such proposal was written and accepted a long time ago (#170), but in the process extended (unnecessarily?) so that the original author lost interest (ghc/ghc#192 (comment)). |
(apologies that I'm currently swamped and therefore not responding as quickly or coherently as I would like)
Yes. That is, we should explicitly document that
Indeed it would be nice if we had a tighter story about coherence for "normal" classes and the ability to enforce it more strictly. But I still think it is reasonable to have some classes that are explicitly marketed as allowing incoherence, where they are used for type inference but not intended to emulate a poor-man's module system (ala
I think excluding
Well, we could choose
This motivates being able to use multiple constructors with the same name, I agree. But does that necessarily mean type-based overloading? Or could it be something like local modules (#283)? That might reduce the pain of qualified imports and juggling constructor aliases, while still being resolved in the renamer rather than relying on type inference to figure out which constructor you mean. Moreover, we don't currently have a story for overloaded constructors in patterns. It seems like one will want that pretty quickly if you have a codebase that has many non-unique constructor names.
I've gone back and forth on these naming questions. Now that |
Yes, I see your point, it is a nice property of What I'm not sure about is how to bring that into GHC itself. What would it look like to have a |
Sorry didn't mean to imply any hastiness, just wanted to make sure you that my questions for you didn't get lost!
Is there a real use case where someone actively wants incoherence here? Or is it more of a case of being hard to enforce coherence even if it just about always happens in practice? I can't imagine wanting I would imagine things like manual serialization instances would rely on these instances, and coherence of serialization instances is of course very important, so would these be in jeopardy as well, or fine as long as you are careful to make sure the serializer and deserializer are always defined in the same module?
Whilst I do like to use I would really prefer if
Fair point. I more or less see UndecidableInstances as a symptom of the true underlying cause which is that complicated instance heads are messy, but complex TypeFamily outputs are not. Similar to how the left hand side of function definitions are generally much simpler than the right hand sides. You get issues with overlapping and coherence and decidability and so on that you just don't get even a fraction as often with complicated TypeFamily outputs.
I suppose it's a matter of taste yeah. Personally I think the argument that you should use a helper term or a Realistically the For the latter example you end up with pretty gnarly error messages to throw at a beginner in the fairly simple situation of calling a constructor wrong, so you can't just start people out with that right off the bat. You also theoretically could have the
I wouldn't be against allowing this, although it seems like you may have to tread pretty carefully with how it interacts with module syntax, particularly with extensions like the one you linked. Although in a more dependent Haskell you could imagine modules and records being more unified, so it seems like it shouldn't be a deep and fundamental problem.
Local modules does seem like a solid proposal (assuming it works smoothly with all this I also think on a more fundamental level that Haskell should have a more first-class way of interacting with fields and constructors as concepts, even without looking as much at the sugar side.
Yeah I tried to keep the proposal simpler and closer to a dual of I think the dual aspect between records and variants can be further utilized to have pattern matching piggyback off of record creation. Given the following type:
You can pattern match on it via the following function:
Which you can of course further put behind a typeclass and give a concise name like
Now I realize there are a variety of things going on above so I didn't want to bundle all of them into a single proposal, as there may be various sub-pieces that people don't agree with. I figured something like the proposal as stated, or a similarly-broad counterproposal, would be easier to reach consensus on.
Isn't there technically already an accepted breaking change to it via adding setting to the class? But yeah I suppose if a separating get-and-set proposal was accepted before that change was implemented then you might be able to cut out that breaking change. Assuming you don't switch to TypeFamilies or change
I'm with you on that one. I am a big fan of lenses but I'm not sold they should be baked in this deeply to such a core part of Haskell that tons of beginners will end up touching fairly directly. I would definitely plan to use various lenses built on top of these constructions myself. I also see lenses as being more likely to be future-limiting, as we can always add more subclasses and such, but if something fits outside of a reasonable optic it seems you are in trouble with a single-class approach. For example record extension seems like it may be a challenge. As far as I can tell it doesn't fit anywhere into the current lens hierarchy. Which reminds me that in your previous comment you used If we go that route I'd suggest to consider also adding the dual:
I realize that GHC would not be able to create instances of this automatically with the current non-extensible approach to records, in the same way that However it'd be nice to have a canonical class to manually define instances for and for libraries to build off of, allowing any future extensible records library to fit seamlessly with any library-level extensible records solutions and similar in the meantime. |
I think it isn't that we want incoherence per se. Rather, coherence prevents two things we do want:
I agree that it would be better if
I'm wondering why a |
I agree, i'm not a fan of structural typing. Writing The correct (or at least more Haskell) solution is harder in this case... (and you couldn't use dot syntax if the context is |
It seems like an annotation either per-field or per-type to not automatically generate an instance would be a pretty reasonable thing to do. Although I am not particularly convinced that having record pattern syntax that deviates from record dot syntax is intuitive or desirable. I'd expect You can always use a positional constructor in those cases, and in fringe cases where the internal structure is a large complicated set of different fields, I'd argue for something like:
I'd argue the currently considered solution of just having incoherence is pretty weird. If I use dot syntax within the module the type is defined, would I expect it to use the manual instance or the automatic instance? The former seems like it'd feel inconsistent and sketchy, and if the latter then why even generate the automatic instance at all.
To be clear I didn't mean the instance would be exposed like that. I just meant that the instance would be used in the literal sense of there being some
Even with just the above coherence is still a concern. For example if you defined one of the serialization directions in one module and the other direction in a different module. I'd mostly agree with the two of you that true structural typing of that form is undesirable. The main "structural typing" I want is things like extending and contracting records/variants, as opposed to things like |
I think all the type-directed overloaded name stuff in Haskell today is shoddy and we not in a position to make it not shoddy any time soon, so the important thing is instead to make the quantification-disambiguated stuff good. I am not sure @adamgundry is as pessimistic as I about type-directed things, but we both agree on wanting the quantification-directed stuff to be better. I would proposal a moratorium on this sort of thing until we settled on proposals for making the name-directed stuff good --- those proposals are much simpler and there is far less to bike shed. Then, with a solid baseline established, we can return to these fancy bikeshedable type-directed things. |
Strongly disagree. RecordDotSyntax will easily lead to the single largest improvement in aesthetics, simplicity and verbosity in our codebase of any extension of the last decade and it's just not close. I also do not think it's shoddy, symbols in typeclasses is a very natural extension of things like the It's also worth noting that nothing is actually be added to the language itself to support this, the only part that has any future-compatibility concerns is the IsLabel instance, and I'd still take this proposal (or equivalent) without that part over nothing. So it's not like we are painting ourselves into a corner by filling up gaps in the language with this stuff. |
Please can we quit with the apocalyptic language (I acknowledge I go in for some of that myself); and with the hype. The last decade introduced Pattern Synonyms, by the way. @tysonzero: is it possible you can simulate the 'compiler magic' you want via Template Haskell? At least you could substantiate the claim for improvement in verbosity -- and aesthetics. |
I'm genuinely not exaggerating.
Sure, which affects maybe a couple dozen lines of our codebase. RecordDotSyntax will affect tens of thousands of lines.
The only part of this proposal that is compiler magic is the resolving instances part, which cannot be done with TH. The rest can be done in library code, although it would have problems like orphans (IsLabel) and non-canonicity (the GHC.Variants classes). |
(Not my experience but ...) This is a proposal not for RecordDotSyntax, but for Variant types. It might be an interesting exercise to implement proper Variants into Haskell, as the dual of Product types. But:
So the motivation is left with the allegation that in @tysonzero's codebase, there are some clashes of desired constructor names. My reaction on first reading this proposal was: 'sfunny, I've never wanted to name a data constructor same as a type. (Neither have I wanted to name a constructor for one type same as the constructor for a different type. [**] Whereas I often want to name a field the same in two different datatypes -- because I have many data structures pointing to the same
The data Region = City CityId | State StateId | ...
Then the DB column is not holding the Addit: There's a growing trend to name data constructors as [**] You're of course liable to get name-clashes amongst constructors in separate modules, from different authors/different packages. That's what the module naming system is for. data Month = Jan | Feb | ... | Oct | Nov | Dec
...
data NumBase = Bin | Oct | Dec | Hex |
Of course, but I was very specifically addressing the comment that said "all the type-directed overloaded name stuff in Haskell today", so I talked about RecordDotSyntax, which is a "type-directed overloaded name stuff in Haskell today".
What's your model? Haskell ADTs are no closer to anonymous extensible variants than Haskell Records are to anonymous extensible records. Both suffer from lack of extension/contraction, structural abstractions, anonymity etc.
I'm sorry but what? This proposal is not at all an imitation of variant types, nor does it attempt to replace existing Haskell ADTs. It is a series of helper classes and instances to help you work with Haskell ADTs, that would be completely future compatible with any reasonable implementation of true anonymous extensible variant types, such as those I've seen in variety of papers. I don't get how to make this any clearer.
None of those would work at all to achieve our desired goals, but I don't really have the patience to actually go through and explain why, so I won't bother for now. |
I think it's reasonable to ask for use cases. The proposal has been up for a couple of weeks; nobody else has come to say: yes I have a real-life use for this. I think it's reasonable to ask whether a given proposal is the only or the best approach for a set of requirements/the name clashes you're experiencing.
Then perhaps it shouldn't have 'Variants' in the title? (I'm also rather dubious how much the purescript-variant library is "an imitation of variant types". I guess the 'theory' is rather diverse.)
purescript at least is ahead of Haskell in having anonymous records, as a "reasonable implementation". (I'd say that Variant library is "compatible" in that it looks like an unconnected feature, rather than 'orthogonal' in the sense of Records/Variants using the same underlying abstraction of label-value pairs.) I agree with @Ericson2314 that there's already enough parts in motion around "name-directed stuff "; we should wait first to see how that settles.
Have you reviewed what the HList team did? That is, without compiler magic, in 2004. Their Appendix C 'Type-Indexed Co-products'. They had only FunDeps available at the time, but I think that stuff's been translated to use Type Families. |
@tysonzero there issues with record dot are:
And there are no plans from the record dot crowd to fix the first issue, no recognition of the second issue from just about anyone but me, and I am not sure anyone besides @adamgundry was interested in fixing the 4th issue. As such, record dot and overloaded labels are a 80% solution that is simply not up to par with the older parts of Haskell, and no one knows how to actually fix them. And yet it still consumes tones of time as we debate what the maimed optics classes look like. The biggest issue with pre-record-dot records is not verbose syntax, but verbose identifiers from humans hand-rolling namespacing with field prefixes. I'll be the first to admit that is complete garbage that we ever had to do that in Haskell, that that part of Haskell 98 records was ever acceptable. Finishing up name-resolution based fields and variant work will fix that most egregious issue with a 95% solution that work always, not matter how complex the constructor and field types are. (The last 5% is data families, but I am happy to just say data families are in the wrong for not providing allowing one to provide a type constructor name.) With that fixed, we can have a more sober conversation where record dot just makes a few bits syntax things less terse, and the main issue of avoiding manual namespacing is already solved. |
I personally think that we here can sometimes suffer too much from letting the perfect become the enemy of the good. I acknowledge that I think it comes down to a question of what we're trying to achieve here: do we prioritize an elegantly designed language, where e.g. everything is first class and all the features mesh with each other even in dark corner cases? or are we trying to build a language useful in common applications? If we wanted the first, I have a lambda calculus I'd like to sell you. But we don't program real programs in a bare lambda calculus, because doing so is painful for practitioners. So I favor offering well thought out, practical conveniences -- even if those conveniences don't work in every case. Others might reasonably feel differently, but a frequent frustration I have heard (and sometimes share) about Haskell is that it's impractical -- let's fight that by accepting proposals that people find useful, even if they are imperfect. To be clear, this isn't an impassioned plea to accept the current proposal, which I remain unconvinced about. But, to me, saying it's like |
Recently the GHC.Records module was introduced for interacting with records without
the problem of field name conflicts.
Since Variants and Records are duals, it is natural to have a matching GHC.Variants
module for interacting with variants without the problem of constructor name conflicts.
Rendered