In Smalltalk, you can “cascade” side-effectful calls to the same object using the semicolon (;) operator. E.g.:
[gist id=”1238072″]If I understand it correctly, the semicolon is effectively a K-combinator or “Kestrel”.
I am jealous. Sure, we have Object#tap, but that’s awfully verbose by comparison:
[gist id=”1238083″](I’m using multiple taps in order to exactly mimic the semantics of the Smalltalk semicolon operator in the example above.)
Let’s see if we can do better for this simple case of cascading a series of commands, while ignoring their return values.
[gist id=”1238090″ bump=1]Yay! That looks a lot nicer.
Nice! I would just drop the extra ‘return’ usage in the Ruby version to make it a bit more idiomatic.
Yeah that was a leftover from an earlier version. Oops.
This isn’t exactly the semantics of Smalltalk’s cascade though.
The semantics of
rcvr msg1;msg2
is the same as
rcvr msg1
rcvr msg2
and the result is the value returned by rcvr msg2
It’s for this reason that theres’ a method in Object
yourself
^self
which is used idiomatically in cases like intialiizing a new object:
realWivesOfBedrock := Dictionary new;
at: “Fred’ put: ‘Wilma’;
at: ‘Barney’ put: “Betty’;
yourself
For a ruby built-in near equivalent to cascading, we might consider instance_eval which sets self in the evaluated block:
require ‘stringio’
x = 2; y = 42
p StringIO.new.instance_eval{
print(x)
print(‘ @ ‘)
print(y)
self
}.string
which produces:
“2 @ 42”
One minor annoyance here is if we want to invoke assignment methods (e.g. an attr_writer generated method) we need to explicitly use self as a receiver in the block to distinguish between a block temporary variable and a method call.
Sorry, but I can’t seem to control the indentation in my comment the way I want it. I hope that the point comes across.
Some more comments on the semantics of cascade.
The canonical ‘Blue Book’ implementation/definition of Smalltalk is based on a stack oriented byte code virtual machine
So a single method send
aStream print: xmight be compiled to something like the following (simplified) byte codes: push aStream # push the receiver – stack is: aStream push x # push the argument – stack is: x aStream send #’print:’ # send the message – stack is now: [result of the message]The send pops the message selector, any arguments and the receiver from the top of the stack and replaces them with the result.The cascade would compile to something like:
aStream print: x; nextPutOn: ‘ @ ‘; print: y push aStream # stack is: aStream dup # stack is: aStream aStream push x # stack is: x aStream aStream send #’print:’ # stack is: [result] aStream pop # discard the result of the first message # stack is: aStream dup # stack is: aStream aStream push ‘ @ ‘ # stack is: ‘ @ ‘ aStream aStream send #’nextPutOn:’ # stack is: [result] aStream pop # stack is: aStream push y # stack is: y aStream send #’print:’ # stack is: [result of last message]
Thank you for all this great, great commentary! May I add your comments to the source file for this article over at https://github.com/avdi/sbpprb ?
Thanks Rick… I was coming in here specifically to say what you said about the receiver.