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+ovNC7mia4+ie_mBMaq9V=BHvBYWqihgVFsVix9m-SWkTrzqQ@mail.gmail.com> |
Hi Lauren, I like Common Lisps and conditions&restarts but likewise did not embark on an implementation in Smalltalk. The user interactions are more than simple choices of direction. Examples: selecting which changes in the working copy to commit and which not; which to load; handling a merge; if a push or fetch failed due to authentication issues, asking the user for new credentials. If restarts can accommodate such "bigger" prompts and results, they might be a nice solution. But alas not available in standard Squeak, so not a solution in reach even if implemented somewhere else. Kind regards, Jakob Lauren P <[email protected]> schrieb am Mi., 29. Apr. 2026, 11:01: > Hi Jakob, > > Is this far off the idea? > > It sounds like you want continuations, but at specific places you ask the > user to proceed to the! next step or do something else (including > abandoning). There might also be context-sensitive branches you can choose > from based on which part of the continuation you're in when the computation > stops (due to error or interrupt). > > If so, that is a Common Lisp restart. I was playing with implementing > them, but never needed one enough to warrant spending much time on it. > > > On Tue, Apr 28, 2026, 16:29 Jakob Reschke <[email protected]> wrote: > >> 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] > > 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]