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
NoFallibleDo proposal #319
Conversation
Interesting idea. Not sure if the motivation is strong enough, but perhaps it is. I have a few technical questions:
|
I would say it works well only with
I believe today Monad comprehensions and List Comprehensions are implemented differently with respect warnings, so there is already some precedent for this. I think we kept
If we think in backpack terms, (or "meta type class" if one prefers), #216 is about providing the module instantiation (resp. class == meta instance), whereas this is about choosing the signature (resp. meta class). But frankly, I don't see see this as about making a more flexible do notation, but about fixing a wart in the language. [Disclaimer I might be more extreme than Cale and others.] As the question often goes today, if we didn't have
Would we accept this half-baked sugar? Absolutely not, I'd think. I'm all for having composable, resuable, insert good adjective syntactic sugar for various mathematical structures---way beyond restricted Monads. But I don't think we can get there incrementally based of our current legacy designs---I think it's going to take some meta-programming research not n-year More |
Yeah, if #216 gets done, then the implementation of fail could be redirected with a qualified do-block in those cases which are presently problematic. I think the explicit qualification is unlikely to be too much of an issue (though I'm not sure, some people might disagree), and the fact that in this case, we want the standard meanings for everything apart from I'm looking at this entire proposal as a stopgap measure anyway, so if something that grants more general control over the meaning of the syntax ends up landing in GHC soonish, it should both be easy for MIRI's fork to switch to it, and solve the same problem for everyone else easily enough, so I wouldn't personally have any issue with it.
Indeed, you wouldn't want to use NoFallibleDo in modules where the behaviour of
That's actually one of the hanging threads in our implementation at the moment, but I'd probably lean toward the change just having no impact on the translation of MonadComprehensions. I agree that if you are using MonadComprehensions, you probably do want some sort of nicer treatment of failed pattern matches, and this would even leave that syntax open for use in case you discovered need for, I don't know, StateT s [] with some implicit backtracking on pattern match failure in the midst of code that did use NoFallibleDo. |
This text seems swapped around, to me. Line numbers are useful only in the non-
Do you mean in GHC? Or in your fork?
I think a proposal to put That last observation leads me to suggest a much simpler workaround: lazy patterns. That is, instead of saying foo = do
A z <- blah
... say foo = do
~(A z) <- blah
...
What do you think of this workaround? |
@goldfirere lazy patterns don't work with GADTs |
Silly me. Of course. I've started work on a paper to address that (among other issues), even. But it actually suggests an alternative design. Instead of marking a whole module as treating pattern-matches in foo = do
{-# COMPLETE #-} A z <- blah
... I'm abusing the idea of How common are these matches in practice? |
Yes indeed, sorry about that.
I mean in GHC.
Well to be clear, I always intend to use at least foo = do
{-# COMPLETE #-} A z <- blah
... I suppose this works, but this seems awfully heavyweight, and "anti huffman". In general, complete pattern matches definitely should be norm and path of least resistance, while the incomplete ones should be the weird ones with the many loud capital letters. Kinda like how C having I never ever want MonadFail, so I am fine to just doing foo = do
before...
n <- blah >>= \case
A n -> pure n
_ -> manualErrorHandling
rest... or foo = do
before...
n <- blah >>= \case
A n -> do
rest...
_ -> manualErrorHandling If there is any sugar I'd want, it would be some way to mark a "default case" to avoid repeating bound variables or rightward drift. Something like: foo = do
before...
case A n <- blah of
_ -> manualErrorHandling
rest... I'm perfectly happy to call |
I appreciate your observation about incentivizing good behavior. But I'm thrown off the scent now: if you're happy to do the manual error-handling yourself, why do you want this new extension? I'm also confused now about I was thinking, briefly, of suggesting that |
@goldfirere I'm only speculating, but I'm quite sure Obsidian folks have problems with newtype Some tag = UnsafeSome (tag Any)
{-# COMPLETE Some #-}
pattern Some :: tag a -> Some tag
pattern Some x <- UnsafeSome x
where Some x = UnsafeSome ((unsafeCoerce :: tag a -> tag Any) x) See https://gitlab.haskell.org/ghc/ghc/issues/1965 Where with GADT variant one could write do Some x <- foo
Some y <- bar
doSomethingWith x y and refinement would work, with above cheat-performant implementation of -- this is in fact safe.
withSome :: Some tag -> (forall a. tag a -> b) -> b
withSome (UnsafeSome thing) some = some (unsafeCoerce thing)
withSomeM :: Monad m => m (Some tag) -> (forall a. tag a -> m r) -> m r
withSomeM m k = m >>= \s -> withSome s k so the code would look like do withSomeM foo $ \x ->
withSomeM bar $ \y ->
doSomethingWith x y i.e. no benefit of Personally I think it is not that bad, that I would rather use TL;DR this is the gist of https://gitlab.haskell.org/ghc/ghc/issues/15681 The proposal would benefit from more clearer and "real" motivational examples. Also from links to GHC issues it allows to work around. |
The motivation for our work on this came from MIRI hiring us to come up with and implement some solution to the issues mentioned in the Motivation section (and on the GHC issue that @phadej already linked to), rather than anything internal to Obsidian Systems, so for this one I don't actually have practical examples handy from my own usage of the language. Despite using I entirely agree that practical examples would be nice to have. It also would have been good motivation for our role in this, but the code which induced them to pay us to make this change presumably isn't open source. Perhaps we can get someone from over there to comment on this proposal in any case though. |
Ah so we have a rather buried sentence on this we should bring to the fore:
Here's my train of thought:
So one could disparage the |
One idea that this conversation has led me to: imagine that #216 is adopted. We might like to implement fail via throw in some module so that QualifiedDo syntax could basically implement this proposal here. This would however leave out the potentially useful warning when GHC failed to verify that your pattern matches were complete. But why should it have to? We have custom type errors, but why not custom warnings? If, by way of analogy with the existing
we had
which is a constraint that is trivially solvable by printing a warning message, then we could have:
alongside a re-export of ordinary (>>=), and that would then behave quite similarly to this proposal, only at the expression-level rather than the module-level. |
To be clear, #216 on it's own is not enough, because once again you are at the mercy of the compiler as to when |
@Ericson2314 Um, but you're at the mercy of the compiler as to when the case expression involving throw gets generated with this proposal as well... it's just that we've selected an expression that has less of an impact on the type signature, as M.fail might do as well. |
And with #216 you can say |
@goldfirere there are some subtleties in that only post type checking can we properly decide what's fallible, so we have to make sure that the the errors can be freely added and remove without influencing the types of the adjacent time. The old #216 however, which is essentially qualified rebindable syntax IIUC, certainly doesn't. Maybe certain instances do like Cale's example, but modifying the desugaring based on whether the user-passed identifers pass a sort of subsumption check sounds quite complex. |
I think I might go over the motivation and alternatives to clear up some of these things from the discussion; each change in the proposal is rather simple, but the sum total of their interactions certainly isn't. |
I should clarify: When I'm thinking that #216 solves this problem, I mean to have it in concert with an ability to report warnings for incomplete pattern matches in Here's an idea: what if we have something like data NoFail m = NoFail { bind :: forall a b. m a -> (a -> m b) -> m b, then :: ..., fail :: TypeError "incomplete pattern match" => String -> m a } I'm not sure if the current setup around |
I still think trying to use some sort of magic constraint is disingenuous. There's two routes:
In both cases, the "constraint" precisely never is handled like other constraints in any meaningful way. I think it would be more honest to just have a magic term: "blow up if this makes throw the desugarer". I know I've wanted a similar "blow up if this isn't optimized away" in the past. |
Still, I think that given most Haskellers are against "syntactically silent" partiality, the sort of "grand plan" I'd hope for is:
I think this because
|
I'm afraid I don't understand #319 (comment) without examples. :( The "new sugar" is well approximated by e.g. foo = do
x <- blah
y <- blah2 x >>= \case
pat1 -> ...
pat2 -> ...
... That's a bit ugly, granted, but I'm not sure it's worth sugaring over. Happy to consider making pattern-match incompleteness a hard error. I have to say it sounds more and more that what you want is just to stop GHC from accepting incomplete pattern matches in |
I am not sure what example to write, since this deals with the internals of the compiler and it's many intermediate representations rather than the surface syntax? But I could point in our PR out the key points in GHC whose behavior determines which flavor of this proposal or the discussion's proposals is implemented. Alternatively, I'd be happy to hop on IRC and just explain and reference those spots it in real time. (I think the latter might be more efficient, if you don't mind.)
I edited in an "(Optional)" for "new sugar" part. I do like it, but for the sake of this argument it is a palliative for not having the fail desugar, not essential.
Yes that is true. All that business with the We could "jump ahead" in the grand plan to just banning them outright with |
I remain unconvinced -- but perhaps I've been too loud. Let's hear from other voices, while I take a step back from this proposal. Happy to take another look if this is revised or submitted to the committee. |
no-fallible-do: Go into more detail with the problem
- `MonadFail` being bad - Why `PatternMatchFail`. Also tentatively clarify comprehension behavior in motivation, this might be changed based on the discussion and is not very constrained by what we are doing.
…stion no-fallible-do: Expand a bit on two things
All I meant is it appears we must have either #327 or
:) Agree completely. |
Sorry, I just came back from vacation, and I'm getting back to my sepherding tasks. Honestly, I think that is too complicated, since there's a very subtle interaction between default modes (which can be enabled via Cabal files and at the top of the module) and possible modifiers. Furthermore, we are somehow overriding the usual behavior of I also got the feeling that the proponents are too keen on breaking backwards compatibility. However, I think the proposal is not strong enough on that respect; and this doesn't seem (in my very subjective view) something the community has complained too much about. |
I'm not sure what to do then, the above seems like a fundamental impasse
To me, this proposal feels like the sort of unglamorous maintaincence work that isn't super noticable or widely desired, but is nonetheless important to keep things working smoothly (in this case reining in accumulated complexity). We have a situation where small decisions over time have created something very weird and out of step with the our general principals — and ad-hoc approximation of a type directed desugaring. In fixing this we affirm our values and reclaim our complexity budget for other things in this area. |
May I ask why that is the case? If Genuine questions popping into my head:
|
I do admit the strictly speaking, the implicit |
@cgibbard @Ericson2314 I've realized that there has been some time since we last discussed about this. Is the linked version the latest one? Do you want to re-apply for Committee review? |
Thanks @sgraf812 for the idea.
We could do another round, but I am not sure we've meaningfully moved on form the old impasse. If I understand correctly:
|
@serras has called for a vote on this (thanks, @serras), and I have voted to reject, for the following reasons:
I'm sympathetic to the goals here, but I think there are more compelling ways to achieve those goals than what's proposed here. |
|
Dear authors: the Committee has agreed to reject this proposal. Several of us see #327 as a better solution to this problem, so we encourage collaboration in that regard. |
I...didn't know this was in the committee review state? If #327 got finished, my plan was return to this, as I viewed it as a complementary not an alternative. I thought the impasse was mainly #327 vs modifiers vs punt, so doing #327 would make this potentially viable. Would it be possible to reopen the proposal in some fashion if #327 we're accepted? (Reading the committee emails I see case C x1 … xn <- u of {} as the reason #327 could do this on it's own. I didn't think of that before, and seeing it now, I do see why #327 could, strictly speaking, serve as an alternative. But, I doubt that is practical given how much more verbose it is. A core philosophical point with this stuff is that total binds are the common case, and therefore they deserve the concise syntax to steer people in the right direction.) |
Ah in particular, @serras asked
And my reply not clear. I meant to say "I think we are still at this impasse: ..., let's hold off until something changes", but what came across is probably more "grrr I have nothing more to say, let's just go to a vote". |
Sorry I only wrote the "this is the impasse" part and didn't answer the second part, and sorry I missed the earlier calling for a vote. |
Apologies if there was a miscommunication. The rejection of course applies to this proposal, so if something changes (the proposed change, or other related assumptions that led to the rejection), we can discuss it of course again. |
@Ericson2314 sorry on my side, I indeed interpreted your comment as marking the proposal as ready for another round of review. As @nomeata says, we are open to discussing the proposal again. |
Thanks for the replies. I suppose I am also curious on the criteria for proposals. Reading the emails from last month, it seems a big issue was that it was too late to change the nature of That one is still not implemented, but living on as #448 where it is important to help put some of the principles in context. I might add that while My experience with #285 taught me that there was some appetite or at least allowance for "restriction of Haskell" extensions that might never be able to become the default, but would be very useful for groups of people trying to code in a certain style. And in general, the entire experience with #378 that eventually went through led me to believe there was some willingness allow certain "vanguard" groups to rethink old parts of Haskell to explore what might be the best ideom. Without sounding too bitter, is the steering committee now of a new opinion on these things? Or does these seem different than those in some key ways I am missing? |
If it helps, I am not just hoping to relitigate proposals like this one. I would like to keep on writing |
Talking just on my name (not on behalf of the Committee), there were several reasons that weighted in.
|
Thanks for responding to me on these things.
OK this make sense.
Oh, I somehow did not realize that. Me too! hat is also part of my motivation. Quite on the contrary, the impression I got was you thought it was a fine thing to do.
How does this square with the above for you? For me, it is precisely because I view
The pattern match fail exception was written with (I am still conscientious of the incongruity of making just one thing in the language require complete patterns. And the complaint in #351 that pattern match exhaustiveness is too under-specified to be part of a language extension would equally apply here. But I think I have a path to making a new better specified version of |
Speaking for myself (like @serras, not for the committee), I have an appetite for more Maybe this would have been different for me if the proposal stated loudly that As stated, the motivation is to give programmers the option of how to treat a Do keep the proposals coming, though. I know it is frustrating to put time into these to have them rejected. I do think that, generally, you (@Ericson2314) and I want similar things for the language -- we just have to figure out how to get the design right. |
Yeah this sounds increasingly like there was mainly a messaging issue here where I just didn't write down the big subjective " That's actually reassuring! It leads me to think the "V2" I outlined above would at least spur a very different conversation. |
I'd say that's a misfeature in the Though, it is a feature in list comprehensions. P.S.: When I write |
This is a proposal to add an extension flag that enables/disables the use of
fail
in do-syntax, allowing it to be replaced with the use ofthrow
, especially in circumstances where MonadFail is inappropriate and the compiler is not yet smart enough to determine whether certain pattern matches can actually fail.Rendered