Skip to content

Implement QualifiedDo - #3373

Merged
natefaubion merged 7 commits into
purescript:masterfrom
pkamenarsky:qualified-do
Dec 30, 2018
Merged

Implement QualifiedDo#3373
natefaubion merged 7 commits into
purescript:masterfrom
pkamenarsky:qualified-do

Conversation

@pkamenarsky

Copy link
Copy Markdown
Contributor

Implements #3245.

Now, the following is possible:

IxMonad.purs:

module IxMonad where

class IxMonad m where
  pure ∷ forall a x. a -> m x x a
  bind ∷ forall a b x y z. m x y a -> (a -> m y z b) -> m x z b

Main.purs:

import IxMonad as I

test :: forall m a. I.IxMonad m => m a a String
test = I.do
  a <- I.pure "test"
  b <- I.pure "test"
  I.pure (a <> b)

instead of having to rebind bind explicitly.

There's no change in behaviour if do is left unqualified:

test :: forall m. Monad m => m String
test = do
  a <- pure "test"
  b <- pure "test"
  pure b

If there's interest I'll add support for ado as well.

@pkamenarsky

Copy link
Copy Markdown
Contributor Author

(Tests fail because tests/support/bower.json was referencing stale 0.12 branches. This has been fixed in the meanwhile, but not sure how to restart CI.)

@natefaubion

natefaubion commented Nov 29, 2018

Copy link
Copy Markdown
Contributor

I personally like this feature, as I think it makes rebindable-do actually useful. I don't know of anyone using the feature in earnest because the ergonomics are so terrible, so at best I consider rebindable-do a neutral side-effect of not having a hard dependency on Prelude. But I know the reception in the ticket was mixed. I think we should make a decision either way if this is something that we want to merge (now or eventually) rather than letting this continue to bit-rot.

@hdgarrood

Copy link
Copy Markdown
Contributor

@natefaubion: Yes, I agree. I'm a little hesitant for the same reasons I gave in #3245, but I'll admit that I don't remember finding myself in a position where I was wanting to use rebindable syntax like this, and in fact I always did view rebindable syntax as a neutral consequence of separating Prelude from the compiler, rather than a feature that I expected to get much use. But looking back at the ticket, I think the ergonomics argument makes sense. You and @garyb being in favour is sufficient for me to say sure, let's do it.

@pkamenarsky this is perhaps a bit late now, but you can trigger a new CI build by pushing an empty commit, or by rebasing your PR on master.

@hdgarrood

Copy link
Copy Markdown
Contributor

Additionally, if we are doing this, I think we should support ado as well.

@pkamenarsky

Copy link
Copy Markdown
Contributor Author

do works now, will try to finish ado in the next couple of days in order to get this ready for review.

@pkamenarsky

Copy link
Copy Markdown
Contributor Author

Ok, I think this might be ready for review 🎉

@maddie927

maddie927 commented Dec 16, 2018

Copy link
Copy Markdown

I’d love to use this in this react-basic branch! I’ve tried a few different approaches to scoping bind and it’s all pretty gross. It’s also harder to understand (both learning and debugging) than qualified do 🙂

In this example I had to move the inner do block out so they each have their own bind scope:

https://github.com/spicydonuts/purescript-react-basic/blob/hooks/examples/counter/src/Counter.purs

And in this one the “convenience” args I was trying we’re kind of useless for the helper function:

https://github.com/spicydonuts/purescript-react-basic/blob/hooks/examples/controlled-input/src/ControlledInput.purs

@natefaubion

Copy link
Copy Markdown
Contributor

I think the approach is straightforward. The only outstanding question I have is how this interacts with something like docs/pursuit. I know ado broke some stuff, and since this doesn't introduce additional constructors I think we should be OK, but I'd like @hdgarrood to weigh in.

@hdgarrood

Copy link
Copy Markdown
Contributor

This should be fine, I think, since a) the Expr type doesn't get serialized during publishing and b) the code to handle this is all happening inside the parser; there haven't been any changes to the type checker. I'm planning on working on a fix for #3414 which would make the whole docs generation process a fair bit more robust than it currently is anyway.

@hdgarrood

Copy link
Copy Markdown
Contributor

