Re: Proposal to improve Promises (was Re: Re: The Inbox: Kernel-tonyg.1610.mcz)

Jakob Reschke <[email protected]>
Newsgroups gmane.comp.lang.smalltalk.squeak.general
Message-ID <CA+ovNC52BZSHPPB--JKETVsPG0qtPWAi0MXCfAHrG7un1dDp=g@mail.gmail.com>
Time flies and the code is years old by now, so I would have to dig
deep into it or think hard to make any promises (pun intended) which
way would work best, but I do not want to spend my free time with that
right now. (I seem to rather spend it writing such emails at midnight,
sigh.)

What I can give you as context is why I used promises there. I sought
for something that allows me to code multi-step workflows that are
interrupted with user interactions (i. e. compute something, then ask
something from the user via a dialog, then continue, maybe another
interaction with waiting, continue, ...) with the following criteria:
  - not obscuring the overall workflow too much by tearing it apart
across many methods that don't seem to invoke each other directly,
  - being able to use the Restart button in the debugger to easily
roll back part of the workflow without having to restart from the very
beginning and repeating lots of clicks,
  - related to the above: not losing access to the contexts of what
happened before the last user interaction ("How did I get here?!"),
  - keeping the workflow implementation somewhat decoupled from the
GUI implementation, especially the wiring what happens after you click
Ok or Cancel,
  - without modal dialogs because I strongly dislike how they break in
Monticello (or used to, I don't know whether anything has changed
there in the meantime).

Promises did not really make me completely happy. I also tried other
things, such as having the workflow in another Process. I was
interested in continuations, too, but never fancied to roll yet
another implementation of them or pull in another dependency for them.

Which means: it is not mandatory that the Squot implementation stay
with promises or with relying on BrokenPromise. Whoever takes time to
look into the issue, and finds a simpler, stable alternative: feel
free to change it. Maybe it is better without promises, or maybe it is
simple to adapt the code to this breaking change and stick to
promises, I don't know.

As you can see in the code that you, Christoph, quoted, the purpose of
most handlings of BrokenPromise is to react to the user cancelling the
interaction, and to cancel the overall workflow, too. I vaguely recall
that the promise (of completing the user interaction) gets rejected
with #cancelled if the user cancels, but I am not sure. Thus this is
not really an error/exception situation. So maybe one way would be to
change the implementation to not reject the promises in such cases but
resolve them with a special cancel value and check for that. That
sounds like making it as ugly as null checks, but maybe that is not
less pretty than the current code handling BrokenPromise.

Also note this ifError:ifNotError: extension... I wrote that and it
looks like at least one reason for it was a different incompatible
change in promises around Squeak 5.3 already. (I vaguely remember
being in favor of that change though.) How much patching will it take
this time to make the adapted code work both in the latest Squeak and
in slightly older Squeak versions? Thus the question in my last email
was serious. If nobody finds a nice way to achieve that, well, then
this is a very annoying breaking change!

Kind regards,
Jakob

