The Inbox: KernelTests-jr.383.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

The Inbox: KernelTests-jr.383.mcz

commits-2
A new version of KernelTests was added to project The Inbox:
http://source.squeak.org/inbox/KernelTests-jr.383.mcz

==================== Summary ====================

Name: KernelTests-jr.383
Author: jr
Time: 21 June 2020, 7:27:47.560506 pm
UUID: ecddb3cb-25c9-6c43-a879-59e527a3a9ee
Ancestors: KernelTests-tonyg.382

Test whether Notifications reject the promises of future sends

In my opinion, they should not.

=============== Diff against KernelTests-tonyg.382 ===============

Item was added:
+ ----- Method: PromiseTest>>testFutureResolutionWithNotification (in category 'tests - future') -----
+ testFutureResolutionWithNotification
+ | p |
+ p := [Notification signal: 'should not reject'. 3 + 4] future value.
+ self assert: (self waitUntil: [p isResolved or: [p isRejected]] orCycleCount: 100).
+ self assert: p isResolved.
+ self assert: 7 equals: p value.!


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: KernelTests-jr.383.mcz

Jakob Reschke
I am trying to come up with a nice solution for this failing test.

Am So., 21. Juni 2020 um 19:27 Uhr schrieb <[hidden email]>:

>
> A new version of KernelTests was added to project The Inbox:
> http://source.squeak.org/inbox/KernelTests-jr.383.mcz
>
> ==================== Summary ====================
>
> Name: KernelTests-jr.383
> Author: jr
> Time: 21 June 2020, 7:27:47.560506 pm
> UUID: ecddb3cb-25c9-6c43-a879-59e527a3a9ee
> Ancestors: KernelTests-tonyg.382
>
> Test whether Notifications reject the promises of future sends
>
> In my opinion, they should not.
>
> =============== Diff against KernelTests-tonyg.382 ===============
>
> Item was added:
> + ----- Method: PromiseTest>>testFutureResolutionWithNotification (in category 'tests - future') -----
> + testFutureResolutionWithNotification
> +       | p |
> +       p := [Notification signal: 'should not reject'. 3 + 4] future value.
> +       self assert: (self waitUntil: [p isResolved or: [p isRejected]] orCycleCount: 100).
> +       self assert: p isResolved.
> +       self assert: 7 equals: p value.!
>
>

Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: KernelTests-jr.383.mcz

Chris Muller-3
Hi Jakob,

Unrelated to your concern about Notifications, I notice the test assigns p to the result of sending #value to the Promise, which will very possibly still be nil at that point, even though it then goes on to test it against #isResolved and #isRejected right afterward.

I find the impedance mismatch between values and Promises of values confusing, too, -- afterall, I thought the purpose was to be able to use them interchangeably.  Equally, I wish 'error' were always some kind of Exception, and would signal it when #value (wait) was sent instead of exposing Promises by forcing clients to ask, #isRejected?  #isResolved?

 - Chris



On Sun, Jun 21, 2020 at 12:46 PM Jakob Reschke <[hidden email]> wrote:
I am trying to come up with a nice solution for this failing test.

Am So., 21. Juni 2020 um 19:27 Uhr schrieb <[hidden email]>:
>
> A new version of KernelTests was added to project The Inbox:
> http://source.squeak.org/inbox/KernelTests-jr.383.mcz
>
> ==================== Summary ====================
>
> Name: KernelTests-jr.383
> Author: jr
> Time: 21 June 2020, 7:27:47.560506 pm
> UUID: ecddb3cb-25c9-6c43-a879-59e527a3a9ee
> Ancestors: KernelTests-tonyg.382
>
> Test whether Notifications reject the promises of future sends
>
> In my opinion, they should not.
>
> =============== Diff against KernelTests-tonyg.382 ===============
>
> Item was added:
> + ----- Method: PromiseTest>>testFutureResolutionWithNotification (in category 'tests - future') -----
> + testFutureResolutionWithNotification
> +       | p |
> +       p := [Notification signal: 'should not reject'. 3 + 4] future value.
> +       self assert: (self waitUntil: [p isResolved or: [p isRejected]] orCycleCount: 100).
> +       self assert: p isResolved.
> +       self assert: 7 equals: p value.!
>
>



Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: KernelTests-jr.383.mcz

Jakob Reschke
Hi Chris,

I was sometimes confused about this too: future itself does not return
a Promise, but future + the following message send does. This is due
to the compiler transformation explained in FutureNode. So value will
not be sent to the Promise and p will hold the Promise right away and
not be nil.

So, I agree that #future is confusing, but it is also much less to
read than p := Promise new. Project current addDeferredUIMessage:
[[... p resolveWith: ...] ifCurtailed: "or whatever we settle on" [p
reject]].

I don't think you ever could use Promises and values interchangeably.
Promise>>wait does more or less what you want, but it means you have
to use the promise explicitly, not interchangeably. You can chain
promises with then:[ifRejected:], and consume the Promise outcome in
the respective block argument, turning your control flow from
left-to-right-top-to-down into Promise style... But just getting the
value of the Promise at some point without wait might just answer nil
if still unresolved.

About signalling errors upon #value as you put it: you mean you want
to get the original Exception when you get the result of the promise,
so you don't have to check in which state the promise is? It sounds
useful at first, but the result or reason (for errors) can be
retrieved more than once; would the exception only be signalled
(again) upon the first retrieval of the result? Also it would look
funny in the Debugger stack: either you put the original stack of the
exception on top of the current (result getting) one, or you hide the
current stack, or you cut away the sender of the exception context
(which makes it less useful). Do you have an idea how to resolve this?

Also note that you can reject a promise with something else than an
Exception. It could also be some kind of error value or just a string
that explains the rejection. You would not be able to distinguish that
from a regular result without type checking. Maybe we could have a
specialized promise that only rejects on exceptions and would allow
the kind of workflow you seem to have in mind where the promise is
like a proxy for the result. If we determine that it is useful. :-)

Kind regards,
Jakob


Am Do., 25. Juni 2020 um 02:55 Uhr schrieb Chris Muller <[hidden email]>:

>
> Hi Jakob,
>
> Unrelated to your concern about Notifications, I notice the test assigns p to the result of sending #value to the Promise, which will very possibly still be nil at that point, even though it then goes on to test it against #isResolved and #isRejected right afterward.
>
> I find the impedance mismatch between values and Promises of values confusing, too, -- afterall, I thought the purpose was to be able to use them interchangeably.  Equally, I wish 'error' were always some kind of Exception, and would signal it when #value (wait) was sent instead of exposing Promises by forcing clients to ask, #isRejected?  #isResolved?
>
>  - Chris
>
>
>
> On Sun, Jun 21, 2020 at 12:46 PM Jakob Reschke <[hidden email]> wrote:
>>
>> I am trying to come up with a nice solution for this failing test.
>>
>> Am So., 21. Juni 2020 um 19:27 Uhr schrieb <[hidden email]>:
>> >
>> > A new version of KernelTests was added to project The Inbox:
>> > http://source.squeak.org/inbox/KernelTests-jr.383.mcz
>> >
>> > ==================== Summary ====================
>> >
>> > Name: KernelTests-jr.383
>> > Author: jr
>> > Time: 21 June 2020, 7:27:47.560506 pm
>> > UUID: ecddb3cb-25c9-6c43-a879-59e527a3a9ee
>> > Ancestors: KernelTests-tonyg.382
>> >
>> > Test whether Notifications reject the promises of future sends
>> >
>> > In my opinion, they should not.
>> >
>> > =============== Diff against KernelTests-tonyg.382 ===============
>> >
>> > Item was added:
>> > + ----- Method: PromiseTest>>testFutureResolutionWithNotification (in category 'tests - future') -----
>> > + testFutureResolutionWithNotification
>> > +       | p |
>> > +       p := [Notification signal: 'should not reject'. 3 + 4] future value.
>> > +       self assert: (self waitUntil: [p isResolved or: [p isRejected]] orCycleCount: 100).
>> > +       self assert: p isResolved.
>> > +       self assert: 7 equals: p value.!
>> >
>> >
>>
>

Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: KernelTests-jr.383.mcz

Chris Muller-4
Hi Jakob,

Sorry for the delayed reply.  So many things right now!

I was sometimes confused about this too: future itself does not return
a Promise, but future + the following message send does. This is due
to the compiler transformation explained in FutureNode. So value will
not be sent to the Promise and p will hold the Promise right away and
not be nil.

Ah!  Sorry, you're right.  So "value" there is just to satisfy the compiler.  I guess the #future magic-trick kinda worked against its own "readability" in that specific case, huh?..  :)  (#yourself would be better form there, IMO).   

So, I agree that #future is confusing, but it is also much less to
read than p := Promise new. Project current addDeferredUIMessage:
[[... p resolveWith: ...] ifCurtailed: "or whatever we settle on" [p
reject]].

#future also breaks the formatter, which I use constantly and, since it has to assume the receiver might be a Morph, has to go through Project current addDeferredUIMessage:, MUCH slower than basic process #fork'ing, which is all I needed.
 
I don't think you ever could use Promises and values interchangeably.
Promise>>wait does more or less what you want, but it means you have
to use the promise explicitly, not interchangeably. You can chain
promises with then:[ifRejected:], and consume the Promise outcome in
the respective block argument, turning your control flow from
left-to-right-top-to-down into Promise style... But just getting the
value of the Promise at some point without wait might just answer nil
if still unresolved.

It essentially renders its #value message completely and utterly useless, since you can't discern between whether nil was the result or simply still running, without checking one of the state messages but... why?

That's why I overrode Promise>>#value to:

    ^ self wait

Interchangeability via #value also means that chaining "just works".  My code can always simply resolveWith: a value or a Promise of a value, and if its a Promise, the existing code chains it for me, without ever having to bring the #then: API into my code.
 

About signalling errors upon #value as you put it: you mean you want
to get the original Exception when you get the result of the promise,
so you don't have to check in which state the promise is? It sounds
useful at first, but the result or reason (for errors) can be
retrieved more than once; would the exception only be signalled
(again) upon the first retrieval of the result?

Yes.  And your program's error-handling can worry about its own errors only, not having to account for BrokenPromise..
 
Also it would look
funny in the Debugger stack: either you put the original stack of the
exception on top of the current (result getting) one, or you hide the
current stack, or you cut away the sender of the exception context
(which makes it less useful). Do you have an idea how to resolve this?

I think you can get the original stack from the error that's signaled.  I'd welcome a better multiprocess debugging tool, for sure, it can be difficult to figure out when things don't go as expected.
 
Also note that you can reject a promise with something else than an
Exception. It could also be some kind of error value or just a string
that explains the rejection.

Yes, but I don't use it that way, because I prefer the interchangeability.  Honestly, it brings all kinds of case-logic into the clients code.
 
You would not be able to distinguish that
from a regular result without type checking.

Current Promise doesn't even have a way to ask #isPending, you always  have to check both terminal states (#isResolved, #isRejected) usually with a "not" in there.  Ugh.
 
Maybe we could have a
specialized promise that only rejects on exceptions and would allow
the kind of workflow you seem to have in mind where the promise is
like a proxy for the result. If we determine that it is useful. :-)

I did subclass Promise in the GraphQL framework to do just that and, it worked beautifully.  Took me quite a bit of mind-wrangling to finally arrive at this simplicity, all the extra stuff I didn't need really took me on a mental ride around the barnyard just to figure out I didn't need it.    :)

During that ride, I did notice a minor inefficiency in Promise>>#wait -- for already-resolved Promises, it could simply check isResolved before going through the Semaphore.

Best,
  Chris