What are you supposed to do about -Wambiguous-fields?

I already completely switched several projects to record dot syntax for accessing record fields, together with NoFieldSelectors, and I really liked it. In my opinion it also makes the code more readable, because function application and field access are more visually distinct, but on that point opinions may vary. As soon as record update is properly stabilized I will no longer look back on the old record syntax and exclusively use the new one :slight_smile: But we should of course keep the old mechanism available for as long as necessary, or even indefinitely.

1 Like

But doesn’t the replacement already exist? That is, OverloadedRecordDot and OverloadedRecordUpdate? I still don’t understand what people think that they are lacking. See also What are you supposed to do about -Wambiguous-fields? - #8 by tomjaguarpaw.

EDIT: Corrected extension names

I didn’t know RecordUpdateSyntax, where would I be able to find information about it? Doesn’t look like it’s in GHC 9.10. (RebindableSyntax is not an option in the general case)

It also appears that the compiler doesn’t generated setField yet, does it?

https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_record_update.html

What I meant is the OverloadedRecordUpdate extension, which is still marked as experimental in the user guide.

Sorry, I got the extension names wrong: they should be OverloadedRecordDot and OverloadedRecordUpdate.

Right, and we have the guarantee that ambiguous field selector/update usage will not be removed until this replacement (or some other) stabilises:

In a subsequent GHC release, remove support for ambiguous field selector/update occurrences entirely and remove the warning. This step should not be taken until RecordDotSyntax or another generally-accepted mechanism for disambiguation is available, to provide users with a clear alternative.

Not as far as I know. It sounds like something that should be derived anyway, but I don’t know the best way of doing that.

The warning GHC-02256 also wasn’t documented yet, so I started a PR to document the warning in the error message index. I currently have one example which uses the solution that @adamgundry proposes, but I am happy to add other solutions as well :slight_smile:
The PR is here: Document GHC-02256 by BinderDavid · Pull Request #484 · haskellfoundation/error-message-index · GitHub

I would also be grateful for suggestions on how to improve the explanation of the warning.

2 Likes

for playing around:
https://bin.mangoiv.com/note?id=f876d8ce-3a7a-4acf-9ce7-adb348d44b5b

edit: yes I know it should be (name :: Symbol) cause otherwise we don’t get an instance but mmh this is hacked together

Thanks to @MangoIV and @DavidB I can provide some answers:

  • OverloadedRecordUpdate is considered experimental.
  • There is no definitive story for generating getField for OverloadedRecordDot.
  • There is not even a blessed class for setField, for OverloadedRecordUpdate.

Them’s fighting words :smiley: Type-directed name resolution is controversial in languages with type inference because it generates an ugly chicken-egg problem

One unintuitive thing for me: why is type inference involved at all? In my example, I should think that the type checker could use my user-written type signature as a constraint: bar : Bar.

blah :: Bar -> Bar
blah bar = bar { foo = 5 }

Rather, this ambiguous nugget seems to be considered in isolation at some point, generating the warning.

{ foo = 5 }

But I don’t need GHC to work so hard; I want to assert what type the record is!

The proposal DuplicateRecordFields without ambiguous field access by adamgundry · Pull Request #366 · ghc-proposals/ghc-proposals · GitHub contains a pretty good discussion about the design space. For example DuplicateRecordFields without ambiguous field access by adamgundry · Pull Request #366 · ghc-proposals/ghc-proposals · GitHub gives good arguments why type directed name resolution doesn’t work so well with the way GHC works.

As another example: There is a recent bug report #23992: `$` converts ambiguous record update error to a warning · Issues · Glasgow Haskell Compiler / GHC · GitLab which shows that your blah bar = bar { foo = 5 } currently works, blah bar = id bar { foo = 5 } does not work, but blah bar = id $ bar { foo = 5 } works again. It is just not very transparent and understandable when GHC can do the type-directed name resolution and when it can’t.

1 Like

I suspect that when the proposal to add the warning was written, it wasn’t anticipated that development of OverloadedRecordUpdate (then part of the proposed RecordDotSyntax along with what is now known as OverloadedRecordDot) would take so long.

This has led to an unfortunate state of affairs where we’re warned of the need to migrate before the ideal migration strategy actually exists, since OverloadedRecordUpdate is still experimental, and in practice basically unusable due to the need for manual setField instances.

So, like others, I’ve ended up just disabling the warning everywhere I’ve seen it so far, on the assumption that the DuplicateRecordFields behaviour won’t actually change before OverloadedRecordUpdate has been fully implemented. And I would strongly urge any GHC developers on this thread to ensure that’s the case, since anything else would be a very annoying regression.

