-
Notifications
You must be signed in to change notification settings - Fork 65
Initial pass on catch rewriting; needs review! #53
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
Conversation
|
I reviewed it in person.
This sounds especially compelling to me! |
Added the start for some tests which pass on this PR but fail in master. I need to add bind tests as well but that is a little more involved since it needs to track how many times each callback gets called for each path (asserting it's always one of the two callbacks but never both and never either more than once). |
Hello! I think lack of makeAff :: ((Error -> Eff e Unit) -> (a -> Eff e Unit) -> Eff e Unit) -> Aff e a synchronous handlers mustn't throw exceptions. |
I'll let @jdegoes chime in with other feedback as I saw he briefly commented in IRC, but I can explain at least one reason why It should be easy enough to provide a function that converts promises into |
@garyb That makes sense. I didn't consider cancellation. @cryogenian I disagree. The return type of |
@spicydonuts Yes, |
Perhaps not mandatory, but it is a somewhat unexpected edge case, especially for anyone who notices that I don't have much experience with exception handling in Eff, and zero in Haskell, so I'm curious what the code would look like if those mechanisms were used. Would all makeAff calls be required to wrap their Eff in catchException? Is that even possible when the Eff doesn't declare that it can throw exceptions (even if it does throw them, like |
Is there a use case for handling asynchronously failing Affs differently than synchronously throwing Affs (especially when they don't declare exception effects, though that seems like potentially a larger issue). |
This may be besides the discussed points here, but there is definitely some weirdness with Aff exception handling currently, I recently had to do this: https://github.com/garyb/purescript-quasar/blob/initial/test/src/Test/Main.purs#L79-L91 because the raised errors just seemed to disappear otherwise. |
Ah yeah. I've also seen some weird behavior with forkAff and failing unit tests, causing each failed test to multiply the error output that gets logged. |
This is just my opinion, but it feels like this is the key distinction between Eff and Aff. Eff = synchronous, might throw an exception because it can't do anything else. Aff = asynchronous, errors go through error callbacks. It seems really odd to have Aff simultaneously be synchronous, but only in some cases when un-catchable exceptions are involved. I want to see an Aff return type and trust it to follow that flow in all cases. |
I'd tend to agree with that. Even if it's not 100% accurate, as it means we're assuming an |
Yeah, it's sort of true both ways you look at it. Doing it basically assumes all Effs have EXCEPTION, but that seems way safer than leaving Aff's semantics fragile. I'd look at it less as an assumption that all Effs can throw and more as a safeguard against the fact that running code at all is dangerous on some level and the point of the Aff type is to bring that complication under control a little bit. |
That's the case! Most ffi functions should have Why not alter signature to makeAff
:: ((Error -> Eff (err :: EXCEPTION|e) Unit)
-> (a -> Eff (err :: EXCEPTION|e) Unit)
-> Eff (err :: EXCEPTION|e) Unit)
-> Aff e a ? BTW, this
I'm not sure what's right approximation here. And, personally, would prefer if all results would be wrapped to Other confusing thing is potentially multiple returned value. If |
You may be right on the signature. Either way though, it's And streams definitely seem like a separate type/concept. Aff/Promise vs Signal/Behavior/Observable. 90% of the time an Aff that resolves twice is both a mistake and unnoticed by the developer using it (until they start noticing weird bugs or output and spend a bunch of time wondering what's wrong with their own code 🙂). |
|
Indeed, now that we have
👍
👍 Basically, I think it's valid to always assume that
Not while I still have breathe. 😆 💀 Promises/A+ have absolutely awful semantics (especially the intersection of success/error and cancellation, but in lots of places, such as the magical auto-flattening of nested promises), and are impure (once you have a reference to a promise, it's effect is already running). |
By the way, I haven't had a chance to review this in depth, but will do so when I get a chance. |
Definitely in agreement now. 😅 |
This sounds in line chatting with @spicydonuts last night too thanks @jdegoes! |
Yes. I'd even put that in By the way, if everyone agrees this PR is consistent with the above goals, well, who am I to argue, you can skip my code review. 😄 My main goal would be to just make sure the behavior is 100% consistent and there are no edge cases. |
Ping. I think this is ready? We have talked about wanting to separate single and multiple resolution (Promise-like versus Observable-like) and lift as much FFI as possible into PureScript but waiting on this one first. |
It'd be helpful for someone with a larger project with tests that uses more of the Aff API to give it a try. I don't really see the need for splitting up the single/multiple continuation use cases since there are already a few FRP/Observable libs out there. That's actually one of the reasons the current behavior is so surprising for someone not already familiar with |
@AppShipIt @spicydonuts I'll deal with this today!
|
Yeah.. what about it's tricky.. I think promises just ignore anything after the first call. |
Could something like |
We should probably remove the |
@spicydonuts This looks good to me. I haven't tested it with a large code base, but the code looks fine.
The "problem" is |
OK, unfortunately this needs rebasing now. Also, I am for merging when others are happy. 👍 |
Cool, I'll do it soon |
2eba38d
to
09f6c37
Compare
I've rebased this. The only change I wasn't quite sure about was |
@spicydonuts Yes, the dev dependencies will ensure it isn't pulled in for other projects. Looks good to me! |
I have a feeling many of these hairy JS functions could be moved to PS, so I want to take a pass at that too before this is accepted. Opening this now to get initial feedback/concerns. This is definitely a scary change, but there are definitely some issues with the current code.
The gist of the problem is that
success
callback calls shouldn't be wrapped in try/catch becausesuccess
returns to user code. An Aff/Promise should only ever resolve to 'success' or 'failure'. Wrapping the success callback in a try/catch makes it possible to resolve with 'success' and then later resolve with 'error', possibly causing user code to re-run.The second issue is that while
success
shouldn't be caught, calling the Eff callback inmakeAff
should catch errors. Currently any synchronous exceptions will bypass the Aff error handling and travel up the stack unhandled.Here is
es6-promise
's equivalent tomakeAff
which is nearly the exact same code: https://github.com/stefanpenner/es6-promise/blob/master/lib/es6-promise/-internal.js#L233This brings me to another thought.. it seems Aff should derive heavily from a good Promise implementation, or maybe even just wrap another implementation. If it wraps native Promises it would require polyfills for some users, but JS people are pretty used to that these days. This would be awesome for FFI as well, as any FFI function that returns Aff would just need the JS side to return a promise-like thing.