What is the inverse of a promise?
Asked Answered
P

4

8

A promise represents a value that might become available in the future (or fails to do so).

What I am looking for is a data type which represents an available value that might become unavailable in the future (possibly due to an error):

Promise a b = TransitionFromTo<PENDING, Either<value a, error b>>
??? a       = TransitionFromTo<value a, Either<ENDED, FAILED>> or
??? a b     = TransitionFromTo<value a, Either<ENDED, error b>>

Has such a concept (or similar) been explored already? Are there existing semantics or common idioms?

For example, it might represent an open database connection that will get closed. My particular use case would be representing a "mutable", i.e. variable-sized, set-like collections in FRP as a stream of such "ending values" - when an event occurs the value is added to the set and when the value "ends" it is removed.

I felt like representing this as a Signal<Option<value>> or {data = value, ended = Promise<null>} doesn't quite match it - the first case doesn't include the guarantee that the value finally settles to Nothing and the second has the data field still accessible after the end.

Polly answered 28/9, 2014 at 11:32 Comment(6)
Your use case of a database connection being closed would seem to be just another future value (where what's important is not the value, but the fact of fulfillment) so why could it not represented by a regular old promise?Haletky
@torazaburo: Yes, that's what I meant with {data = value, ended = Promise<null>} (and indeed it is how I've currently implemented it). However, it feels wrong, because data - the connection - can always be accessed, and because the promise doesn't represent a value.Polly
I'm far from any kind of promises expert, but it seems to me that promises don't need to have values, and in fact often the value is ignored. Can the value not be thought of as sort of a detail, additional information about the resolution? To me, promises where values are ignored have no code smell whatsover.Haletky
@torazaburo: Yes, that's true, there are a few use cases that ignore the value and just are interested in the temporal chaining; and for these sometimes promises are created that don't carry a value. This might indeed be used in an implementation of the type, but currently I'm looking for a name of such a (abstract) data structure that also carries the "before-settling" value.Polly
The first thing that came to mind is a weak reference, which can be used to access a value until it can't anymore.Umberto
Update: In this answer, the creator of Bacon.js says: "Personally I often create an Observable called death or whatever to signal the end-of-life for the Observer and then instead of subscribing to the subject I subscribe to subject.takeUntil(death)."Polly
W
8

In short - It's possible to model with an Async Generator.

In the DB connection example, conceptually you've got a sequence of DB connections, every time you access the value you're yielding (possibly asynchronously) a value from the sequence (a connection). Yielding can be asynchronous, and the value itself might be asynchronous too. The sequence might end (making it never available again) or it might always yield the result - it might remain pending and never yield another connection again.

It's worth mentioning that an async generator is a vast superset of the type you're after - it's much more expressive and is not the diret inverse.

In long - Inverse how?

You could inverse a promise in several different ways.

A promise is a singular temporal getter. That is it holds the following:

  • It represents a single value.
  • Its value is temporal (that is, time dependant).
  • It's a getter.

Quoting Kris's work on the temporality of promises:

An observer can subscribe to eventually see the value of a promise. They can do this before or after the promise has a value. Any number of observers can subscribe multiple times and any single observer can subscribe to the same promise multiple times.... Promises are broadcast. The law that no consumer can interfere with another consumer makes it impossible for promises to abort work in progress. A promise represents a result, not the work leading to that result.

The inverse of a promise in each of these regards is different.

  • A Deferred is a singular temporal setter. It is the dual of a promise and it allows setting a value similarly to how a promise allows getting it.

  • A Reader (more commonly called an observable) is the multiple version of a promise, and the temporal version of an iterable. It represents multiple values that are temporally coming. It's like a promise that can change its value.

  • A Value , one of out most used and primitive things is the synchronous version of a promise.

If you want something that is unlike a promise in all three - you need something more advanced. A Generator, is the inverse of a promise in that regard, it's a spatial, multivalued setter. It's the opposite of a promise in every such regard.

However, what you're talking of is something that's async in both regards, you want something that is both available/unavailable and changes value. That's an async generator, one of the more complex types in play here.

Your type needs to be similar to a generator that's async twice, once in having the next value and once in getting the value itself, I've asked a similar C# question here. Here is an interesting talk and lecture about it.

Basically you want a generator whose values and next() are asynchronous. It's rather complex and there are few things it models properly (for example - an infinite scroll where both the scrolling and the content are asynchronous).

In a sense, the sequence ending signals your value "not being available" anymore, and the sequence generating the next instance indicates your signal not being available temporally again.

I also recommend Erik Meijer's talk and Kris Kowal's GTOR about this subject.

