Skip to content

Alternative tailRecM implementation #57

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

Merged

Conversation

natefaubion
Copy link
Collaborator

The current implementation of tailRecM is problematic because it will asynchronously bounce completely synchronous computations. This breaks the guarantee Aff provides that synchronous computations will always execute synchronously. In fact, one could argue that the current implementation breaks equational reasoning by introducing an observable difference between (essentially) leftEff do a; b and do liftEff a; liftEff b.

This implementation is a bit hairier, but by observing synchronous effects in tailRecM, we can guarantee that a totally synchronous computation remains synchronous, and that it is stack safe.

This fixes SD-1665, and all tests pass on SlamData. Thoughts on this implementation?

@garyb
Copy link
Member

garyb commented Jun 1, 2016

I'm about to go out so don't have time to review this right now, but I found SD-1665 horrifying when I read about it this morning, so I'm glad it seems like there might be a reasonable way out of it.

@natefaubion natefaubion force-pushed the monadrec-sync-bounce branch from a520cb6 to 859a596 Compare June 1, 2016 15:51
@natefaubion
Copy link
Collaborator Author

I have pushed some inline comments to hopefully better explain what is going on.

// status = 2 (asynchronous effect)
//
// The inner Aff is run, and if the status is still 0, we have observed
// a synchronous effect. The result is stashed for later synchronous
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain why the status being still 0 means we've observed a synchronous effect? I'm sure you're right, but since I don't really know the guts of Aff too well, it wasn't quite clear to me how.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The order of synchronous effects are:

  • Initialize status to 0
  • Run Aff
  • Observe status

If the status changed between initializing and observing it, our Aff has resolved synchronously. When resolving synchronously, we check that the status has NOT changed from its initial state. You have to observe the change in two places: during resolution and, after launching the Aff.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, thanks! I think it makes sense to me what is going on here now.

@jdegoes
Copy link
Contributor

jdegoes commented Jun 1, 2016

👍 To this way of solving it.

@natefaubion
Copy link
Collaborator Author

Also if anyone has any ideas to test any edge cases, I'm happy to add it.

@natefaubion natefaubion force-pushed the monadrec-sync-bounce branch from 859a596 to b9fc891 Compare June 1, 2016 16:46
@natefaubion
Copy link
Collaborator Author

I've pushed (what I think are) better inline comments, and also added a test that uses tailRecM to interleave synchronous and asynchronous effects.

Avoids async bouncing by observing synchronous effects and looping.
@natefaubion natefaubion force-pushed the monadrec-sync-bounce branch from b9fc891 to fe23499 Compare June 1, 2016 17:11
@natefaubion
Copy link
Collaborator Author

I have added a test that verifies that a completely synchronous tailRecM is indeed resolved synchronously.

@jdegoes
Copy link
Contributor

jdegoes commented Jun 1, 2016

:shipit: Nice work @natefaubion!

@natefaubion natefaubion merged commit 406d6cf into purescript-contrib:master Jun 1, 2016
@jonsterling
Copy link
Contributor

Great job figuring this out, @natefaubion!

@ajnsit
Copy link

ajnsit commented Jun 1, 2018

the guarantee Aff provides that synchronous computations will always execute synchronously

Ah, this helps explain a bug I was seeing, where forking an aff would still call the handler synchronously. Is this documented properly somewhere? Also, is there a way to force a true fork irrespective of whether the computation is synchronous or asynchronous.

@safareli
Copy link
Contributor

safareli commented Jun 1, 2018

delay with 0 or some small number might do the trick

fork do
 delay (Milliseconds 0.0)
 ..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants