Polymorphism and/or named parameters: the ruby way?

(Berger, Daniel) #1

A typical idiom for constructors is to do "yield self if block_given?",
which I prefer over hash based arguments. For example:

class Foo
   attr_accessor :bar, :baz, :zap
   def initialize
      yield self if block_given?
   end
end

foo = Foo.new do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

Many thanks to Hal Fulton's "The Ruby Way" for teaching me this trick.
:slight_smile:

Regards,

Dan

···

-----Original Message-----
From: Stephan Mueller [mailto:d454d@web.de]
Sent: Saturday, August 06, 2005 12:01 PM
To: ruby-talk ML
Subject: polymorphism and/or named parameters: the ruby way?

Hello,

i am kind of a newbi, please forgive me if i am asking stale
questions.

I want to adjust the initialization of an object according to
the parameters given to new.

(Eric Mahurin) #2

What's the advantage of this over:

foo = Foo.new
foo.bar = "hello"
foo.baz = 5
foo.zap = "world"

Is it method chaining? You want this thing to return foo? You
could just end this statement list with "foo", or you could use
"ensure":

begin
  foo = Foo.new
ensure
  foo.bar = "hello"
  foo.baz = 5
  foo.zap = "world"
end

This begin..ensure..end expression returns foo (the result
before ensure) not "world" as you might expect. Great for
doing post operations after you calculate a return value
(equivalent of C's i++ is another example).

···

--- "Berger, Daniel" <Daniel.Berger@qwest.com> wrote:

> -----Original Message-----
> From: Stephan Mueller [mailto:d454d@web.de]
> Sent: Saturday, August 06, 2005 12:01 PM
> To: ruby-talk ML
> Subject: polymorphism and/or named parameters: the ruby
way?
>
>
> Hello,
>
> i am kind of a newbi, please forgive me if i am asking
stale
> questions.
>
> I want to adjust the initialization of an object according
to
> the parameters given to new.

A typical idiom for constructors is to do "yield self if
block_given?",
which I prefer over hash based arguments. For example:

class Foo
   attr_accessor :bar, :baz, :zap
   def initialize
      yield self if block_given?
   end
end

foo = Foo.new do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

____________________________________________________
Start your day with Yahoo! - make it your home page

(Joel VanderWerf) #3

Eric Mahurin wrote:
...

class Foo
  attr_accessor :bar, :baz, :zap
  def initialize
     yield self if block_given?
  end
end

foo = Foo.new do |f|
  f.bar = "hello"
  f.baz = 5
  f.zap = "world"
end

What's the advantage of this over:

foo = Foo.new
foo.bar = "hello"
foo.baz = 5
foo.zap = "world"

One advantage is you can construct and configure an object without
assigning to a local variable. This can be useful if you are
constructing a bunch of objects in #map or in literal hashes or arrays
or (this is a bit far-fetched, but why not) parameter lists. And it is
nice not to add a symbol to the namespace. I particularly like the fact
that if you are doing several of these, possibly even in different
methods, you can use the same |f| parameter and cut and paste between
the assignments:

def make_foos
  foo1 = Foo.new do |f|
    f.bar = "BAR"
  end

  foo2 = Foo.new do |f|
    f.bar = "ZAP"
  end
end

That has a nice standardized look to me, especially when you are
populating many fields in several different methods.

Is it method chaining? You want this thing to return foo? You
could just end this statement list with "foo", or you could use
"ensure":

begin
  foo = Foo.new
ensure
  foo.bar = "hello"
  foo.baz = 5
  foo.zap = "world"
end

Danger Will Robinson. An exception may happen in Foo.new....

This begin..ensure..end expression returns foo (the result
before ensure) not "world" as you might expect. Great for
doing post operations after you calculate a return value
(equivalent of C's i++ is another example).

You can use an else clause to return a value from a begin..end block,
but only if there is at least one rescue clause (or else you get a warning).

p(
begin
  a = []
  a << 1
  a << 2
rescue
else
  a
end
)

# ==> [1, 2]

···

--- "Berger, Daniel" <Daniel.Berger@qwest.com> wrote:

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Florian Frank) #4

Eric Mahurin wrote:

What's the advantage of this over:

foo = Foo.new
foo.bar = "hello"
foo.baz = 5
foo.zap = "world"

1. To avoid, having to call a configure method after configuration parameters have been set (or before certain instance methods are called),
2. to guarantee, that no Foo objects are created, that aren't configured correctly.

Consider this contrived example:

class Foo
  FIELDS = %w[bar baz zap]

  attr_accessor(*FIELDS)

  def initialize
    if block_given?
      yield self
      configure
    else
      raise ArgumentError, "configuration required!"
    end
  end

  def configure
    for f in FIELDS
      __send__(f) or raise ArgumentError, "configuration for #{f} missing!"
    end
    @greeting = [ @bar ] * @baz * ', ' + " #@zap!"
  end

  def greet
    puts @greeting
  end
end

f = Foo.new do |f|
  f.bar = "hello"
  f.baz = 5
  f.zap = "world"
end
f.greet

After the configuration block has been executed, f is guaranteed to be a correctly configured Foo instance. (At least no parameter has been forgotten, but further checks are possible as well.)

(James Edward Gray II) #5

Perhaps you want to create and use an object without even assigning it to a variable:

Message do |mes|
     # build message here...

     mes.send
end

James Edward Gray II

···

On Aug 8, 2005, at 12:21 PM, Eric Mahurin wrote:

--- "Berger, Daniel" <Daniel.Berger@qwest.com> wrote:

A typical idiom for constructors is to do "yield self if
block_given?",
which I prefer over hash based arguments. For example:

class Foo
   attr_accessor :bar, :baz, :zap
   def initialize
      yield self if block_given?
   end
end

foo = Foo.new do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

What's the advantage of this over:

foo = Foo.new
foo.bar = "hello"
foo.baz = 5
foo.zap = "world"

(Eric Mahurin) #6

The exception issue bothers me too with this ensure trick.
But, this else doesn't really help. I was wanting something
that appplied some post operations after you determine the
return value. For example, the equivalent to the C expression
a[i++] is the following using the ensure "operator":

a[begin i ensure i+=1 end]

I've benchmarked various ways of doing this and this method is
the clear winner.

Functionally, I guess something like this would be most
flexible (no performance advantage like "ensure"):

class Object
  def post
    yield(self)
    self
  end
end

Then you could do these types of things:

a[i.post{i+=1}]

foo = Foo.new.post do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

With this, no need for the "yield(self) if block_given?" in
initialize.

This very useful little method is a nice thing to have in your
bag-o-tricks.

···

--- Joel VanderWerf <vjoel@path.berkeley.edu> wrote:

Eric Mahurin wrote:
> --- "Berger, Daniel" <Daniel.Berger@qwest.com> wrote:
...
>>class Foo
>> attr_accessor :bar, :baz, :zap
>> def initialize
>> yield self if block_given?
>> end
>>end
>>
>>foo = Foo.new do |f|
>> f.bar = "hello"
>> f.baz = 5
>> f.zap = "world"
>>end
>
>
> What's the advantage of this over:
>
> foo = Foo.new
> foo.bar = "hello"
> foo.baz = 5
> foo.zap = "world"

One advantage is you can construct and configure an object
without
assigning to a local variable. This can be useful if you are
constructing a bunch of objects in #map or in literal hashes
or arrays
or (this is a bit far-fetched, but why not) parameter lists.
And it is
nice not to add a symbol to the namespace. I particularly
like the fact
that if you are doing several of these, possibly even in
different
methods, you can use the same |f| parameter and cut and paste
between
the assignments:

def make_foos
  foo1 = Foo.new do |f|
    f.bar = "BAR"
  end

  foo2 = Foo.new do |f|
    f.bar = "ZAP"
  end
end

That has a nice standardized look to me, especially when you
are
populating many fields in several different methods.

> Is it method chaining? You want this thing to return foo?
You
> could just end this statement list with "foo", or you could
use
> "ensure":
>
> begin
> foo = Foo.new
> ensure
> foo.bar = "hello"
> foo.baz = 5
> foo.zap = "world"
> end

Danger Will Robinson. An exception may happen in Foo.new....

> This begin..ensure..end expression returns foo (the result
> before ensure) not "world" as you might expect. Great for
> doing post operations after you calculate a return value
> (equivalent of C's i++ is another example).

You can use an else clause to return a value from a
begin..end block,
but only if there is at least one rescue clause (or else you
get a warning).

p(
begin
  a = []
  a << 1
  a << 2
rescue
else
  a
end
)

# ==> [1, 2]

____________________________________________________
Start your day with Yahoo! - make it your home page

(Eric Mahurin) #7

Yep, that makes more sense. It looks a lot more useful when
you do some operations after the yield. Kind of like the
reason the IO.open block form is useful - it does the close for
you.

···

--- Florian Frank <flori@nixe.ping.de> wrote:

Eric Mahurin wrote:

>What's the advantage of this over:
>
>foo = Foo.new
>foo.bar = "hello"
>foo.baz = 5
>foo.zap = "world"
>
>
1. To avoid, having to call a configure method after
configuration
parameters have been set (or before certain instance methods
are called),
2. to guarantee, that no Foo objects are created, that aren't
configured
correctly.

Consider this contrived example:

class Foo
  FIELDS = %w[bar baz zap]

  attr_accessor(*FIELDS)

  def initialize
    if block_given?
      yield self
      configure
    else
      raise ArgumentError, "configuration required!"
    end
  end

  def configure
    for f in FIELDS
      __send__(f) or raise ArgumentError, "configuration for
#{f} missing!"
    end
    @greeting = [ @bar ] * @baz * ', ' + " #@zap!"
  end

  def greet
    puts @greeting
  end
end

f = Foo.new do |f|
  f.bar = "hello"
  f.baz = 5
  f.zap = "world"
end
f.greet

After the configuration block has been executed, f is
guaranteed to be a
correctly configured Foo instance. (At least no parameter has
been
forgotten, but further checks are possible as well.)

____________________________________________________
Start your day with Yahoo! - make it your home page

(Eric Mahurin) #8

You missed a .new, I believe.

I'd still say my Object#post (someone else called it "then")
would be a better thing to have instead of every initialize
doing "yield self if block_given?" at the end. If there was
something done after the yield (like checking or even
closing/destroying the object), then there'd be more use.

class Object
  def post
    yield(self)
    self
  end
end

Message.new.post do |mes|
     # build message here...
     mes.send
end

···

--- James Edward Gray II <james@grayproductions.net> wrote:

On Aug 8, 2005, at 12:21 PM, Eric Mahurin wrote:

> --- "Berger, Daniel" <Daniel.Berger@qwest.com> wrote:
>>
>> A typical idiom for constructors is to do "yield self if
>> block_given?",
>> which I prefer over hash based arguments. For example:
>>
>> class Foo
>> attr_accessor :bar, :baz, :zap
>> def initialize
>> yield self if block_given?
>> end
>> end
>>
>> foo = Foo.new do |f|
>> f.bar = "hello"
>> f.baz = 5
>> f.zap = "world"
>> end
>>
>
> What's the advantage of this over:
>
> foo = Foo.new
> foo.bar = "hello"
> foo.baz = 5
> foo.zap = "world"

Perhaps you want to create and use an object without even
assigning
it to a variable:

Message do |mes|
     # build message here...

     mes.send
end

____________________________________________________
Start your day with Yahoo! - make it your home page

(Joel VanderWerf) #9

Eric Mahurin wrote:
...

Functionally, I guess something like this would be most
flexible (no performance advantage like "ensure"):

class Object
  def post
    yield(self)
    self
  end
end

Then you could do these types of things:

a[i.post{i+=1}]

foo = Foo.new.post do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

With this, no need for the "yield(self) if block_given?" in
initialize.

This very useful little method is a nice thing to have in your
bag-o-tricks.

Nice. you could even call it #then:

class Object
  def then
    yield(self)
    self
  end
end

#Then you could do these types of things:
a = (0..10).to_a
i = 3
p a[i.then{i+=1}]
p i

Foo = Struct.new :bar, :baz, :zap

foo = Foo.new.then do |f|
   f.bar = "hello"
   f.baz = 5
   f.zap = "world"
end

p foo

__END__

output:

3
4
#<struct Foo bar="hello", baz=5, zap="world">

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(James Edward Gray II) #10

Perhaps you want to create and use an object without even
assigning it to a variable:

Message do |mes|
     # build message here...

     mes.send
end

You missed a .new, I believe.

Oops, right you are.

I'd still say my Object#post (someone else called it "then")
would be a better thing to have instead of every initialize
doing "yield self if block_given?" at the end. If there was
something done after the yield (like checking or even
closing/destroying the object), then there'd be more use.

class Object
  def post
    yield(self)
    self
  end
end

Message.new.post do |mes|
     # build message here...
     mes.send
end

Hmm, I don't like that because it moves the management responsibility down to the caller, for no gain that I can see. But, of course, we are both free to create them however we like. :wink:

James Edward Gray II

···

On Aug 18, 2005, at 10:36 AM, Eric Mahurin wrote:

(Simon Kröger) #11

This very useful little method is a nice thing to have in your
bag-o-tricks.

Nice. you could even call it #then:

class Object
  def then
    yield(self)
    self
  end
end

#Then you could do these types of things:
a = (0..10).to_a
i = 3
p a[i.then{i+=1}]
p i

[..snip..]

But be carefull:

i = 3
p i.then{i+=1}
p i

i = [3]
p i.then{i << 4}
p i

output:
3
4
[3, 4]

(perhaps this was obvious to all, except me)

cheers

Simon

(Joel VanderWerf) #12

Simon Kröger wrote:

This very useful little method is a nice thing to have in your
bag-o-tricks.

Nice. you could even call it #then:

class Object
  def then
    yield(self)
    self
  end
end

#Then you could do these types of things:
a = (0..10).to_a
i = 3
p a[i.then{i+=1}]
p i

[..snip..]

But be carefull:

i = 3
p i.then{i+=1}
p i

i = [3]
p i.then{i << 4}
p i

output:
3
4
[3, 4]
[3, 4]

(perhaps this was obvious to all, except me)

cheers

Simon

Good point. You really have to be especially clear about whether you are
modifying a variable binding or an object.

i = [3]
p i.then{i+=[4]} # ==> [3]
p i # ==> [3, 4]

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

(Simon Kröger) #13

Joel VanderWerf wrote:

Simon Kröger wrote:

This very useful little method is a nice thing to have in your
bag-o-tricks.

Nice. you could even call it #then:

class Object
def then
   yield(self)
   self
end
end

#Then you could do these types of things:
a = (0..10).to_a
i = 3
p a[i.then{i+=1}]
p i

[..snip..]

But be carefull:

i = 3
p i.then{i+=1}
p i

i = [3]
p i.then{i << 4}
p i

output:
3
4
[3, 4]

(perhaps this was obvious to all, except me)

cheers

Simon

Good point. You really have to be especially clear about whether you are
modifying a variable binding or an object.

i = [3]
p i.then{i+=[4]} # ==> [3]
p i # ==> [3, 4]

woohoo,
i+=[4] creates a new array, right?!

this is - lets say - 'suboptimal' in terms of speed for the
99.9% of cases where you do not need the old array.
(ok, you should realy use <<, except if you use 'then' -
I would not like to teach someone this)

cheers

Simon

(Eric Mahurin) #14

This gets us into the whole destructive vs. non-destructive
argument. << is the efficient destructive version and + is the
non-destructive create-a-new-object, less efficient, but more
flexible version. Non-destructive methods (that return a new
object) don't have the side-effect problem and allow method
chaining in a clean way. It is no different for this "then"
(or "post" as I had it originally) method.

···

--- Simon Kröger <SimonKroeger@gmx.de> wrote:

Joel VanderWerf wrote:

> Simon Kröger wrote:
>
>>>>This very useful little method is a nice thing to have in
your
>>>>bag-o-tricks.
>>>
>>>
>>>Nice. you could even call it #then:
>>>
>>>class Object
>>> def then
>>> yield(self)
>>> self
>>> end
>>>end
>>>
>>>#Then you could do these types of things:
>>>a = (0..10).to_a
>>>i = 3
>>>p a[i.then{i+=1}]
>>>p i
>>>
>>>[..snip..]
>>
>>
>>But be carefull:
>>
>>i = 3
>>p i.then{i+=1}
>>p i
>>
>>i = [3]
>>p i.then{i << 4}
>>p i
>>
>>
>>output:
>>3
>>4
>>[3, 4]
>>[3, 4]
>>
>>
>>(perhaps this was obvious to all, except me)
>>
>>cheers
>>
>>Simon
>
>
> Good point. You really have to be especially clear about
whether you are
> modifying a variable binding or an object.
>
> i = [3]
> p i.then{i+=[4]} # ==> [3]
> p i # ==> [3, 4]
>

woohoo,
i+=[4] creates a new array, right?!

this is - lets say - 'suboptimal' in terms of speed for the
99.9% of cases where you do not need the old array.
(ok, you should realy use <<, except if you use 'then' -
I would not like to teach someone this)

____________________________________________________
Start your day with Yahoo! - make it your home page