I'm having trouble getting the sender of a message in SmallTalk. What I want to accomplish is to modify the return value of a method (A) from another method (B) which is called by the first one (A). Again... A calls B, and I want B to return a value from A's context.
Example code:
This would be A:
A
| aResult aPartialResult |
aPartialResult := self B.
"do things with aPartialResult"
^aResult.
And this would be B:
B
| aResult |
[ aResult := "do something" ]
on: Exception
do: ["make A return something"].
^aResult.
The thing is that I want to the exceptions that could be raised in B to be handled in B too. That's why I don't just raise an exception in B to handle it in A and easily return from there.
I thought I would be able to do this using thisContext, but the sender is nil. It wouldn't hurt to also get an answer on why is that...
Thanks in advance!
Guillermo, Exception handling can seamlessly replace a few bad ideas here:
Also, retrieveCart:onErrorReturnFrom: is doing too much. With all the error handlers, the actual logic gets lost.
So, the first thing I would to is create Error subclasses to represent your domain concepts eg AddBookError, CartExpiredError, InvalidCartError
Then you just set the error message, maybe like:
CartExpiredError>>initialize
super initialize.
self messageText: '1|', UserInterface cartHasExpiredErrorMessage.
The next thing (really two steps) is to replace the raw dictionary methods with private accessors, which can use your new Error classes, like so:
timestampFor: aCartId
^ cartCreationDateAndTime at: aCartId ifAbsent: [ InvalidCartError signal ].
and
cartNumber: aCartId
^ carts at: aCartId ifAbsent: [ InvalidCartError signal ].
Cart>>add: aQuantity booksWithISBN: aBookISBN
fail ifTrue: [ AddBookError signal ].
Now, retrieveCart:onErrorReturnFrom: can become:
retrieveCart: aCartId
| aCartCreationDateAndTime |
aCartCreationDateAndTime := self timestampFor: aCartId.
Time now > (aCartCreationDateAndTime + 30 minutes) ifTrue: [ CartExpiredError signal ].
^ self cartNumber: aCartId.
And finally, the greatly-simplified A becomes:
add: aQuantity booksWithISBN: aBookISBN toCart: aCartId
| aCart |
[aCart := self retrieveCart: aCartId.
aCart add: aQuantity booksWithISBN: aBookISBN]
on: Error
do: [ :e | ^ e messageText ].
^ '0|OK'.
This can still be cleaned (eg Make a superclass for all the Error classes that prepends '1|' to the messageText), and obviously you will have to work this simplified version into your actual project, but can you start to see how exceptions can make your life easier?
Here is a working mockup of the code, with passing tests on github
nb The one other thing I noticed was aCartCreationDateAndTime. It would seem more natural to have this be a property of the cart, but maybe that doesn't make sense in the actual application...
A simple way is that A pass a Block with a return inside to B, like :
A
| aResult aPartialResult |
aPartialResult := self BonSpecialConditionDo: [:partial | ^partial].
...snip...
Then
BonSpecialConditionDo: aBlock
| partialResult |
partialResult := self doSomethingPartial.
^[self doSomething]
on: SomeException
do: [:exc | aBlock value: partialResult]
Beware, catching Exception is considered dangerous (you catch too many things).
EDIT: I just removed an un-necessary return ^ inside the handler
EDIT: doing it with superpowers (but don't kill a fly with a hammer)
B
| partialResult |
partialResult := self doSomethingPartial.
^[self doSomething]
on: SomeException
do: [:exc | thisContext home sender home return: partialResult ]
I thought you could access thisContext through the Exception exc (this is the instance variable handlerContext), but there does not seem to be any convenient message for accessing this internal state...
Got it!
Here's A:
A
| aResult aPartialResult |
aPartialResult := self BOnErrorReturnFrom: thisContext.
"do things with aPartialResult"
^aResult.
And this would be B:
BOnErrorReturnFrom: aContext
| aResult |
[ aResult := "do something" ]
on: Exception
do: [aContext return: "whatever you want :)"].
^aResult.
It wasn't so hard after I realized that the keyword "thisContext", when used in a BlockClosure, doesn't return the ContextPart in which the block was declared but something else (still not sure what it is, though).
In response to Sean:
What I'm attempting to do with this is to avoid repetition of code. The object which implements A and B is the inner (model side) part of a REST interface, so I want it to return a string and only a string (I don't want exceptions nor anything different than a string object to get through). In my specific problem, A (sorry for breaking the Smalltalk message naming convention, but I think that changing that now will lead to further confusion...) receives a cart ID and will do something with that cart. A has to retrieve the cart using that ID, make some validations on the cart and, in case of error, return an ad hoc message (always as a string). This retrieval-and-validation code will be repeated in each message that has to retrieve a cart.
This is the proper code I finally got:
This is A: (Pay no attention to the #try message. It just assures that no exception gets out without being converted to a string. If anyone knows how to do this in a better way, please tell me how!)
add: aQuantity booksWithISBN: aBookISBN toCart: aCartId
| aCart aContext |
aContext := thisContext.
^self try: [
aCart := self retrieveCart: aCartId onErrorReturnFrom: aContext.
[aCart add: aQuantity booksWithISBN: aBookISBN]
on: Error
do: [ :anError | ^'1|', (self formatAsResponse: anError messageText) ].
^'0|OK'.
].
This is B:
retrieveCart: aCartId onErrorReturnFrom: aContext
| aCartCreationDateAndTime aCart |
[aCartCreationDateAndTime := cartCreationDateAndTime at: aCartId asInteger.]
on: KeyNotFound
do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage)).].
(systemClock now > (aCartCreationDateAndTime + 30 minutes))
ifTrue: [aContext return: ('1|', (UserInterface cartHasExpiredErrorMessage))].
[aCart := carts at: aCartId asInteger.]
on: KeyNotFound
do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage))].
^aCart.
A
| aResult aPartialResult |
aPartialResult := self B.
"do things with aPartialResult"
^aResult.
B
| aResult |
[ aResult := "do something" ] on: Exception do: ["make A return something"]. ^aResult.
i would write it as
A
| aResult aPartialResult |
aPartialResult := self B. aPartialResult ifNotNil:[
"do things with aPartialResult"].
^aResult.
B
| aResult |
[ aResult := "do something" ] on: Exception do: [:e|^nil]. ^aResult.
well then thats just me!!!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.