[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Anyone playing with higher order messaging in ruby?

Christoffer Lernö

3/14/2007 7:04:00 PM

For example, something that I often want to do is:

array.each { |entry| entry.do_something(1, "a") }

If you know you're doing things like this a lot, there is an obvious
shortcut:

class Array

class Forwarder
def initialize(array)
@array = array
end
def method_missing(symbol, *args)
@array.each { |entry| entry.__send__(symbol, *args) }
end
end

def each_do
Forwarder.new(self)
end

end

Now you can do:

array.each_do.do_something(1, "a")


In a more generalized case, you want to use a block to act as a class.

module Trampolines
class Trampoline
def initialize(block)
@block = block
end
def method_missing(symbol, *args, &block)
args << block if block
@block.call(symbol, *args)
end
end
end

def trampoline(&block)
raise "Missing block" unless block
Trampolines::Trampoline.new(block)
end


Here we could define


class Array
def each_do2
trampoline { |symbol, *args| each { |o| o.__send__(symbol,
*args) } }
end
end

Which would work the same way as the previous code.


But you could also do things like

def wait(time)
trampoline do |method, *args|
# queue_event executes the block after time seconds
queue_event(time) { self.__send__(method, *args) }
end
end

And write code like:

wait(5).shutdown("Shutdown in seconds")
wait(10).shutdown("Shutdown in 5 seconds")
wait(15).shutdown_now

Instead of wrapping it in blocks.


Other people must have played around with this.

I'd like to learn more about using these methods, so are there any
links and sites people could share with me?


/Christoffer



17 Answers

Christoffer Lernö

3/14/2007 7:39:00 PM

0

On Mar 14, 2007, at 20:04 , Christoffer Lernö wrote:

> In a more generalized case, you want to use a block to act as a class.
>
> module Trampolines
> class Trampoline
> def initialize(block)
> @block = block
> end
> def method_missing(symbol, *args, &block)
> args << block if block
> @block.call(symbol, *args)
> end
> end
> end
>
> def trampoline(&block)
> raise "Missing block" unless block
> Trampolines::Trampoline.new(block)
> end

This code adds methods to all enumerables.

module Enumerable
%w(each collect select reject).each do |method|
class_eval "def #{method}_do; trampoline { |m, b, *a| #{method}
{ |o| o.__send__(m, *a, &b) } }; end"
end
end

For example

["a", "b", "c"].reject_do[/a/] # => ["b", "c"]
["a", "b", "c"].collect_do.capitalize #=> ["A", "B", "C"]



benjohn

3/14/2007 10:48:00 PM

0


On 14 Mar 2007, at 19:57, Jason Roelofs wrote:

> I haven't personally messed with this kind of stuff but code like
> this just
> makes me love Ruby even more. These types of, well DSL is really
> the best
> way to describe it, in such little code, makes Ruby such a blast to
> program
> in, and it's readable too!

I think I must be getting too old, or I'm being a spring time Scrouge...

While it's neat, and it's cool Ruby can do that stuff, I've got to
ask "Why?".

I've come across this used in DSLs, but I generally think, "I'm sure
you're going to get in to trouble down the line. Why don't you just
use the regular syntax?"

I don't really see that it makes for an especially more concise
program, and really, it just seems to be introducing a different
syntax that's kind of crow bared in as an overloading of the current
syntax and semantics: as such, it doesn't seem to act in an expected
way. Frankly, it all seems reminiscent of some of the c++ template
techniques which, while terribly clever, don't generally seem to
actually achieve a lot that can't be done more simply in other ways;
and introduce so much complexity that even a seriously good coder's
complexity budget is nearly all used up with a very small problem.

No, you're right, I am getting old ;-)



Christoffer Lernö

3/15/2007 6:14:00 AM

0

On Mar 14, 2007, at 23:48 , Benjohn Barnes wrote:

