Question about an asynchronous function example in the FFI chapter of the book

@milesfrain Hi. I would’ve PM’ed you, but I’m prevented from doing so as a new user by Discourse. Anyway, I noticed that you wrote this section of the book, I was wondering, why does wait need to be wrapped as an Effect? Couldn’t it just be imported as an Int -> Promise Unit (instead of writing and importing the sleepImpl wrapper function for it), then be wrapped in the PS side instead of in the JS side? I.e.:

foreign import wait :: Int -> Promise Unit
sleep :: Int -> Aff Unit
sleep = toAffE <<< pure <<< wait

And why does it even need to be wrapped in the first place? Couldn’t it just be converted directly using toAff instead of toAffE? I.e., with toAff <<< wait instead of toAffE <<< pure <<< wait?

I don’t have a great explanation for this beyond what’s covered in the docs of the aff-promise library:

Notice that the fetch(url) call is wrapped in a thunk and imported as an Effect, because otherwise the fetch operation would be initiated as a side-effect of a pure function - while fetch returns a promise, at the point the function returns the network request has already been initiated.

Perhaps someone else could explain this more clearly. Might be good to include that improved explanation in the book too.

Thanks. Hopefully @nwolverson, who seems to have written that documentation, could weigh in on the matter.

By using Effect you can defer the creation of the timer, consider the differences of the following:

a = launchAff_ do
  let slept = toAff $ wait 300
  log "waiting 1"
  slept
  log "waiting 2"
  slept
  log "done waiting"

-- Using the books definition of sleep
b = launchAff_ do
  let slept = sleep 300
  log "waiting 1"
  slept
  log "waiting 2"
  slept
  log "done waiting"

a will take around 300ms to complete, whereas b will take around 600ms. In a, the slept promise is created and resolved only once

2 Likes

Ah thanks, the example was very enlightening! That answers the question about why toAffE <<< pure <<< wait might be used instead of toAff <<< wait.

I’m still curious why wrapping wait was done in the JS side with the sleepImpl wrapper rather in the PS side with pure <<< wait though, which should work the same, right?

Edit: Nevermind, I just tried it, and it seems that it doesn’t work the same. Why though? My understanding is that pure wrap outputs in an Effect, which is the same as putting the computation in a nullary function to defer computation, or is that wrong?

And is there a way to interop with the JS function by only writing PS, instead of also having to write JS wrappers too? Please share examples!

Effects pure could be written in foreign code as:

exports.pure = x => () => x;

Composing wait with pure isn’t the same as sleep, as the computation inside wait wasn’t deferred by the () =>, only the result (which is too late)

Maybe a clearer example:

exports.sleepImpl1 = ms => () =>
  wait(ms);

// This is the 'inlined' version of composing wait and pure
// Note that once again wait has been evaluated too early
exports.sleepImpl2 = ms => {
  var x = wait(ms);
  return () => x;
};

I think you always either need to use unsafeCoerce or foreign code to interop wait with Aff (via Effect)

3 Likes

Thank you, you’ve dispelled my misconception about how pure worked and made me understand why what I was doing didn’t work as I’d expected. I really appreciate the clear examples and explanations!

2 Likes