Skip to content
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

Suspend terminology #152

Closed
backuitist opened this issue Mar 15, 2018 · 11 comments
Closed

Suspend terminology #152

backuitist opened this issue Mar 15, 2018 · 11 comments

Comments

@backuitist
Copy link

Cats-effect uses suspend to describe the action of capturing an effect. I believe that suspend is generally understood to mean something else: suspending a computation, that is, to return the control immediately.

Here are a few occurrences that I found:

In addition, there exists a synonymous to suspend, capture as seen in #78
In my opinion, capture does a better job at describing what IO does as suspend suggests that "unsuspension" (i.e evaluating your effect) isn't repeatable. This is of course highly debatable.

Furthermore, the use of suspend for side-effects makes it unavailable for other uses, such as a "semantically blocking" operation: typelevel/fs2#1097 (comment)

@alexandru
Copy link
Member

alexandru commented Mar 15, 2018

Capturing an effect goes hand in hand with suspending a computation. You can't capture the effect without it.

The suspend word is defined as:

  • temporarily prevent from continuing or being in force or effect
  • officially prohibit someone from carrying out their usual role
  • defer or delay
  • cause an imposed sentence to not be enforced

In my mind as a non-native English speaker, suspend is a synonym for pause. For it to be used for say Future would be incorrect, because eager asynchronous execution is not a suspension or a delay.

Aka from a semantics point of view, this doesn't make much sense:

Future.suspend {
  Future(1 + 1)
}

I don't know how Kotlin's coroutines work, but if they are anything like Scala's Future (and they probably are), then "suspend" in that context refers to the underlying engine, being an implementation detail that the user doesn't really care about.

But most importantly, we cannot change suspend because it's been in use in Scalaz since 2012 at least, in the Free encoding and we can probably find traces of it that are older than that. It's also in use in Cat's Free too.

Maybe even more important is usage of Task.suspend, the Scalaz 7 Task having been the de facto IO monad for Scalaz, until Monix and Cats-effect happened. Note that Jason Zaugg that worked on SIP-22 was also an contributor of Scalaz, having touched all Scalaz files I mentioned above.

TL;DR — consistency with Kotlin or Haskell is actually far less important than consistency with the rest of the Scala ecosystem and everybody has used Suspend as the name of the Free ADT state and if you squint, IO is nothing more than an evolved Free encoding, which is why it was also used in Scalaz's Task, which every user of Scalaz has been using thus far 😉


Btw capture isn't a bad name if we didn't have suspend already, however let me make a counter argument in regards to side effects ...

IO.suspend isn't only about suspending side effects, but also about stack safety.

In general when talking of referential transparency, we ignore memory allocation, however with eager evaluation and without having tail calls optimized by the runtime, in many cases we need to suspend execution in order to preserve stack safety.

See my recent PR from Cats for example: typelevel/cats#2185
In this PR you can see that:

  1. the issue has nothing to do with side effects per se, as we understand them
  2. suspend is needed in order for that flatMap to be stack safe

So in this particular instance capture, with the semantics you're defining, is actually less correct than suspend 😉

@alexandru
Copy link
Member

Btw @backuitist, Kotlin's Arrow is also doing IO.suspend as well.

They got it from us actually. Didn't seem to upset @pakoito 🙂

@pakoito
Copy link

pakoito commented Mar 15, 2018

There's been a minor embuggerance by the Kotlin std library in that suspend is also now a stdlib function and it collides with ours. There's a chance we'll rename it to delay, defer, or something similar.

@alexandru
Copy link
Member

Ah, so @backuitist was right for mentioning it for Kotlin.

We've been using delay as your invoke. If you change it, I recommend defer, it's been in use by ReactiveX 😉

@pakoito
Copy link

pakoito commented Mar 15, 2018

Yes, my only concern is that in other places defer is () => F[A] rather than a () => A that we wrap. Next version will come with a major rename of many things, so we'll have to make do.

@SystemFw
Copy link
Contributor

There's a longer discussion about this in the fs2 ticket linked above. In particular I also don't think suspend works to replace what we mean by semantically blocking in fs2.

@backuitist
Copy link
Author