I fear this whole situation is going to confuse everyone who hasn’t totally kept up with the status of the many record extensions. Which, anecdotally, seems to be almost everyone.

tl;dr I don’t think there’s currently a satisfying answer to the actual title of this thread. (EDIT: at least in the record-update case which is what we’re discussing - the same warning also covers ambiguous selection, but there are better workarounds for that)

1 Like

We have indeed been given that guarantee:

https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0366-no-ambiguous-field-access.rst#transition-period

2 Likes

Thanks for digging that out! I’m still unsure about the wisdom of introducing this warning, given that most current code will be fine when OverloadedRecordUpdate arrives, but that’s at least reassuring.

Yes, it does seem a bit strange to have the warning whilst you can’t do anything about it.

This is a long thread! Would anyone feel able to summarise the consensus (if one exists) about what you’d like to happen in GHC?

I hope I don’t summarize that incorrectly in that I say that:

  • the current situation of telling the user to migrate to disambiguation based on module qualification doesn’t work and that one would expect it to work based on types
  • we would all like to use HasField as a replacement that respects the technicalities of GHC but it’s not usable as the replacement it’s supposed to be at the current state as the implementation of OverloadedRecordUpdate hasn’t progressed enough, in particular by forcing RebindableSyntax and not generating setField for the user
  • there needs to be a migration strategy for users who cannot implement the recommendation the warning suggests at the moment

Please add on to this or correct me if I summarized incorrectly.

1 Like

Somewhat disagree: I am satisfied by the explanations that allowing type-based record disambiguation creates a large and nasty wart in the way information flows through the compiler. Personally, I quite like proposal #537, because it uses existing syntax, doesn’t leave the big wart in GHC, and lets me write forward-compatible versions of record updates in amazonka-* service packages without having to ship 300+ new sdists to Hackage.

Personally, I’ve never been that excited about any of the recent record extensions. Old-school records were unergonomic, but lenses are so much more versatile that I consider them well worth the learning curve.

1 Like

That’s fair, I was summarising the discussion up to that point.

My memory is that proposal #537 stalled because there wasn’t a clear winner between two options:

  • Permitting the renamer to look at type signatures in limited situations, so e.g. (r :: T a b c) { f = x } would unambiguously pick out the f field of type T. This would be backwards compatible, but is potentially verbose where T has many type parameters, and it may feel rather restrictive that type signatures have to be placed in very specific places to be used when disambiguating.
  • Permitting type names to be used as module qualifiers, rather like LocalModules, e.g. r { T.f = x }. This is not backwards compatible or necessarily obvious to use, and may lead to awkward corners (e.g. if T is a module name as well as a type name) but it avoids writing out the type parameters.

I think either of these is plausible in principle, but the details need working out and we need to somehow reach consensus. It would be great if someone could help drive that discussion forward.

On the OverloadedRecordUpdate/setField front, I put quite a bit of effort into nailing down the design in proposal #583, which has been accepted, and I’m fairly happy with where that ended up. So it’s now a Small Matter of Programming to actually implement it. Unfortunately I’m not sure I currently have bandwidth to do so, but if anyone would like to take this on, I’d be happy to advise.

Correcting one misapprehension from earlier: automatic constraint solving for HasField(getField) (used by OverloadedRecordDot) is fully implemented in GHC and I think it is in reasonable shape. The only obvious thing missing in released GHCs is support for representation polymorphism, which I’ve recently implemented in GHC HEAD.

2 Likes

Thanks Adam, I agree with your “wasn’t a clear winner”. (At risk of bikeshedding about mere lexing) I know the idea is to use dot-suffix to give record access the same flavour as in other languages, but it’s not a good fit with Haskell in general:

What’s more, if we’re updating more than one field, it’s even less obvious:

r { T.f = x, T.g = y }         -- repeat the T. everwhere?
r { T.f = x, g = y }           -- T. implicitly repeated?
r { f = x, T.g = y }           -- retrospectively repeat the T. ?

Inside { ... } we’ve quite a bit of lexical freedom. (For comparison, inside [ ... ] we can put list comprehensions.) Maybe

r { T | f = x, g = y }
r { T a b c | f = x, g = y }
--         ^^ ample opportunities for bikeshedding as to which separator

I appreciate you’re needing someone to drive this. I’m not volunteering because I’ve always had a distaste for Haskell’s approach to record syntax; I find nothing to love about any of the attempts to work round the H98 restrictions. I simply don’t use record field names outside { ... }.