Am Mo., 27. Apr. 2026 um 23:04 Uhr schrieb Thiede, Christoph
<[email protected]>:
>
> I mentioned you because you might understand your code base best. :D Quickly skimming through some representative references to BrokenPromise on the develop branch:
>
>       [[[answer := aWaitable wait]
>             on: BrokenPromise do:
>                   [:bp | bp ifError: [:error | exception := error. bp return] ifNotError: [bp pass]]
>             on: Error do:
>                   [:e | exception := e. e return]]
>
> This is easy:
>
>       [[[answer := aWaitable wait]
>             on: (Smalltalk at: #BrokenPromise) do:
>                   [:bp | bp ifError: [:error | exception := error. bp return] ifNotError: [bp pass]]
>             on: Error do:
>                   [:e | exception := e. e return]]
>
> (The second handler will now handle the failed promise.)
>
>       self withUnitOfWork:
>             [[SquotGUI waitFor:
>                   ((workingCopy newInteractiveSaveOperation
>                               title: 'Select changes and message for the new version';
>                               applyToWorkingCopy)
>                         then: [:result | self refresh])]
>                   on: BrokenPromise do: [:e | "cancelled" e return]].
>
> This is harder: You explicitly only search for asynchronous errors but not for synchronous errors. If the operation can be assumed not to raise synchronous errors as well, simply rewrite:
>
>       self withUnitOfWork:
>             [[SquotGUI waitFor:
>                   ((workingCopy newInteractiveSaveOperation
>                               title: 'Select changes and message for the new version';
>                               applyToWorkingCopy)
>                         then: [:result | self refresh])]
>                   on: Error do: [:e | "cancelled" e return]].
>
> If the operation might also raise synchronous errors, this is not something we could easily catch with the new refactoring. If necessary, you could test whether the signaler context of the error was Promise>>#signalErrorValue.
>
>       [SquotGUI waitFor: promise] "will be resolved with a SquitAddRemote or rejected with #cancelled"
>             on: BrokenPromise do: [:e | e ifNotError: [^ self undo]].
>
> No idea when non-erroring broken promises are raised. Only in Squeak 5.3 and older? Then simply:
>
>       [SquotGUI waitFor: promise] "will be resolved with a SquitAddRemote or rejected with #cancelled"
>             on: (Smalltalk at: #BrokenPromise) do: [:e | e ifNotError: [^ self undo]].
>
> Overall, I think this should be doable. But I also mentioned you because I would be interested in your opinion on this refactoring - does it prevent possible use cases you would have in mind or does it only streamline working with promises and prevent bad ideas?
>
> Best,
> Christoph
> ________________________________
> Von: Jakob Reschke <[email protected]>
> Gesendet: Montag, 27. April 2026 21:31 Uhr
> An: The general-purpose Squeak developers list <[email protected]>
> Cc: Thiede, Christoph <[email protected]>
> Betreff: Re: [squeak-dev] Re: Proposal to improve Promises (was Re: Re: The Inbox: Kernel-tonyg.1610.mcz)
>
> If somebody volunteers to refactor Squot to work without
> BrokenPromise, will the result also work on older Squeak releases?
>
>
> Am Mo., 27. Apr. 2026 um 19:54 Uhr schrieb Christoph Thiede via
> Squeak-dev <[email protected]>:
> >
> > Hi Jakob, Tony, Chris, all,
> >
> > I only found out today that BrokenPromise has been deleted from the trunk. This is quite a breaking change.
> >
> > For example, one can no longer install Squot (which is recommended in the Preference Wizard) into a recent trunk image because it handles and extends BrokenPromises in multiple places.
> >
> > Regarding functionality, it is no longer possible to (i) disambiguate an exception raised asynchronously from a regular exception, (ii) access the original signaler context of the inner exception, or (iii) identify the causing promise. Squot seems to only do (i), while I think (ii) and maybe (iii) would be helpful debugging features (when I occassionally witnessed sporadic bugs in Squot in a few years ago, it was already hard enough to find out where they came from because the stack was not preserved, and not even preserving the signaler context or the resolvers will likely worsen such situations).
> >
> > That being said, I also understand that this extra exception wrappers felt opaque and cumbersome. Evolving APIs vs retaining backward-compatibility always is a tradeoff. Not sure what's more important.
> >
> > At the very least, why do we have to raise a generic Error rather than a specific one if errorValue is not an exception?
> > For the other case, maybe we would need a way to attach multiple tags to exceptions to preserve information such as original signaler contexts or re-signaling promises to the original exception object without removing other information. Like, Exception>>tagDictionary instead of Exception>>tag? Maybe a transparent wrapper around the original exception class that forwards all unknown requests to the original exception (though this would not preserve identity, if that matters)? Maybe something else?
> >
> > For the upcoming release, I suggest we should do at least one of the following options:
> >
> > (i) Update Squot to no longer depend on BrokenPromise (it is not actively being maintained right now and a successor is in development, but still our recommendation in the preference wizard)
> > (ii) Provide backward compatibility for BrokenPromise (though I don't know how we could achieve that)
> >
> > Also, we should document this breaking change in the release notes.
> >
> > Best,
> > Christoph
> >
> > --
> > Sent from Squeak Inbox Talk
> >
> > On 2025-08-12T10:15:25+02:00, [email protected] wrote:
> >
> > On 8/8/25 03:24, Chris Muller wrote:
> > > It might be fine, but please let me know:  If the settlement
> > > of one Promise utilizes a variable which refers to another, yet
> > > unsettled Promise, and that inner promise is rejected, how should the
> > > (outer) Promise respond?  I guess it would be Error Error Error up the
> > > Promise chain; likely each signaled from different Processes, is that
> > > right?  Or, do we only want one signal from the "root" Promise?
> >
> > Promises do chain together, yes. The implementation in Squeak already
> > does something reasonably sensible (I'm sure there are more corner cases
> > waiting to be discovered), drawing heavily on https://promisesaplus.com/.
> >
> > > But yes, for how *I* use Promises, this proposal is absolutely fine.
> >
> > Thanks. I'll go ahead and merge then, I think! I have a small queue of
> > improvements to Promises that will follow.
> >
> > Best,
> > Tony
> >
> > Squeak-dev mailing list -- [email protected]
> > To unsubscribe send an email to [email protected]
Squeak-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.