Skip to content
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

Ambiguous Type per-signature pragma #232

Merged
merged 15 commits into from Aug 15, 2019
Merged

Conversation

AntC2
Copy link
Contributor

@AntC2 AntC2 commented May 22, 2019

The proposal has been accepted; the following discussion is mostly of historic interest.


Per-signature pragma {-# AMBIGUOUS #-} to allow that signature's type to be ambiguous; instead of the module-wide -XAllowAmbiguousTypes, which dangerously lifts ambiguity checking on all signatures.

Also tweak the error reporting to avoid recklessly suggesting users turn on -XAllowAmbiguousTypes; and provide a flag -Wallowed-ambiguous-types that shows the ambiguity message as a warning.

Rendered

@goldfirere
Copy link
Contributor

I'm in strong support of this well-written proposal, but please permit me to offer some suggestions:

  • I think {-# AMBIGUOUS #-} should be allowed wherever type annotations are allowed. Beyond just variable and method signatures, this includes in expressions, patterns, and pattern synonym signatures. For an example of when you need this in a pattern:
apply (x :: forall a. (Read a, Show a) => String -> String) = x @Int "01"

Though we can't use this today, with my "type-lambda" proposal, we could write

norm :: forall a. (Read a, Show a) => String -> String
norm = show @a . read

and then call apply (\ @a -> norm @a).

  • With -Wallowed-ambiguous-types, do you mean that a warning would be raised even if the user includes {-# AMBIGUOUS #-}? That seems off to me. One guideline I have for a warning is that there should be some action the user can take to make the warning go away (other than just saying -Wno-xyz). I could see warning on ambiguous types with -XAllowAmbiguousTypes though.

  • I would support a more ambitious proposal that sets a deprecation strategy for -XAllowAmbiguousTypes. Though maybe some programs have so many ambiguous types that this would be too annoying. In that case, I like the {-# NOT_AMBIGUOUS #-} idea (spelling slightly changed).

  • Unlike {-# OVERLAPPABLE #-} and friends, which do change semantics and probably shouldn't be pragmas (but, instead, something else, like a keyword), this one does not change semantics. It just skips a check. Yes, yes, that could be called a change in semantics, but given a program accepted with {-# AMBIGUOUS #-} either (1) the same program would have been accepted with the same meaning but without the pragma, or (2) the program would be rejected without the pragma. In no case does the pragma change the name of an accepted program, and so (to me) a pragma is appropriate here.

Thanks for writing this up @AntC2.

@int-index
Copy link
Contributor

either (1) the same program would have been accepted with the same meaning but without the pragma, or (2) the program would be rejected without the pragma.

Same goes for {-# OVERLAPPABLE #-}, no?

@adamgundry
Copy link
Contributor

I echo @goldfirere's support, thanks @AntC2!

  • Perhaps -Wambiguous-types is a shorter and equally clear alternative to -Wallowed-ambiguous-types? (The extension could really have been called AmbiguousTypes in the beginning -- we don't say AllowRankNTypes after all!)
  • I agree that a warning should be issued when AllowAmbiguousTypes is used to permit an ambiguity, but not when the {-# AMBIGUOUS #-} pragma is used. If I'm converting a codebase to this wonderful new world, it would be nice to set -Werror=ambiguous-types, fix the errors and then stop. One can always grep for AMBIGUOUS, after all.
  • Presumably -Wall will not include the new warning, at least initially?
  • Do I understand correctly that a type marked {-# AMBIGUOUS #-} is permitted to not, in fact, be ambiguous? Or should it be rejected with an error telling the user to remove the redundant pragma?
  • I'm not convinced {-# NO[T_]AMBIGUOUS #-} is worth the candle.

@AntC2
Copy link
Contributor Author

AntC2 commented May 22, 2019

@goldfirere Unlike {-# OVERLAPPABLE #-} and friends, which do change semantics and probably shouldn't be pragmas (but, instead, something else, like a keyword), this one does not change semantics. ... either (1) the same program would have been accepted with the same meaning but without the pragma, or (2) the program would be rejected without the pragma. ... and so (to me) a pragma is appropriate here.

Yes. My chief concern is with sourcecode tooling and people importing this program as a package: they'll need to know they're in for TypeApplications. So do we provide some indication at the top of the program/in the LANGUAGE settings? I guess the tooling could go looking for pragmas, but they're a little tricky to detect mechanically because they're treated as comments and they might have arbitrary whitespace inside.

@int-index Same ["does not change semantics"] goes for {-# OVERLAPPABLE #-}, no?

Actually no: if an instance is marked OVERLAPPABLE, GHC somewhat avoids usage sites committing to it. So you can somewhat avoid orphan instance problems. As opposed to marking the OVERLAPPING in the importing instance, which merely accepts the instance and imports the orphan. (Not a lot of people know that; and it's highly unreliable/dependent on optimisation settings, for example.)

@AntC2
Copy link
Contributor Author

AntC2 commented May 22, 2019

@goldfirere I think {-# AMBIGUOUS #-} should be allowed wherever type annotations are allowed.

Ah, OK. I was thinking AMBIGUOUS wouldn't make sense in a pattern signature/synonym. I don't have your ingenuity to think of weird places to put @pplications. ;-)

warning would be raised even if the user includes {-# AMBIGUOUS #-}? That seems off to me.
[and @adamgundry agrees] it would be nice to set -Werror=ambiguous-types, fix the errors and then stop

OK fair point re "and then stop". I was trying to keep it simple for the implementer.

@goldfirere would support a more ambitious proposal that sets a deprecation strategy for -XAllowAmbiguousTypes.

Yes that was my thinking in the Interactions section "migration path". Without saying it out loud for fear of upsetting @pplication-aholics, who I expected to say this:

I like the {-# NOT_AMBIGUOUS #-} idea

That only makes sense if we don't deprecate -XAllowAmbiguousTypes. Or am I not understanding?

@adamgundry Do I understand correctly that a type marked {-# AMBIGUOUS #-} is permitted to not, in fact, be ambiguous?

;-) That's the current behaviour with -XAllowAmbiguousTypes, because the alternative would be hugely annoying (and probably impossible). Then I was trying to keep proposed behaviour as close as possible to current.

Or should it be rejected with an error telling the user to remove the redundant pragma?

I'm happy to take a recommendation. Error or only warning? How easy to implement?

@RyanGlScott
Copy link
Contributor

I'm definitely in support of controlling the scope of AllowAmbiguousTypes to be on a finer granularity than what it currently is. I'm not entirely sure if a pragma is the best way to accomplish this.

In the past, there have been many proposals to allow controlling warnings on a per-identifier basis, such as this GHC issue suggesting an {-# ADOPT #-} pragma for suppressing orphan instance–related warnings. By response to this is usually of the form: instead of concocting new pragmas for each warning we wish to suppress, why not have a general mechanism that could work for any warning by delimiting regions of code that should have the warning disabled? This is the subject of GHC issue #602, and while no one has yet proposed a concrete design for how this would look, I think there is a lot of merit in considering this.

Granted, AllowAmbiguousTypes isn't really about warnings per se, so it's in a slightly different territory. But at the same time, I do think it would be worthwhile to consider it as a possible way to achieve the end goal of enabling ambiguity in controlled bursts. For instance, this proposal states that:

There is to be a pragma {-# AMBIGUOUS #-}, to appear immediately after the :: of a function or method definition's signature (so before the type). Not applicable for term type annotations beginning ::, nor for pattern signatures.

It is worth noting that this specification prevents the use of {-# AMBIGUOUS #-} in certain places where ambiguity can bite, such as in the following code:

type family F a

g (x :: F a) = x

Compiling this without AllowAmbiguousTypes will yield:

    • Couldn't match type ‘F a0’ with ‘F a’
      Expected type: F a -> F a
        Actual type: F a0 -> F a0
      NB: ‘F’ is a non-injective type family
      The type variable ‘a0’ is ambiguous
    • In the ambiguity check for the inferred type for ‘g’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        g :: forall a. F a -> F a
  |
7 | g (x :: F a) = x
  | ^^^^^^^^^^^^^^^^

However, since g does not have a type signature, one has no way of marking it as ambiguous with an {-# AMBIGUOUS #-} pragma. Of course, one might argue that having to write an explicit type signature is the price one must pay for using {-# AMBIGUOUS #-}. Fair enough, although there are other places where ambiguity can creep in that are less obvious how to fix:

  1. Instance declarations, as in the following example:

    data Option a
    class C a
    instance C (F b) => C (Option a)

    There is no :: in an instance declaration, so (as far I can tell) it cannot be combined with an {-# AMBIGUOUS #-} pragma.

  2. Data declarations, such as data T :: F a -> Type (example taken from this issue) don't seem to be compatible with {-# AMBIGUOUS #-} pragmas. This would be possible if we had top-level kind signatures, I suppose, but until they are implemented, this poses a challenge.


Having written all of this up, I realize that it's somewhat unfair to oppose this proposal (which, by all accounts, is very well motivated and clear in its scope) on the grounds that it should be superseded by a more ambitious proposal without a clear design. In that case, I suppose I could live with my idea simply being mentioned as an alternative. If that is what is decided, however, I would like to see the proposal amended to mention the following:

  1. A mention of places where {-# AMBIGUOUS #-} pragmas can not be used (e.g., functions without top-level signatures, instance declarations, etc.), and
  2. For each such place, a justification for why it is acceptable not to allow {-# AMBIGUOUS #-} pragmas there.

@AntC2
Copy link
Contributor Author

AntC2 commented May 25, 2019

Thanks @RyanGlScott, re your specific point

It is worth noting that this specification prevents the use of {-# AMBIGUOUS #-} in certain places ... [pattern signatures]

Yes, @goldfirere said the same. That's exposing my paucity of imagining how devious @pplicationists can be. I wanted the proposal to be narrowly scoped to start with. So I've left the proposal as written for a while to see other feedback. But I'm happy to change it to: {-# AMBIGUOUS #-} can appear wherever :: can appear.

I wanted to narrow the scope because there's many causes/places GHC says The type variable ‘a0’ is ambiguous. By no means all of them can be suppressed with AllowAmbiguousTypes.

As audience for this change I have in mind not GHC HQ/the people who typically respond to these proposals, but the people who ask questions on StackOverflow/are confused and hoodwinked by GHC saying To defer the ambiguity check to use sites, enable AllowAmbiguousTypes/can't discriminate when that's sensible vs when it'll just make for more trouble.

The sort of people who say "I'm far from an expert on type inference, so I can't really offer a
better inference algorithm that would avoid this problem. The best you can do, as far as I can tell, is to enable AllowAmbiguousTypes and hope for the best."
. A compiler really shouldn't leave people in the lurch to 'hope for the best'. Or ponder "If we AllowAmbiguousTypes, then the definition of Q3 is accepted, but
q3 fails with a similar error, complaining that GHC cannot match a with Bool because it is "untouchable". This seems confusing--" [same thread at GHC HQ]

As to your more general (and frankly rather ineffable point) about "delimiting regions of code" ... Thanks for drawing attention to #602. I've put a response on there, in general (but not ineffably general) terms.

Granted, AllowAmbiguousTypes isn't really about warnings per se, ...

That's right. It's suppressing what would otherwise be a rejection/error, not a warning. Furthermore, it exhibits what I might call 'action at a distance': we are in effect not suppressing an error check at the definition site so much as mandating @pplication at the usage site -- which might be in another module, certainly not in the "regions of code" where the ambiguous definition appears.

The more I thought about #602, the more it seemed to me there were plenty of examples of this 'action at a distance' such that "regions of code" is not a helpful delimitation. (Try arriving at a sensible delimitation of overlapping instances in terms of "regions of code".)

Whether or not something more effable can be rescued from #602, currently it seems to me poorly argued, unsupported by use cases, and just plain wrong as stated. I see no merit. Also AllowAmbiguousTypes -- and particularly GHC's current recklessly suggesting it -- is doing clear harm and confusion currently. (See my recent comments on proposal #148 for concrete examples.) Waiting for #602 (a decade so far, and still counting) would be a clear case of making the (arguably) perfect the enemy of saving continued harm.

@AntC2
Copy link
Contributor Author

AntC2 commented May 25, 2019

Ah, to sweep up a couple of @RyanGlScott's other examples (seeking feedback whether this makes sense)

Instance declarations, as in the following example:

   data Option a
   class C a
   instance C (F b) => C (Option a)

There is no :: in an instance declaration,

With -XInstanceSigs there is, so assuming class C has a method

class C a  where methC :: blah
instance C (F b) => C (Option a)  where
  methC :: {-# AMBIGUOUS #-} C (F b) => ... (Option a) ...

? If C doesn't have a method, it can't appear in @pplications, only constraints/signatures. What would be a use case? Should that instance be accepted anyway?

Following the "wherever :: can appear" new rule:

data T :: {-# AMBIGUOUS #-} F a -> Type

(Whatever it is you're trying to do with T.)

functions without top-level signatures,

Sorry, don't follow: if there's no signature, there can't be a tyvar to be ambiguous. (If the function's type is given via pattern signatures/term annotations, the "wherever :: can appear" new rule would cover?)

@AntC2
Copy link
Contributor Author

AntC2 commented May 31, 2019

Thanks @goldfirere , @RyanGlScott. I've incorp'd your feedback as follows:

  • {-# AMBIGUOUS #-} to be valid following any :: -- even where such an ambiguous signature only makes sense with future features (such as type-lambda) or with kind @pplications. (The argument being such sites are today allowed to be ambiguous with AllowAmbiguousTypes. See examples at point 1.)

  • But not for other inferred types. That is, you need to put an explicit sig for toplevel functions or -XInstanceSigs.

  • Rejected the possibility of a {-# NO[T_]AMBIGUOUS #-} pragma: that would interfere with the long-term goal of deprecating AllowAmbiguousTypes.

  • {-# AMBIGUOUS #-} to suppress warning messages for specific sigs that would otherwise appear with -XAllowAmbiguousTypes and -Wambiguous-types.

I'm surprised feedback on this has dried up so quickly. I'll be ready to pass for committee consideration within a couple of days, unless there's violent disagreement. (At that point, I'll make more executive decisions re the 'Unresolved questions')

@goldfirere
Copy link
Contributor

Thanks for this updates. I'm in support.

@nomeata nomeata force-pushed the master branch 2 times, most recently from 6b33e58 to e7fdbc7 Compare June 11, 2019 12:04
@AntC2
Copy link
Contributor Author

AntC2 commented Jun 30, 2019

Hi @nomeata, please submit this for Committee discussion.

No further feedback received on the 'Unresolved questions', so I've frozen in a resolution for each, but left them visible for discussion. No strong objections from anybody, but OTOH very few explicitly supporting.

There was another example on StackOverflow this month of somebody writing a class with an ambiguous method signature; getting the rejection message blah blah, enable AllowAmbiguousTypes; and consequently getting a landslide of "confusing to me" ambiguity rejections at usage sites; then only after that coming to ask what was ambiguous.

@nomeata nomeata added the Pending shepherd recommendation The shepherd needs to evaluate the proposal and make a recommendataion label Jun 30, 2019
@goldfirere
Copy link
Contributor

I will be shepherding this proposal. Before making my recommendation (which is expected to be "accept"), I would like @AntC2 to clarify a few issues:

  • The migration path. That section of the proposal appears to mix actions the user is expected to take and actions that GHC developers are supposed to take. Could you clarify this?

  • What bin of warnings with the new warning go in? It could be any of on-by-default, -W, -Wall, -Wcompat, I think.

Thanks!

@AntC2
Copy link
Contributor Author

AntC2 commented Jul 2, 2019

Thanks @goldfirere, before I clarify the proposal could I clarify ...

  • This proposal is not aiming to start a path towards deprecating module-wide AllowAmbiguousTypes. (I say that's out of scope/a possible future.) That's because the feedback (especially from you) was that some people expect all/most signatures to be ambiguous, then festooning everything with {-# AMBIGUOUS #-} would be onerous and distracting. I think we need some road miles with the per-sig pragma to find a consensus. Then most of the 'migration path' is how to migrate a module with a few ambiguous signatures towards using per-sig pragmas. I'll split that 'migration path' section to reflect more strongly what's future scope.

  • In that case re bin of warnings, I think -Wcompat would not be appropriate until/unless we're on a deprecation path. (I presume it's relatively easy to switch a warning into a different bin later?) -Wambiguous-types only makes sense if LANGUAGE AllowAmbiguousTypes is set. If not allowed, ambiguous types get rejected. It would be daft to get both a rejection and a warning for a signature. Then on-by-default would be too annoying. I'm thinking the appropriate bin is -W "normal warnings", on grounds an ambiguous signature is outside Haskell 2010.

I'll clarify the wording to say that if Wambiguous-types is on but not AllowAmbiguousTypes, don't issue warnings/do issue rejections.

@goldfirere
Copy link
Contributor

It sounds like you've clarified on your own. :) You're zeroing in on exactly the changes I wanted. Thanks!

…gration path for modules vs future for GHC
@AntC2
Copy link
Contributor Author

AntC2 commented Jul 4, 2019

OK done. Over to @goldfirere ...

@goldfirere
Copy link
Contributor

This proposal will now be discussed by the committee. We welcome all authors to join the discussion, but kindly ask others to refrain from posting.

Summary

  1. Proposed is to add a new {-# AMBIGUOUS #-} pragma, usable in prefix wherever types appear. When this pragma is in place, the ambiguity check is skipped.

  2. Additionally, proposed is to add a warning -Wambiguous-types, part of -W, that triggers whenever an ambiguous type is accepted without the pragma (this can happen only with -XAllowAmbiguousTypes).

  3. The proposal also suggests rewording of the current suggestion to add -XAllowAmbiguousTypes, informing the user that they probably want to fix their type, not enable an extension.

Recommendation

I recommend acceptance. I am wholly in support of points (1) and (3) above, and weakly in support of (2). Module-wide language settings should either be impossible (or very difficult) to specify locally, or should be agreed to be benign. AllowAmbiguousTypes is neither: a user should know precisely where they're writing ambiguous types (users will have to use type application, after all), and it is an advanced feature with the capability of causing obscure type errors in clients.

My lukewarm support of (2) is that it's a bit annoying for users to have to specify both -XAllowAmbiguousTypes and {-# AMBIGUOUS #-}. I do see the motivation to do so, however. And users who don't wish to do this can just -XAllowAmbiguousTypes -Wno-ambiguous-types, so it's not so terrible. In the end, I do think obviating -XAllowAmbiguousTypes would be a good thing, which is why I have any support at all for (2).

I look forward to your discussion, committee. As usual, silence will be taken as agreement.

@goldfirere goldfirere removed the Pending shepherd recommendation The shepherd needs to evaluate the proposal and make a recommendataion label Jul 5, 2019
@AntC2
Copy link
Contributor Author

AntC2 commented Jul 31, 2019

Done. See Changelog.

Another month, another confused learner bamboozled by GHC's error message into unnecessary AllowAmbiguousTypes. With added non-injective Type Families red herring.

@gridaphobe
Copy link
Contributor

Sorry to be so late to the discussion. I also like the direction of this proposal, but have a couple questions about the specifics.

  1. The proposal introduces {-# AMBIGUOUS -#} as a positional pragma, but in most cases it's applied to types that are being ascribed to some identifier. This makes it seem like a good candidate for a named pragma. The exceptions are expressions and type applications. Richard is suggesting we remove the ambiguity check from type applications entirely, and I haven't seen an argument for allowing ambiguous types in ascriptions to arbitrary expressions. Does anyone have an example where this is necessary?

  2. The -Wambiguous-types warning seems unnecessary to me. It's main purpose appears to be to provide a migration path for code currently using -XAllowAmbiguousTypes, where the warning tells you which signatures must be annotated before you remove the global extension. But it would be easier to just remove the extension first, and then add the pragma wherever a type error appears!

@AntC2
Copy link
Contributor Author

AntC2 commented Aug 2, 2019

@gridaphobe 1. ... I haven't seen an argument for allowing ambiguous types in ascriptions to arbitrary expressions.

The proposal is to touch everywhere that's currently touched by AllowAmbiguousTypes. Currently this is rejected unless you set that extension

x = undefined :: (Num a, Ord b) => a

(To be precise, with AllowAmbiguousTypes set, that's rejected for other reasons ;-). And introducing TypeApplications to fix them seems to kick out some weird kind errors GHC 8.4.4.)

You might want to argue ambiguous expression ascriptions shouldn't be allowed. Take that up with the original author of the extension. That'd be a different proposal (because it risks breaking currently accepted code).

  1. The -Wambiguous-types warning seems unnecessary to me. ...

@simonpj gets it. You seem to be of the brutalist school of software evolution.

@gridaphobe
Copy link
Contributor

gridaphobe commented Aug 2, 2019 via email

@gridaphobe
Copy link
Contributor

gridaphobe commented Aug 2, 2019 via email

@AntC2
Copy link
Contributor Author

AntC2 commented Aug 2, 2019

@gridaphobe AllowAmbiguousTypes allows any user-written type in the module to be ambiguous. The whole point of this proposal is that that’s painting with far too large a brush.

I think you've misunderstood the motivation. It'd be informative to read through the thread I link to in July's cafe. The major point of this proposal is that GHC suggests switching on AllowAmbiguousTypes when it's not needed (as in that case). With that switched on, that it exacerbates the situation by indeed painting with far too large a brush is a consequence, and not "the whole point".

So I think this is a natural time for us to ask where desired ambiguity arises.

I've answered that: the ambiguity is not desired. It's bogus, as a consequence of GHC's mis-leading message.

The main instance I’m aware of is in class methods that are meant to be @pplied to the type rather than a value. ...

The cause I'm addressing is novice users who neither need nor understand ambiguous types. Their use case is not "meant to" anything. Their use case is because they're too trusting of GHC suggestions.

If we disallow ambiguity in ascriptions to expressions ...

That would be a different proposal, that potentially breaks currently accepted code. By all means write it yourself. If the Committee judges it would be preferable to this proposal, I'll abandon this.

The people putting {-# AMBIGUOUS #-} pragmas are non-novice, who presumably know what an ambiguous signature is about, and know they're going to need type applications, and presumably also know that for expressions they might as well "resolve it in the ascribed type".

(and type applications)

No this proposal (Point 7) is not disallowing ambiguity in type applications. To the contrary, it's allowing ambiguity without any rejection/extension set/nor pragma. So it's the opposite way round to your counter-proposal for expression signatures.

@gridaphobe
Copy link
Contributor

@gridaphobe AllowAmbiguousTypes allows any user-written type in the module to be ambiguous. The whole point of this proposal is that that’s painting with far too large a brush.

I think you've misunderstood the motivation. It'd be informative to read through the thread I link to in July's cafe. The major point of this proposal is that GHC suggests switching on AllowAmbiguousTypes when it's not needed (as in that case). With that switched on, that it exacerbates the situation by indeed painting with far too large a brush is a consequence, and not "the whole point".

It seems to me that the motivation is to aid both novice and expert users. If the only issue were that GHC makes a bad suggestion, the proposal could be as simple as "stop suggesting that users enable AllowAmbiguousTypes". But this proposal takes the time (rightly!) to shrink the scope of the allowed ambiguity, which is presumably there to aid expert users who know that they want the ambiguous signature, but don't want other ambiguous signatures sneaking in accidentally. So I think my question about where we might reasonably want an ambiguous signature stands.

(and type applications)

No this proposal (Point 7) is not disallowing ambiguity in type applications. To the contrary, it's allowing ambiguity without any rejection/extension set/nor pragma. So it's the opposite way round to your counter-proposal for expression signatures.

You're quite right on that point, I mispoke.

@AntC2
Copy link
Contributor Author

AntC2 commented Aug 3, 2019

@gridaphobe I think my question about where we might reasonably want an ambiguous signature stands.

I was re-stating my motivation as wanting to avoid/minimise the scope of the ambiguity. I'm not claiming to understand why someone wants ambiguity. I manage to write all my code without it, and without type applications. So in the absence of someone chipping in here on use cases, I see three scenarios:

  1. Ascribing ambiguous types at an expression is dumb; and nobody's doing it. So this proposal has no effect.

  2. Ascribing ambiguous types at an expression is dumb and a mis-feature; but somebody's doing it because you can currently. (Or they've switched on module wide AllowAmbiguousTypes and given an ambiguous expression signature unintentionally/because GHC won't tell them.) Then this proposal helps by identifying the location, and gets them to think whether it's needed. They can change the signature; then no need to be {-# AMBIGUOUS #-}

  3. Ascribing ambiguous types at an expression has sensible use cases. Then this proposal allows marking as {-# AMIBGUOUS #-}. But removing the ability breaks the code.

What worries me about 'named pragmas' -- where do I go to find out what that means exactly? Is that an existing feature or a pipe-dream? Is:

  • Amongst the names with ambiguous signatures are datatype field names.
  • Those are not uniquely identified now that we have DuplicateRecordFields.
  • There's also the longer-standing DisambiguateRecordFields and NamedFieldPuns.

Those combined means it not necessarily easy to get from a field name to its definition to some pragma about the name.

Addit: if by 'named pragmas' you mean something along the lines of DEPRECATED or WARNING, note this from the Users Guide

You can only attach to entities declared at top level in the module being compiled, and you can only use unqualified names in the list of entities. A capitalised name, such as T refers to either the type constructor T or the data constructor T, or both if both are in scope.

Then consider you might have AMBIGUOUS signatures for non-top-level names; for a data constructor but not same-named type constructor; for one field name but not another same-named.

[end of Addit]

Then how do we persuade users to move from the module-wide AllowAmbiguousTypes -- which, BTW, some people seem to advocate switching on globally -- to a complicated name-specific pragma? Putting a pragma slap against the signature that GHC is highlighting seems a modest burden.

Could I suggest the applicability of ambiguous expression signatures gets revisited as and when AllowAmbiguousTypes gets considered for deprecating (outside scope of this proposal)?

@gridaphobe
Copy link
Contributor

Addit: if by 'named pragmas' you mean something along the lines of DEPRECATED or WARNING

Yes, that's precisely what I meant, sorry for the confusion. Concretely, I would like to be able to write

class Foo a where
  foo :: a -> a
  bar :: Int
  {-# AMBIGUOUS bar #-}

note this from the Users Guide

You can only attach to entities declared at top level in the module being compiled, and you can only use unqualified names in the list of entities. A capitalised name, such as T refers to either the type constructor T or the data constructor T, or both if both are in scope.

Then consider you might have AMBIGUOUS signatures for non-top-level names; for a data constructor but not same-named type constructor; for one field name but not another same-named.

These are all places where AllowAmbiguousTypes would currently accept ambiguity, but I also find them unconvincing, or we have ways of dealing with them. It's not clear why you would have a local binder with an ambiguous signature, for the same reason as for arbitrary expressions. The use-site is local, so why not just make the signature unambiguous? There are also other named pragmas like INLINE that can apply to local binders, so there would be precedent for doing the same with AMBIGUOUS if necessary. For constructors there's an accepted proposal that enables disambiguating which constructor a pragma should apply to. For duplicate fields I don't think we have an existing way to disambiguate them, though I'm also perplexed as to why you would want ambiguity there.

I guess my takeaway from this is that it would be nice to have a better understanding of where Haskellers desire ambiguous types. The only case I'm aware of is typeclass methods like bar above (which feels like the Haskell analogue of a static method from object-oriented languages). What are other scenarios where ambiguity is useful? An answer would help us better target the extension/pragma to the desired usecases.

That being said, I don't mean to stand in the way of this proposal. I think it would be a marked improvement over the status quo. I just saw a potential opportunity to improve it further.

@AntC2
Copy link
Contributor Author

AntC2 commented Aug 4, 2019

@gridaphobe it would be nice to have a better understanding of where Haskellers desire ambiguous types.

I'm happy to say I have no understanding of where Haskellers desire ambiguous types. I have no such desire. And I observe (most) novices don't either. So I'm trying to avoid the harm GHC is currently doing. (Most of your first para has gone over my head. I'm not trying to redesign the ambiguity in signatures. Wouldn't those considerations have been apparent to the originator?)

I don't mean to stand in the way of this proposal.

Good, because so far it's seemed you want to make the perfect the enemy of the vastly better. I'm not going to hang around for other proposals that claim to disambiguate pragmas; but seem to require code beakages; and seem to have gone nowhere for nearly 2 years; and anyway haven't anticipated a generalised way to attach pragmas to names.

@goldfirere
Copy link
Contributor

The underlying question @gridaphobe is asking is (I believe -- sorry if I'm putting the wrong words into your mouth): Is a type ambiguous, or is an identifier ambiguous? I think this is a good question. If a type is ambiguous, then the syntax proposed here is the right one. If an identifier is ambiguous, then a named pragma might be better.

I'm in favor of calling a type ambiguous. The identifier can sometimes be a bit hard to pin down (examples: what do we do in an InstanceSig? do we have to label all fields of an ambiguous record constructor as ambiguous if the constructor type is?) and I think a correct program can include an ambiguous expression signature.

Given the long length of time this has been under consideration and the considerable silence-is-agreement among the committee, I'm going to make a deadline of Friday, at which point I will accept (and acknowledging @gridaphobe's statement that he wishes not to block acceptance).

/remind me Friday to accept this

@reminders-prs
Copy link

reminders-prs bot commented Aug 5, 2019

@goldfirere set a reminder for Aug 9th 2019

Copy link
Contributor

@aspiwack aspiwack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is cleared up for me. Thanks.

proposals/AmbiguousType-pragma.rst Outdated Show resolved Hide resolved
@reminders-prs reminders-prs bot removed the reminder label Aug 9, 2019
@reminders-prs
Copy link

reminders-prs bot commented Aug 9, 2019

👋 @goldfirere, accept this

@goldfirere
Copy link
Contributor

I hereby accept. @nomeata, I believe you do the next steps?

@goldfirere goldfirere added Accepted The committee has decided to accept the proposal and removed Pending committee review The committee needs to evaluate the proposal and make a decision labels Aug 14, 2019
@nomeata nomeata merged commit 08abf77 into ghc-proposals:master Aug 15, 2019
nomeata added a commit that referenced this pull request Aug 15, 2019
@nomeata
Copy link
Contributor

nomeata commented Aug 15, 2019

Done, although you should/could notify the mailing list about the status change.

@AntC2
Copy link
Contributor Author

AntC2 commented Oct 13, 2021

This proposal's idea referenced in the proposed syntax for Modifiers, next-to-last bullet. I agree it makes sense to use a modifier rather than a pragma. Compare:

norm :: %Ambiguous forall a. (Read a, Show a) => String -> String
norm %Ambiguous :: forall a. (Read a, Show a) => String -> String          -- ? see below re scoping

norm :: {-# AMBIGUOUS #-} forall a. (Read a, Show a) => String -> String   -- as proposed here

Only I'm a little worried over scoping: Modifiers are proposed to scope only over the immediately following lexical item they're prefixed to (minimal munch), whereas {-# AMBIGUOUS #-} as proposed here is to scope over the whole signature. It would be annoying to need extra parens around a signature just to stretch the scope of %Ambiguous. So put %Ambiguous before the ::, per second illustration?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Accepted The committee has decided to accept the proposal
Development

Successfully merging this pull request may close these issues.

None yet