Anyone playing with higher order messaging in ruby?

C

Christoffer Lernö

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
 
C

Christoffer Lernö

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

module Trampolines
class Trampoline
def initialize(block)
@block =3D 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} =20
{ |o| o.__send__(m, *a, &b) } }; end"
end
end

For example

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

=20
=20=
 
B

Benjohn Barnes

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 ;-)
 
C

Christoffer Lernö

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
 
R

Rick DeNatale

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>
 
R

Robert Dober

--------------------- 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
 
B

Brian Candler

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_proc.html
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.
 
R

Robert Klemme

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:
1
=> nil2
=> 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
 
C

Christoffer Lernö

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.
 
C

Christoffer Lernö

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
 
R

Robert Dober

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 =3D [connection1, connection2...]
# message =3D "Hello"
# sender =3D "Bob"

# With normal each:
packet =3D 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


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

Christoffer Lernö

And another example:

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

# With normal each:
packet =3D 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,

No, each_do will only create BroadcastChatPacket once.

/Christoffer
 
J

Jenda Krynicky

Christoffer said:
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

Looks to me that it's easier to extend the Forwarder a bit and implement
this like this:

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

module Enumerable
%w(each collect select reject).each do |method|
class_eval "def #{method}_do; Forwarder.new(self, :#{method}) end"
end
end


it's a shame the builtins do not work like this already. And while it's
possible to overwrite the "collect", "select" and "reject" in
Enumerable, to overwrite the "each" you'd (AFAIK) have to touch all the
individual classes.

I should not have to name something I only use once:

list.each { |x| x.whatever() }

Jenda
 
G

Gary Wright

it's a shame the builtins do not work like this already. And while
it's
possible to overwrite the "collect", "select" and "reject" in
Enumerable, to overwrite the "each" you'd (AFAIK) have to touch all
the
individual classes.

Somewhat related....

In Ruby 1.9 iterating methods (I couldn't think of a better term) such
as each, map, and so on return an Enumerator object if a block isn't
provided. This makes it easier to add the magic dot behavior:

class Enumerable::Enumerator
def method_missing(*args, &block)
each { |x| x.send(*args, &block ) }
end
end

[1,-2,3,-4].map.abs # [1,2,3,4]
[1,-2,3,-4].map.abs.map.succ # [2,3,4,5]
%w{mon tue wed}.map.capitalize # ["Mon", "Tue", "Wed"]`

This works because Enumerable::Enumerator#each returns the original
collection (instead of the iterator) when a block is provided.

[1,2].each.class # Enumerator
[1,2].each.each.class # Enumerator
[1,2].each.each {}.class # Array


Gary Wright
 
R

Robert Dober

And another example:

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

# With normal each:
packet =3D 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,

No, each_do will only create BroadcastChatPacket once.

/Christoffer
Sure got confused it is evaluated in method_missing only once, good,
thx for clarifying, I see the practical benefit now too.

Robert


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

Simen Edvardsen

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

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

[...]

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

I wrote this some time ago:

class Message
def initialize(message, *args)
@message, @args =3D message, args
end

def call(x, *args)
x.send @message, *(@args + args)
end

def to_proc
lambda { |*args| call(*args) }
end
end

Now you can do:

puts [*0..100].select(&Message.new:)>, 50)).inspect
puts [*0..100].inject(&Message.new:)+))
puts %w(Once upon a time).map(&Message.new:)upcase)).inspect
puts Message.new:)index, /[aeiou]/, -3).call("hello")
puts %w(1 2 3 4 5).map(&Message.new:)to_i)).map(&Message.new:)class)).ins=
pect

The &Message.new syntax is a bit verbose, so you could alias it:

def _(msg, *args)
Message.new(msg, *args)
end

And then it runs smooth:

puts [*0..100].select(&_:)>, 50)).inspect

I don't know if this helps. It does do away with the need for
method_missing or changing core classes or stuff like that. I don't
know how useful it is, because I've never used it for anything
serious. If it was to be really useful, it should've been incorporated
into the language and standard libraries.

--=20
- Simen
 
R

Rick DeNatale

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

Usually rendered in English as "Great minds think alike."

Then again I had a good friend from England some years ago who never
failed to follow that phrase with "or fools fail to differ!" <G>
 
R

Robert Dober

Usually rendered in English as "Great minds think alike."

Then again I had a good friend from England some years ago who never
failed to follow that phrase with "or fools fail to differ!" <G>
I can still learn from your modesty :))
But sigh Rick you see nobody cares for my code I will have to sell it
to Microsoft ;)
And as an eventual side remark - after the defeat at Twickenham ARGHHH
- it is good to know that Englishmen can have friends too. <ducking>
no <digging a hole>
but what do I fear? they have the best humor of the world, really,
like e.g. Monthy Ruby's

... ok I guess I need some sleep now.

Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top