So here’s the spiel: Object-oriented programming is all about sending messages.
See, given some object, you don’t directly call methods; oh no, you send messages, like asking a favor; “Hey, object, do you think, like, you could, ….”, and the object gets to decide if it wants do you the favor or not.
As a practical matter, the distinction between methods and messages varies among OO languages. For example, with Java, code using the standard message-invocation syntax will only compile if the message directly corresponds to a method.
I picked the double-angle syntax as something of a visual indicator of transmission. My original version used block notation for passing arguments; this followed the list of receivers. But while writing this I decided it didn’t look very good, and, more important, didn’t seem to correctly convey its role. So I added the [] method. The basic goal was to have something that made some sort of visual sense. YMMV, blah blah blah, this is an experiment. Syntax aside, the neat stuff is what happens inside Symbol. In the basic case, something like
My approach is to to allow the list of receivers to include Proc objects. Each Proc would need to define a conditional to determine if an object should be included in the receiver list. If, while processing the
foo.someMessage() // Compiler says the class behind foo must have a someMessage method
In Ruby, on the other hand, you are free to send pretty much any message you choose; the receiver does not need a message-method mapping in order to handle it.
foo.some_message # Code behind foo may or may not have a some_message method.
In the extreme case, you can have a Ruby class that handles all messages:
class Pushover def method_missing( sym, *args ) puts "Do I want to #{sym.to_s.gsub( '_', ' ' )}? Sure!" end end po = Pushover.new po.buy_a_fake_rolex po.make_money_fastBut even though Ruby affords this complete disconnect between messages and methods, most people code with the view that messages map to methods, or some well-defined set of message-based behavior. Maybe because coding pushover classes opens one up to too many risks. (But note that, in the right hands, you get great results .) I wonder, though, if part of the reason is the syntax itself, coupled with the phrase “object-oriented”. Alan Kay, creator of Smalltalk, has said .
Again, the whole point of OOP is not to have to worry about what is inside an object. Objects made on different machines and with different languages should be able to talk to each other—and will have-to in the future. Late-binding here involves trapping incompatibilities into recompatibility methods—a good discussion of some of the issues is found in [Popek 1984].He’s also reported as saying, “Smalltalk is object-oriented, but it should have been message oriented.“ From a certain point of view, while objects handle the gory details, it’s the message exchange that’s really key. But objects always seem to be the center of attention; they always come first:
big_shot_object.poor_message_comes_last
In fact, you can’t even think about sending a message without first having an object.
Or can you?
Let’s see. First, we’ll need some new syntax. I’m not yet loopy enough to go invent my own language, so we’ll munge up Ruby code for our purposes. So this may not look as slick and clean as we might like. But, instead of the familiar
receiver.message( arg1, arg2 )
we’ll use flip things around a bit , and use
:my_message.>>( receiver1, receiver2 )
What this syntax means is, “Send the message my_message
to all of the listed receivers.”
And this
:my_message[ arg1, arg2, arg3 ].>>( receiver1, receiver2 )
means the same thing, but also passing arg1, arg2, etc. as message arguments.
Oh, and before you wig out over funky syntax or the violation of the sanctity of Symbols or countless other details, know that much of what drives me to code (or do much of anything, for that matter) is an attitude of, “Gee, what happens if I push this button?”
So there’s an element of the, um, unpolished and experimental in the mix. (Though I do expect that admissions of this sort merely prompt many readers to leer and start rubbing their hands as the imagination revs.)
Message-oriented Programming
What I wanted to explore was some form of message-oriented programming, where one could start with a message, then decide on a set of receivers. I thought of different ways to write this, and settled on using Symbols because I wanted something that afforded a decent literal syntax (e.g., “my string”, [ :my, :array ], 123). The idea of having to instantiate an instance of aMessage
class before using one seemed clumsy. (String literals were my first choice, but having to type all those quote marks got tiresome, and it tended toward the fugly side of things.)
Symbols are also handy because they don’t actually do anything. There have very few methods, and are unlikely to pop up in unexpected places where the results of a classotomy might cause conflict. Adding new methods to the Symbol class seemed reasonably safe.
I picked the double-angle syntax as something of a visual indicator of transmission. My original version used block notation for passing arguments; this followed the list of receivers. But while writing this I decided it didn’t look very good, and, more important, didn’t seem to correctly convey its role. So I added the [] method. The basic goal was to have something that made some sort of visual sense. YMMV, blah blah blah, this is an experiment. Syntax aside, the neat stuff is what happens inside Symbol. In the basic case, something like
:message.>>( recv1, recv2 )
would be translated somehow into
recv1.send( :message )
recv2.send( :message )
(And I of course if there are any arguments involved they’d get passed along, too.)
Of course, this raises a new issue: what is the return value, if any, of a cross-receiver message dispatch? I picked an array (and we’ll be getting to implementation details shortly). So, send a message to some number of receivers, and get back an array of response values.
Shooting in the Dark
I also wondered about possible use cases. If you already have a list of known receivers handy, then you might simply write a loop to do the message sending. But what if you wanted to send a message to some unknown set of objects, a set where the recipients are defined by some property or behavior? For example, imagine your application has all sort of IO-like objects floating around. You have no obvious way to locate them all, but you’d like to tell each of them to close, perhaps in preparation of some shutdown command.My approach is to to allow the list of receivers to include Proc objects. Each Proc would need to define a conditional to determine if an object should be included in the receiver list. If, while processing the
>>
command, a Proc is encountered, the code loops over all objects in ObjectSpace, using the Proc to determine if an object qualifies as a receiver.
Code, please
So here we go:class Symbol def []( *args) @args = args self end def >>( *objs ) results = [] arglist = @args objs.each{ |obj| begin if obj.class == Proc temp_ary = select_by_proc( obj ) results.concat( self.>>( *temp_ary ){ arglist } ) else results << dispatch( obj, arglist ) end rescue Exception results << $!.clone end } # Symbols stick around like a bad cold, so we need to reset the # arg list after dispatching the message @args = nil results end def dispatch( obj, arglist ) return obj.send( self.to_s, *arglist ) if arglist && ( arglist.size > 0 ) obj.send( self.to_s ) end def select_by_proc( pk ) objs = [] ObjectSpace.each_object do |obj| begin objs << obj if pk.call( obj ) rescue Exception warn "select_by_proc exception: #{$!}" end end objs.uniq end end
Pass me the MOP, please
So let’s see an example. Earlier we looked at dynamic code that might a bit too easygoing. Now let’s look at some really gullible code:class Gullible def initialize( name ) @name = name end def herbal_cialas "Sure, I respond to herbal cialas!" end def bank_account_details "@name: #{@name.intern.to_i}" end endSuppose then that there are a few gullible objects floating around:
jim = Gullible.new( 'Jim' ) greg = Gullible.new( 'Greg' )Now, if we decided to engage in some object-level spamming, we could really, um, mop up; we don’t need to know who these poor souls are, we just need to go find all objects that say they respond to messages about, say, herbal cialas, and ask them a favor, such as “Give me your bank account details”. Like so:
poor_souls_accounts = :bank_account_details.>>( lambda { |o| o.respond_to?( :herbal_cialas ) }) p poor_souls_accounts # ["Greg: 17221", "Jim: 17229"]>Now I just need to figure out spam filters. Shouldn’t be too hard.