It looks like (fibers operations) provides two basic operation combinators:
choice-operation, corresponding to Concurrent ML's choose and Racket's choice-evt; and
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):
guard, like Racket's guard-evt; and
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.
It looks like
(fibers operations)provides two basic operation combinators:choice-operation, corresponding to Concurrent ML'schooseand Racket'schoice-evt; andwrap-operation, like CML'swrapand Racket'swrap-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):
guard, like Racket'sguard-evt; andwithNack, a.k.a.nack-guard-evt.Reppy writes that “the first three combinators have analogs in many concurrent languages, but the
withNackcombinator is unique to CML.”The
guardcombinator constructs an operation that encapsulates a thunk. Each time theguard-based operation is supplied toperform-operation(directly or indirectly), the thunk is called to produce a fresh operation that is used in place of theguard-based operation. (More precisely, the thunk is called at most once for each call toperform-operation: it may be skipped if some other ready operation is chosen before theguard-based operation is considered.)The
withNackcombinator is similar, but the function it encapsulates takes an argument:perform-operationcalls 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 becauseperform-operationhas 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-channellibrary. Quite apart from the kill-safety aspects,guard-evtis needed to implement anasync-channel-put-evtwith the same guarantee aschannel-put-evt(a.k.aput-operation) that the put takes place if and only if the operation is chosen byperform-operation.I don't think
make-base-operationis sufficient to implement either theguardor thewithNackcombinator. A naïve implementation ofguard-operationcould 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 toperform-operation, which it needs to do in order to get a fresh underlying operation from the thunk for each call. More obviously,nack-guard-operationwould needperform-operationto cooperate by signaling the “negative acknowledgment” operations.