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

Missing basic combinators guard-operation and nack-guard-operation #97

Open
LiberalArtist opened this issue Sep 14, 2023 · 0 comments
Open

Comments

@LiberalArtist
Copy link

LiberalArtist commented Sep 14, 2023

It looks like (fibers operations) provides two basic operation combinators:

  1. choice-operation, corresponding to Concurrent ML's choose and Racket's choice-evt; and
  2. wrap-operation, like CML's wrap and Racket's wrap-evt.

But AFAICT, this library seems to be missing the other two basic combinators (of the four enumerated in Concurrent Programming in ML § 6.5):

  1. guard, like Racket's guard-evt; and
  2. withNack, a.k.a. nack-guard-evt.

Reppy writes that “the first three combinators have analogs in many concurrent languages, but the withNack combinator is unique to CML.”

The guard combinator constructs an operation that encapsulates a thunk. Each time the guard-based operation is supplied to perform-operation (directly or indirectly), the thunk is called to produce a fresh operation that is used in place of the guard-based operation. (More precisely, the thunk is called at most once for each call to perform-operation: it may be skipped if some other ready operation is chosen before the guard-based operation is considered.)

The withNack combinator is similar, but the function it encapsulates takes an argument: perform-operation calls it with a “negative acknowledgment” operation which will become ready if and when the operation returned by the callback definitely will never be chosen (e.g. normally because perform-operation has committed to some other operation, but also if the fiber blocked on it has terminated or if control escapes in some other way).

It's hard to write a short motivating example, but I'd point to the paper “Kill-Safe Synchronization Abstractions”, which walks through the implementation of the racket/async-channel library. Quite apart from the kill-safety aspects, guard-evt is needed to implement an async-channel-put-evt with the same guarantee as channel-put-evt (a.k.a put-operation) that the put takes place if and only if the operation is chosen by perform-operation.

I don't think make-base-operation is sufficient to implement either the guard or the withNack combinator. A naïve implementation of guard-operation could use try and block functions that close over some shared mutable state, but there doesn't seem to be any way for an implementation to distinguish each call to perform-operation, which it needs to do in order to get a fresh underlying operation from the thunk for each call. More obviously, nack-guard-operation would need perform-operation to cooperate by signaling the “negative acknowledgment” operations.

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

No branches or pull requests

2 participants