Pass block instead of here document?

Discussion in 'Ruby' started by Morton Goldberg, Oct 25, 2006.

  1. 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
    Morton Goldberg, Oct 25, 2006
    #1
    1. Advertising

  2. Morton Goldberg

    Tim Pease Guest

    On 10/25/06, Morton Goldberg <> wrote:
    > I have a situation where I want to send many messages, one after the
    > other, to single object. Instead of writing, say,
    >


    <snip>

    > <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
    Tim Pease, Oct 25, 2006
    #2
    1. Advertising

  3. On Oct 25, 2006, at 3:39 PM, Tim Pease wrote:

    > On 10/25/06, Morton Goldberg <> wrote:
    >> I have a situation where I want to send many messages, one after the
    >> other, to single object. Instead of writing, say,
    >>

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


    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
    Morton Goldberg, Oct 25, 2006
    #3
  4. On 10/25/06, Morton Goldberg <> wrote:
    > 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.

    --
    Rick DeNatale

    My blog on Ruby
    http://talklikeaduck.denhaven2.com/
    Rick DeNatale, Oct 26, 2006
    #4
  5. 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

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
    Joel VanderWerf, Oct 26, 2006
    #5
  6. On 10/26/06, Joel VanderWerf <> wrote:
    > 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.

    --
    Rick DeNatale

    My blog on Ruby
    http://talklikeaduck.denhaven2.com/
    Rick DeNatale, Oct 26, 2006
    #6
  7. Rick DeNatale wrote:
    > On 10/26/06, Joel VanderWerf <> wrote:
    >> 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).

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

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

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
    Joel VanderWerf, Oct 26, 2006
    #7
  8. On 10/26/06, Joel VanderWerf <> wrote:
    > Rick DeNatale wrote:


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


    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.

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

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


    Makes sense.

    --
    Rick DeNatale

    My blog on Ruby
    http://talklikeaduck.denhaven2.com/
    Rick DeNatale, Oct 26, 2006
    #8
  9. Rick DeNatale wrote:
    > On 10/26/06, Joel VanderWerf <> wrote:
    >> Rick DeNatale wrote:

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

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

    --
    vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
    Joel VanderWerf, Oct 27, 2006
    #9
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. morrell
    Replies:
    1
    Views:
    938
    roy axenov
    Oct 10, 2006
  2. Mr A
    Replies:
    111
    Views:
    2,074
  3. George Hester

    Try over here likely more to the point here

    George Hester, Sep 30, 2004, in forum: Javascript
    Replies:
    0
    Views:
    106
    George Hester
    Sep 30, 2004
  4. FAQ server
    Replies:
    0
    Views:
    149
    FAQ server
    Aug 10, 2006
  5. FAQ server
    Replies:
    0
    Views:
    127
    FAQ server
    Oct 7, 2006
Loading...

Share This Page