Wednesday, July 12, 2006

Thinking of perform:

I'm sure you've seen this pattern before:

    (anObject respondsTo: #message)
      ifTrue: [anObject message]
      ifFalse: [anObject somethingElse]

How could we change it so it is not as redundant? Like this, for example.

    anObject
      perform: #message
      ifNotUnderstood: [anObject somethingElse]

To keep the implementation nice, I thought of the following:

    Object>>perform:ifNotUnderstood:

      < primitive: xyz >
      ^aBlock value

If you didn't have a primitive, then there is always the #respondsTo: stuff*. But at least you would be able to write that piece only once :).

* Question: instead of the primitive, could you use on: MessageNotUnderstood do: [:ex | ex return: aBlock value]?

3 comments:

Reinout Heeck said...

As to your question: you could use MessageNotUnderstood but you'll have to determine wether the MNU came from the perform, IOW look if the exception came from (thisContext sender). Along the same lines I generally don't use (ex return: aBlock value) in exception handlers but rather do a nonlocal return (^aBlock value) so I know where I return to instead of 'some' place where an exception got raised. Wrapping an exception handler around a piece of code has a runtime penalty, in tight loops it may be worthwile to hoist the handler outside the loop if possible.

Andres said...

Indeed, trick question... using an exception handler could trap exceptions deeper than intended, and this would hide defects away. The same can be said of on: Error do: [:ex | ... ].

Interestingly, however, the use of non local returns should be avoided as its behavior depends on the implementation of the exception framework. I have not found a case in which using ^returns breaks VisualWorks, but I had to debug SUnit on Squeak and it turned out that exception handlers doing ^returns were messing the exception handling mechanism up.

Lesson: ^returns in exception handlers are evaluated in the process that handles the exception, not in the process that caused the exception. Always handle the exception by sending it return, resume, or any other handling message.

Marcel Weiher said...

The case without the alternative has a clean solution with a Higher Order Message (HOM)

    anObject ifResponds doSomething.

This will send #doSomething to anObject if and only if anObject can respond to it.  As it is a HOM, the doSomething message can take arbitrary arguments.