backuitist commented Mar 15, 2018

@alexandru pause isn't capturing a side-effect:
For the sake of the argument let's say we have the following

val io = IO.suspend {
   println("hello")
}

Then per this definition, you'd expect

io.resume() // prints hello
io.resume() // already resumed

IO.suspend isn't only about suspending side effects, but also about stack safety.

IMO its prime responsibility is capturing an effect. Providing stack safety is a nice, orthogonal, by-product and must be mentioned in the doc, but I believe it's better to have a term that describes accurately what a function is primarily designed for than something fuzzy that conflates both meanings.

consistency with Kotlin or Haskell is actually far less important than consistency with the rest of the Scala ecosystem

That's a good point and I understand that it isn't very practical to rename all these things, still raising this issue is perhaps useful for future readers to have an idea of the origins of this terminology.
I personally don't struggle with all this, but IMO it makes it harder to convince/educate people coming from Java.

@alexandru
Copy link
Member

@backuitist got this email with your message, but now I'm not seeing it anymore, either GitHub is playing tricks or you deleted it 🙂

@alexandru On a side note your cat's free example isn't going to be difficult to rename as it is private[free]. In general I have the feeling that renaming suspend into capture should not have such a tremendous impact on client code -- it is a breaking change, sure, but maybe not of a huge magnitude.

I understand your reticence for suspend and I too care very much about naming.

However a rename won't happen because:

  1. people doing FP in Scala's community know what suspend is, you're trying to re-purpose suspend for something else and that will never happen, due to too many projects being involved
  2. any difference between suspend and capture is only superficial
  3. API breakage is always bad, so when it happens, we need a really, really, REALLY good reason for it

At this point trying to rename suspend in Scala's community is like trying to rename flatMap into >>=, or chain, or smoosh, or mapcat.

Mind you, Scala does not have the greatest track record in regards to API breakage. And it kind of sucks, no matter what we tell ourselves.

Personally I'm a fan of Rich Hickey's philosophy, that he presents in his Spec-ulation presentation. In it he claims that API breakage should never happen without a namespace change (e.g. cats.effect -> cats.effect2). And if that makes API breakage to be really expensive, well, that's a feature.

We can't do that, I would be on board with it but we don't have buy-in from the rest of the community.

But it should go without saying that renaming capture to suspend is a no-go. Plus I really like suspend. It's delay that I have a problem with, but we aren't changing that either for the same reasons.

IMO its prime responsibility is capturing an effect. Providing stack safety is a nice, orthogonal, by-product and must be mentioned in the doc, but I believe it's better to have a term that describes accurately what a function is primarily designed for than something fuzzy that conflates both meanings.

N.B. allocating stack space is a side effect. If it wouldn't be, then pure, eager programs wouldn't crash. You can encounter this problem sometimes in Haskell too. We just choose to ignore it for practical purposes 😉

Also, technically, it has no such thing as a "prime responsibility". Yes, it's about suspending side effects, however its responsibility is to uphold the laws defined by SyncLaws.

So actually its only responsibilities at the moment are these:

F.suspend(fa) <-> F.flatten(F.pure(fa))

F.suspend[A](throw e) <-> F.raiseError(e)

This is another way of saying that while some people, like us, care about names, in terms of what the thing actually does, only the described laws are important.

it makes it harder to convince/educate people coming from Java.

I think your real issue here is with "blocking", with which I actually agree 🙂

But changing suspend won't fix the confusion of Java folks, while at the same time creating confusion in the Scala camp.

@backuitist
Copy link
Author

@backuitist got this email with your message, but now I'm not seeing it anymore, either GitHub is playing tricks or you deleted it 🙂

I indeed deleted it, but you were too quick! :) I thought that last comment didn't add much value to the debate, as it's pretty clear that things won't change. At least my last point regarding future readers holds!

@alexandru
Copy link
Member

@backuitist we have a documentation website now and we probably should add explanations on this terminology somewhere in there.

Can we close this issue for now? I like clean issue trackers 🙂

@backuitist
Copy link
Author

backuitist commented Mar 23, 2018 via email

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

No branches or pull requests

4 participants