>
> On 14 Mar 2007, at 19:57, Jason Roelofs wrote:
>
>> I haven't personally messed with this kind of stuff but code like
>> this just
>> makes me love Ruby even more. These types of, well DSL is really
>> the best
>> way to describe it, in such little code, makes Ruby such a blast
>> to program
>> in, and it's readable too!
>
> I think I must be getting too old, or I'm being a spring time
> Scrouge...
>
> While it's neat, and it's cool Ruby can do that stuff, I've got to
> ask "Why?".
>
> I've come across this used in DSLs, but I generally think, "I'm
> sure you're going to get in to trouble down the line. Why don't you
> just use the regular syntax?"
>
> I don't really see that it makes for an especially more concise
> program, and really, it just seems to be introducing a different
> syntax that's kind of crow bared in as an overloading of the
> current syntax and semantics: as such, it doesn't seem to act in an
> expected way. Frankly, it all seems reminiscent of some of the c++
> template techniques which, while terribly clever, don't generally
> seem to actually achieve a lot that can't be done more simply in
> other ways; and introduce so much complexity that even a seriously
> good coder's complexity budget is nearly all used up with a very
> small problem.
>
> No, you're right, I am getting old ;-)

For code like this:

subscribers.send_message(:chat, @current_user, chat_text)

Instead of

subscribers.each { |s| s.add_to_queue(:send_message, [:chat,
@current_user, chat_text]) }

It is also useful for transactions, where you can define a trampoline
like this (writing this code in my mail program, it probably has bugs):

def transaction
queue = []
$exec = trampoline { |method, *args| @queue << [method, args] }
yield
queue.each do |method, args|
__send__(method, *args)
end
end

def exec
$exec
end


Now you write code like this

transaction do
do_something
exec.start_system1
do_something_else
raise "Some error" if (something_failed)
exec.start_system2
end

If the transaction works, both start_system1 and start_system2 is
called. Otherwise neither happens.

It helps making the code straightforward, especially if the
start_system calls are IO or similar that is difficult to reverse.


/Christoffer


Rick DeNatale

3/15/2007 8:28:00 PM

0

On 3/14/07, Benjohn Barnes <benjohn@fysh.org> wrote:
>
> On 14 Mar 2007, at 19:57, Jason Roelofs wrote:
>
> > I haven't personally messed with this kind of stuff but code like
> > this just
> > makes me love Ruby even more. These types of, well DSL is really
> > the best
> > way to describe it, in such little code, makes Ruby such a blast to
> > program
> > in, and it's readable too!
>
> I think I must be getting too old, or I'm being a spring time Scrouge...
>
> While it's neat, and it's cool Ruby can do that stuff, I've got to
> ask "Why?".
>
> I've come across this used in DSLs, but I generally think, "I'm sure
> you're going to get in to trouble down the line. Why don't you just
> use the regular syntax?"

Yes it's powerful, and it's useful, but it needs to be done with
thought and care. We used to call similar things like this in
Smalltalk "stupid Smalltalk tricks." It's like any other tool, it can
be used for good or evil, it's the workman who determines this.

I just blogged about some of the ramifications of this this morning.
(see my signature for the URL of my blog).

Rails makes use of this and similar techniques a lot. Two examples,
which I've recently encountered reading the rails code:

1) Named routes. Normally you set up routes which represent mappings
between urls and actions and their parameters with the connect method
of an internal class called ActionController::Routing::RouteSet in
code like this, in your config/routs.rb file:

ActionController::Routing::Routes.draw do | map |
map.connect ':controller/xyz/:arg1/:arg2'
end

Routes are used both to map an incoming url to an controller, action
and parameters, and also to map the other way to generate a url.
Routes defined above are anonymous, when asked to generate a url it
picks which route to use itself. But you can also name routes so that
you can explicitly give a particular route to use when generating a
url. Let's say you want to
name that route above xyz. To do this you use the method xyz instead
of connect:

map.xyz 'controller/xyz/:arg1/:arg1'

which does the same thing as above but also defines a helper method
called xyz_url.

Of course the mechanism used to do this is to catch :xyz with
method_missing and turn it into a call to named_route passing the name
(:xyz) and any other parameters, which in turn invokes connect and
then creates the helper.

2) mime content negotiation

An action might be able to respond to a request with more than one
mime-type. An HTTP request can give a prioritized lists of mime-types
which the client will accept. The way to handle this negotiation in
an action is to use the respond_to method you might have code in an
action like:

respond_to do |wants|
wants.html { redirect_to(person_list_url) }
wants.js
wants.xml { render :xml => @person.to_xml(:include => @company) }
end

What this code says is something like:
if the requester wants html redirect to the person_list_url
if the requester wants java script perform the normal rendering
for javascript (such as look for an rjs template with the right name
and render that)
etc.

It's kind of like a case statement, except what really happens also
depends on which of those alternatives came first in the requests list
of acceptable types.

Now what happens under the covers is that the respond_to method
creates an ActionController::MimeResponds::Responder object which
becomes map in the code above. Then it yields to the block. The html
method adds html to the list of available mime types, along with the
block, or as in the case of the invocation of the js method where no
block is given, a default block. Then once the block given to
respond_to returns, the Responder is asked to respond by executing the
first block found using the list of requested mime-types.

In Rails 1.2, ActionController::MimeResponds:Responder uses
method__missing to support new mime types not 'baked in' to Rails.

These are just two examples from Rails, there are many more.

It's good to read such carefully crafted code and understand the
techniques and the possibilities, but I'd caution about getting too
carried away with such things before you are ready, and most folks
thing they are ready before they really are. <G>

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

Robert Dober

3/15/2007 9:00:00 PM

0

--------------------- 8< ---------------------

module Enumerable
class MapProxy
def initialize obj
@obj = obj
end #def initialize obj
def method_missing name, *args, &blk
@obj.map do
| ele |
ele.send name, *args, &blk
end
end #def method_missing name, *args, &blk
end #class MapProxy

alias_method :aliased_map, :map
def map *args, &blk
return MapProxy.new( self ) if args.length.zero? && blk.nil?
aliased_map *args, &blk
end #def map *args, &blk
end #module Enumerable

---------------- 8< --------------------

This is from a thread of 2006-09-27 posted the 28th, looks familiar
it was about "for or against the magic dot notation" but the
functionality is the same :)

the French say: "Les grands esprits se resemblent", but my modesty
forbids any translations ;)

Cheers
Robert

Brian Candler

3/16/2007 6:26:00 AM

0

On Thu, Mar 15, 2007 at 04:04:01AM +0900, Christoffer Lern? wrote:
> For example, something that I often want to do is:
>
> array.each { |entry| entry.do_something(1, "a") }

If do_something doesn't take any arguments then there's the hack which
allows

array.each(&:do_something)

See http://pragdave.pragprog.com/pragdave/2005/11/symbolto...

> If you know you're doing things like this a lot, there is an obvious
> shortcut:
>
> class Array
>
> class Forwarder
> def initialize(array)
> @array = array
> end
> def method_missing(symbol, *args)
> @array.each { |entry| entry.__send__(symbol, *args) }
> end
> end
>
> def each_do
> Forwarder.new(self)
> end
>
> end
>
> Now you can do:
>
> array.each_do.do_something(1, "a")

or just:

module Enumberable
def each_do(meth, *args, &blk)
each { |item| item.send(meth, *args, &blk) }
end
end

array.each_do(:do_something, 1, "a")

which has the advantage that do_something can take a block too, if you wish.

B.

Robert Klemme

3/16/2007 9:11:00 AM

0

On 15.03.2007 21:28, Rick DeNatale wrote:
> 2) mime content negotiation
>
> An action might be able to respond to a request with more than one
> mime-type. An HTTP request can give a prioritized lists of mime-types
> which the client will accept. The way to handle this negotiation in
> an action is to use the respond_to method you might have code in an
> action like:
>
> respond_to do |wants|
> wants.html { redirect_to(person_list_url) }
> wants.js
> wants.xml { render :xml => @person.to_xml(:include =>
> @company) }
> end
>
> What this code says is something like:
> if the requester wants html redirect to the person_list_url
> if the requester wants java script perform the normal rendering
> for javascript (such as look for an rjs template with the right name
> and render that)
> etc.
>
> It's kind of like a case statement, except what really happens also
> depends on which of those alternatives came first in the requests list
> of acceptable types.

That is no difference to case statements: with those, order matters as well:

>> x="foo"
=> "foo"
>> case x
>> when /fo/; puts 1
>> when /oo/; puts 2
>> else puts 0
>> end
1
=> nil
>> case x
>> when /oo/; puts 2
>> when /fo/; puts 1
>> else puts 0
>> end
2
=> nil

I think the major difference (apart from syntactical aspects) is the
execution time. Of course this could also be mimicked with a case
statement by returning a Proc but the Rails version looks much more elegant.

> It's good to read such carefully crafted code and understand the
> techniques and the possibilities, but I'd caution about getting too
> carried away with such things before you are ready, and most folks
> thing they are ready before they really are. <G>