Wandering answered 28/9, 2014 at 12:5 Comment(7)
I think I want to inverse the temporal component of the promise - it's like running it backwards from "has value" into "pending/unavailable". It should still be a single value, and a getter (the creator of the structure sets the value and determines when to end it).Polly
I see what you've done to the sequence of DB connections (I've read GTOR and found this async+async generator concept very interesting). However, the next event should not make the previous value unavailable, it should persist until it decides itself to end - think of pooling connections. My real-world use case is a collection of (reactive) childnodes which are created as stream events (by a generator so to say) and then should be removed from their parent once they "end".Polly
@Polly I recall something relevant in dl.acm.org/citation.cfm?id=54016 (the original promises paper) but I can't open it here since I don't have my academic VPN handy here. So I'll check when I'm at the university - if you have access to the resource - have a look :) Edit: I've done something rather rude, maybe it'll bear fruit we'll have to wait and see.Wandering
Yes, luckily I don't need my university VPN to access it :-) Do you know what exactly I should be looking for? PS: What did you consider rude?Polly
@Polly can you come here real quick?Wandering
I'm not convinced in my answer. I'll do my best to formalize this with temporal logic, hopefully that'll turn up a thing or two.Wandering
Yeah, me neither. Although I think a generator that yields the value when asked while it is available and just returning/throwing when asked after it is unavailable could fit the requirements.Polly
Q
2

A promise is an ordered triple:

Time -> Notification -> Value

Its inverse must also be an ordered triple:

Value -> Notification -> Time

However, you don't want to be notified when the value begins to decay, which is immediately; instead, you want to be notified when the value has decayed.

Value -> Time -> Notification

Notification carries Dispose semantics. Actually, it'd be quite similar to IDisposable if it were defined as IDisposable<T>.

public interface IDisposable<T out>
{
  T Value { get; }
  IDisposable Subscribe(Action disposedAction);
  void Dispose();  // Not entirely necessary, perhaps.
}

It looks like a Frankenstein hybrid of Task<T> and IObservable<T>.

The async dual of IDisposable perhaps?

Quizzical answered 28/9, 2014 at 18:24 Comment(7)
The async dual of disposable is a fascinating thing, and we've had lots of fun discussing its API in Bluebird. I don't think this is it though. Kris mentioned weakrefs or promises being true proxies are relevant.Wandering
@BenjaminGruenbaum Yea, I guess it's not dual. The dual would inverse Value into a setter; e.g., void(T next). That's more like IObserver<T>. Strange.Quizzical
The dual would be a deferred :)Wandering
Hmm, they're not the same pattern? Deferred == Delegate == IObserver<T>?Quizzical
You use C#'s notation so lemme try this: Deferred in this case is to promise as a task completion source is to a task.Wandering
Thanks, that clarifies it. Lol, I just noticed this wasn't a C# question. Sorry!Quizzical
That's ok it's language agnostic really, it had many names in many languages :)Wandering
S
0

you mentioned 'scala' in your tags, so: on jvm it can be done as a soft/weak reference. you can port that concept to different technology but will have to do memory management yourself. but it better fits to handling single values rather than streams

Sinotibetan answered 29/9, 2014 at 7:41 Comment(1)
A weak reference is nota promise inverse though. What he wants is a value (let's call it Expirable) that you can unwrap it like a Scala Future, and that works if you append handlers quick enough, at one point the value can "expire" at which point you all future unwrap attempts will pend forever.Wandering
P
0

The E language has references that can become Broken:

A Promise may become Near, Far, or Broken. Separately, A Far reference may become Broken.

  • A Near reference is the one familiar from single machine object programming languages. It is a reference arrow in which both head and tail are in the same Vat, and the arrowhead is attached to the object being designated. Being Near is a final state: once a reference is Near, it is forever Near. Therefore, Near references are not susceptible to partition. Near references support both immediate calls and eventual sends. Immediate calls are guaranteed to reach their recipient. Eventually sent messages are guaranteed to be delivered according to the partial-order, except across a checkpoint/revival.

  • An Eventual reference is strictly weaker than a Near reference. It only supports eventual sends, with only a fail-stop guarantee, rather than guaranteed delivery. Fail-stop means that, should it ever come to designate an object, it will deliver messages to that object according to the partial-order until it fails. Once it fails, it will never again deliver any messages, and it will eventually become Broken.

  • A Broken reference is strictly weaker than Eventual or Near references. It does not and never will designate an object. Instead, it holds a Throwable to indicate what problem caused it not to designate an object. It does not support immediate calls -- if one is attempted it throws its problem. It does not support eventual sends -- if one is attempted, the result is a reference broken by this same problem. Like Near, Broken is a terminal state (once Broken always Broken). Broken references are transitively immutable and are transitively passed by copy.

Polly answered 30/10, 2023 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.