Well I mean there are changes in the desugaring too, but ado is already straight up broken with respect to docs, so this can't make it any worse.

@maddie927

Copy link
Copy Markdown

I gave this branch a try and it works! Counter, ControlledInput

One thing I noticed is that it seemed to work even without bind in my exports list. Not sure if that's intentional or good/bad. I'm also kind of unclear whether discard should be included.

@maddie927

Copy link
Copy Markdown

What are the next steps or blockers here?

@natefaubion

Copy link
Copy Markdown
Contributor

One thing I noticed is that it seemed to work even without bind in my exports list.

Can you explain what you meant by these comments?

@maddie927

Copy link
Copy Markdown

The module I defined top level bind in was using explicit exports. That export list did not include bind, but M.do still worked when imported as import M as M. When I changed the import to import M (bind) as M I got a compile error on the import.

, parseDo
, parseAdo
, P.try parseDo
, P.try parseAdo

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we want to add backtracking here. This will backtrack on any failure inside a do-block, which will result in poor errors. We only want to apply backtracking to the parsing of the do keyword, which means we need to move this into parseDo and parseAdo.

@pkamenarsky pkamenarsky Dec 27, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Interestingly, my intuition was that by using <|> the parser would try consuming the qualified keyword first and only if that fails try and parse the unqualified one:

(parseQualified (reserved "do") >>= pure ∘ getQual) <|> (reserved "do" *> pure Nothing)

However we still need to backtrack explicitly while parsing the qualified version, otherwise every qualified token is expected to be do/ado.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

<|> will only fallback to the next branch if you have wrapped in try or you have not consumed any tokens. parseQualified likely consumes the qualification, so when it does not get a do, it will not fallback to the next alternative unless you explicitly wrap it in try.

parseDo :: TokenParser Expr
parseDo = do
m <- parseQualified (reserved "do") >>= \(Qualified m _) -> pure m
m <- P.try (parseQualified (reserved "do") >>= pure . getQual) <|> (reserved "do" *> pure Nothing)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

>>= pure . k is just fmap.

@natefaubion

Copy link
Copy Markdown
Contributor

@spicydonuts I cannot reproduce that issue with this branch. If I don't export bind, I get the expected error. Can you reproduce it with a minimal example? Something would be very awry if that were the case since this desugaring pass is called before name resolution, so to the compiler it's no different than using the operators directly.

@maddie927

Copy link
Copy Markdown

I tried this here and it seems like it was probably just a build caching issue:

  1. Build
  2. Remove bind from the exports in src/React/Basic.purs
  3. Remove the explicit imports on the qualified import of React.Basic in src/React/Basic/Compat.purs (becomes import React.Basic as React)
  4. Build -- no errors
  5. rm -rf output
  6. Build -- Unknown value React.bind

@natefaubion

Copy link
Copy Markdown
Contributor

By "build", do you mean a full compiler build, or ide build?

@natefaubion

Copy link
Copy Markdown
Contributor

In any case, that doesn't sound like an issue with this PR specifically.

@maddie927

Copy link
Copy Markdown

Full, using pulp build for each of those builds. I'm not sure if incremental builds were happening in the background actually.. I'm using VSCode with the ide plugin but I haven't paid much attention to it for this because it can't parse the qualified do syntax.

@natefaubion

Copy link
Copy Markdown
Contributor

@spicydonuts I've tried, but I still can't reproduce an error calling purs compile directly. If you can reproduce it, please open a ticket. Otherwise I'm going to say it's probably IDE related. It doesn't sound like it's related to this feature, specifically.

Does anyone have any other comments?

@hdgarrood hdgarrood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me!

@natefaubion
natefaubion merged commit 6189770 into purescript:master Dec 30, 2018
@natefaubion

Copy link
Copy Markdown
Contributor

Thank you @pkamenarsky for implementing this! Thank you @spicydonuts for trying this out. 🎉

@garyb garyb mentioned this pull request Jan 12, 2019
3 tasks
dariooddenino pushed a commit to dariooddenino/purescript that referenced this pull request Jan 18, 2019
* Implement QualifiedDo

* Improve QualifiedDo tests

* Implement QualifiedAdo

* Trigger CI

* Backtrack only while parsing the do/ado keywords

* >>= pure . k is just fmap

* Clean up tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants