Pass block instead of here document?

M

Morton Goldberg

I have a situation where I want to send many messages, one after the
other, to single object. Instead of writing, say,

foo.eat burger, fries
foo.drink beer
foo.be_merry

I want to be able to write something like:

foo.perform { eat burger, fries; drink beer; be_merry }

I'm not much at meta-programming. The best I've been able to come up
with so far is:

<code>
#! /usr/bin/env ruby -w

class Foo
def report
defined_here = (methods - Object.methods).join(", ")
puts "#{self.class.name} defines: " + defined_here
end
def say(text)
puts %[#{inspect} says "#{text}"]
end
def eat(*food)
food = food.join(" and ")
say "Downing #{food}. Yum, yum, yum!"
end
def drink(beverage)
say "Chug, chug, chug"
end
def be_merry
say "What, me worry?"
end
end

module Kernel
def tell(obj, to_do)
obj.instance_eval(to_do)
end
end

tell Foo.new, <<TO_DO
report
eat 'burger', 'fries'
drink 'beer'
be_merry
TO_DO
</code>

Although this works, it offends my sense of programming esthetics.
I'd rather pass in a block. Is there a way to do that? Something like:

<pseud-code>
module Kernel
def tell(obj, &to_do)
# what goes here?
end
end

tell Foo.new do
report
eat 'burger', 'fries'
drink 'beer'
be_merry
end
</pseud-code>

Instead of a Kernel#tell method, a Foo#perform method would be OK:

<pseud-code>
Foo.new.perform { report; eat 'burger', 'fries'; drink 'beer';
be_merry }
</pseud-code>

Regards, Morton
 
T

Tim Pease

I have a situation where I want to send many messages, one after the
other, to single object. Instead of writing, say,

<pseud-code>
Foo.new.perform { report; eat 'burger', 'fries'; drink 'beer';
be_merry }
</pseud-code>

class Foo
def initialize( &block )
return unless block_given?
self.instance_eval &block
end

def eat( *args )
puts args.inspect
end

def drink( *args )
puts args.inspect
end
end


Foo.new do
eat 'burger', 'fries'
drink 'beer'
end

Is that what you are looking for? You can just as easily create a
process method (instead of using the initialize method) to do
something like this ...

f = Foo.new
f.process do
eat 'burger', 'fries'
drink 'beer'
end

Blessings,
TwP
 
M

Morton Goldberg

class Foo
def initialize( &block )
return unless block_given?
self.instance_eval &block
end

def eat( *args )
puts args.inspect
end

def drink( *args )
puts args.inspect
end
end


Foo.new do
eat 'burger', 'fries'
drink 'beer'
end

Is that what you are looking for? You can just as easily create a
process method (instead of using the initialize method) to do
something like this ...

f = Foo.new
f.process do
eat 'burger', 'fries'
drink 'beer'
end

Blessings,
TwP

It's the second way that I was looking for. Now that I know that
Object#instance_eval will accept a block, I can see how easy it is to
write Foo#perform

Thanks for your help.

Regards, Morton
 
R

Rick DeNatale

I have a situation where I want to send many messages, one after the
other, to single object. Instead of writing, say,

foo.eat burger, fries
foo.drink beer
foo.be_merry

I want to be able to write something like:

foo.perform { eat burger, fries; drink beer; be_merry }

Others have already answered your question, but the above syntax
reminds me of one thing in Smalltalk which I miss a bit in Ruby.

Smalltalk has very little syntax, but one piece is the use of
semicolons to do what Smalltalk calls message cascading. As in Ruby
each method in Smalltalk returns a value and if you write something
like:

obj msg1 msg2

This sends msg1 to obj, and then sends msg2 to the value returned by
obj msg1, much like the equivalent ruby syntax:

obj.msg1.msg2

But in Smalltalk if you write:

obj msg1;msg2

the object referenced by obj is sent msg1 and then msg2 in sequence,
regardless of the value returned by obj msg1.

Since Ruby doesn' t have such syntax, it's probably not missed much,
but since Smalltalk did it was pretty heavily used. Smalltalk also
had a method in Object called yourself which returned the receiver
which is commonly used as the last message in such a cascade on the
right hand side of an assignment.
 
J

Joel VanderWerf

Rick DeNatale wrote:
...
But in Smalltalk if you write:

obj msg1;msg2

the object referenced by obj is sent msg1 and then msg2 in sequence,
regardless of the value returned by obj msg1.

Not quite the same, but this is something similar that's been floating
around the ml for a while:

class Object
def then
yield(self)
self
end
end

hash = Hash.new.
then do |h| h[1] = 2 end.
then do |h| h[3] = 4 end

p hash

That's much clunkier syntactically, but it is actually useful in one
case: when working with classes whose #new method doesn't take a block
that allows further configuration of the new object. For example, sockets:

def socket
@socket ||= UDPSocket.open.then do |s|
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
s.connect("192.168.255.255", 1234)
end
end

It works particularly well with the ||= assignment operator.

Without the #then method you can't use ||= and it's just a little clunkier:

def socket
unless @socket
@socket = UDPSocket.open
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
@socket.connect("255.255.255.255", 1234)
end
@socket
end
 
R

Rick DeNatale

Rick DeNatale wrote:
...
But in Smalltalk if you write:

obj msg1;msg2

the object referenced by obj is sent msg1 and then msg2 in sequence,
regardless of the value returned by obj msg1.

Not quite the same, but this is something similar that's been floating
around the ml for a while:

class Object
def then
yield(self)
self
end
end

hash = Hash.new.
then do |h| h[1] = 2 end.
then do |h| h[3] = 4 end

p hash

That's much clunkier syntactically, but it is actually useful in one
case: when working with classes whose #new method doesn't take a block
that allows further configuration of the new object. For example, sockets:

def socket
@socket ||= UDPSocket.open.then do |s|
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
s.connect("192.168.255.255", 1234)
end
end

Of course this relies on UDPSocket::eek:pen returning the new socket
rather than something else.
It works particularly well with the ||= assignment operator.

Which in turn relies on the block evaluating to the socket as well.

It's more of a variation on Smalltalk's yourself than a cascade.

Nice nonetheless.
 
J

Joel VanderWerf

Rick said:
Rick DeNatale wrote:
...
But in Smalltalk if you write:

obj msg1;msg2

the object referenced by obj is sent msg1 and then msg2 in sequence,
regardless of the value returned by obj msg1.

Not quite the same, but this is something similar that's been floating
around the ml for a while:

class Object
def then
yield(self)
self
end
end

hash = Hash.new.
then do |h| h[1] = 2 end.
then do |h| h[3] = 4 end

p hash

That's much clunkier syntactically, but it is actually useful in one
case: when working with classes whose #new method doesn't take a block
that allows further configuration of the new object. For example,
sockets:

def socket
@socket ||= UDPSocket.open.then do |s|
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
s.connect("192.168.255.255", 1234)
end
end

Of course this relies on UDPSocket::eek:pen returning the new socket
rather than something else.

UDPSocket.open is the same as UDPSocket.new, which returns the new
socket, as is standard for all #new methods.

Actually, UDPSocket *does* take a block, so you don't need this
construct here. It's for those cases (getting rarer and rarer as ruby
libs evolve) where someone defines an #initialize method that does not
take a block (or takes a block but does not yield the object to it).
Which in turn relies on the block evaluating to the socket as well.

Actually, no. The value of the block is discarded, and #then returns the
receiver. That's the point of the #then method. It yields self and then
returns self.
It's more of a variation on Smalltalk's yourself than a cascade.

Nice nonetheless.

Thanks!
 
R

Rick DeNatale

Rick DeNatale wrote:

UDPSocket.open is the same as UDPSocket.new, which returns the new
socket, as is standard for all #new methods.

Actually, UDPSocket *does* take a block, so you don't need this
construct here. It's for those cases (getting rarer and rarer as ruby
libs evolve) where someone defines an #initialize method that does not
take a block (or takes a block but does not yield the object to it).

Or does something else with the block like

Hash::new

which captures a block if given and uses it to handle requests for missing keys.
Actually, no. The value of the block is discarded, and #then returns the
receiver. That's the point of the #then method. It yields self and then
returns self.

Makes sense.
 
J

Joel VanderWerf

Rick said:
Or does something else with the block like

Hash::new

which captures a block if given and uses it to handle requests for
missing keys.

Right. I shouldn't have suggested that all #initialize methods should
yield the object for further configuration, since there are plenty of
other legitimate uses for blocks, such as this one.
 

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,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top