Agreed. ;-)

Kind regards

robert

Christoffer Lernö

3/16/2007 11:20:00 AM

0


On Mar 16, 2007, at 07:25 , Brian Candler wrote:

>> If you know you're doing things like this a lot, there is an obvious
>> shortcut:
>>
>> class Array
>>
>> class Forwarder
>> def initialize(array)
>> @array = array
>> end
>> def method_missing(symbol, *args)
>> @array.each { |entry| entry.__send__(symbol, *args) }
>> end
>> end
>>
>> def each_do
>> Forwarder.new(self)
>> end
>>
>> end
>>
>> Now you can do:
>>
>> array.each_do.do_something(1, "a")
>
> or just:
>
> module Enumberable
> def each_do(meth, *args, &blk)
> each { |item| item.send(meth, *args, &blk) }
> end
> end
>
> array.each_do(:do_something, 1, "a")
>
> which has the advantage that do_something can take a block too, if
> you wish.

Well, actually you might want to pass around the result of each_do to
something else, this is why I believe that in most cases the former
version is preferable.

Consider a chat server where @clients = [connection1, connection2...]

assuming each connection has a method like "broadcast_chat", the code
looks like this:

@clients.each_do.broadcast_chat(incoming_chat)

I like it because it feels clear what is happening and that this is
the same as

@clients.each { |c| c.broadcast_chat(incoming_chat) }

Here

@cliente.each_do(:broadcast_chat, incoming_chat)

is not as obvious when it comes to detemining what is the action and
what is the data.

Christoffer Lernö

3/16/2007 11:31:00 AM

0

On Mar 14, 2007, at 23:48 , Benjohn Barnes wrote:

> On 14 Mar 2007, at 19:57, Jason Roelofs wrote:
>
>> I haven't personally messed with this kind of stuff but code like
>> this just
>> makes me love Ruby even more. These types of, well DSL is really
>> the best
>> way to describe it, in such little code, makes Ruby such a blast
>> to program
>> in, and it's readable too!
>
> I think I must be getting too old, or I'm being a spring time
> Scrouge...
>
> While it's neat, and it's cool Ruby can do that stuff, I've got to
> ask "Why?".

And another example:

# A chat server where @clients = [connection1, connection2...]
# message = "Hello"
# sender = "Bob"

# With normal each:
packet = BroadcastChatPacket.new("#{sender} says: #{message}")
@clients.each { |c| c.broadcast(packet) }

# With each_do:
@clients.each_do.broadcast(BroadcastChatPacket.new("#{sender} says: #
{message}"))

Eliminating the need for the temporary variable.

/Christoffer

Robert Dober

3/16/2007 12:22:00 PM

0

On 3/16/07, Christoffer Lernö <lerno@dragonascendant.com> wrote:
> On Mar 14, 2007, at 23:48 , Benjohn Barnes wrote:
>
> > On 14 Mar 2007, at 19:57, Jason Roelofs wrote:
> >
> >> I haven't personally messed with this kind of stuff but code like
> >> this just
> >> makes me love Ruby even more. These types of, well DSL is really
> >> the best
> >> way to describe it, in such little code, makes Ruby such a blast
> >> to program
> >> in, and it's readable too!
> >
> > I think I must be getting too old, or I'm being a spring time
> > Scrouge...
> >
> > While it's neat, and it's cool Ruby can do that stuff, I've got to
> > ask "Why?".
>
> And another example:
>
> # A chat server where @clients = [connection1, connection2...]
> # message = "Hello"
> # sender = "Bob"
>
> # With normal each:
> packet = BroadcastChatPacket.new("#{sender} says: #{message}")
> @clients.each { |c| c.broadcast(packet) }
>
> # With each_do:
> @clients.each_do.broadcast(BroadcastChatPacket.new("#{sender} says: #
> {message}"))
>
> Eliminating the need for the temporary variable.
>
Do I understand correctly when I remark that a new BroadcastChatPacket
instance is created or each iterator call in #each_do.broadcast,

If not than it is just the same as
@clients.each{ |c| c.broadcast BCP.new... }

in this context I do not really see the practical interest though I
*obviously* like that style as I have shown above.

Well 6 months is a long time, but no I still like this magic dot notation.

Cheers
Robert
> /Christoffer
>
>


--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw