Why does this use a block

A

Anonymous

So I'm new to Ruby and I'm trying to use OptionParser to parse in some
command line arguments. I looked at some documentation on OptionParser and
there is something that's confusing me to no end. In all the examples a
block is being passed in to the constructor of the OptionParser object. The
constructor simply makes a yield call, passing self as an argument.

What is the point of this? Can we not convert

opts = OptionParser.new() {|opts| body}

To

opts = OptionParser.new()
body

? This seems more natural to me. But the former seems like a common Ruby
idiom so I'm wondering what I am missing.

Thanks,

Jaimrk
 
E

Eric Hodel

So I'm new to Ruby and I'm trying to use OptionParser to parse in some
command line arguments. I looked at some documentation on
OptionParser and there is something that's confusing me to no end.
In all the examples a block is being passed in to the constructor
of the OptionParser object. The constructor simply makes a yield
call, passing self as an argument.

What is the point of this? Can we not convert

opts = OptionParser.new() {|opts| body}

To

opts = OptionParser.new()
body

? This seems more natural to me. But the former seems like a common
Ruby idiom so I'm wondering what I am missing.

It works both ways. Blocks are pretty, so people use them.
 
E

Erik Veenstra

About using blocks in constructors in general...

(Although "initialize" is an initializer and not a constructor.
It only takes care of one part of the construction.)

"initialize" should take care of the initialization of the
object: Setting instance variables and checking whether the
instance variables are set correctly. It's the latter which is
important.

Setting instance variables is traditionally done in two ways: a
list of arguments and a hash of options. (Or a combination of
them.) You might want to call them "arguments" and "named
arguments".

The list:

def initialize(first_name, last_name)
@first_name = first_name.to_s
@last_name = last_name.to_s

raise ArgumentError, "first name isn't set" unless @first_name
raise ArgumentError, "last name isn't set" unless @last_name
end

The hash:

def initialize(options={})
@case_sensitive = options[:case_sensitive] || true
@direction = options[:direction] || :forward

raise ArgumentError, "" unless @first_name
end

In Java, you'll often see a third way (Java style, Ruby code):

attr_writer :first_name
attr_writer :last_name

def initialize
end

something = Something.new
something.first_name = "Erik"
something.last_name = "Veenstra"

Personally, I think this is conceptually unacceptable: There's
no way for the constructor to check whether the instance
variables are set, or whether they are set correctly.

This can be overcome by calling the initializer with a block:

attr_writer :first_name
attr_writer :last_name

def initialize(&block)
block.call(self)

raise ArgumentError, "first name isn't set" unless @first_name
raise ArgumentError, "last name isn't set" unless @last_name
end

Which is called like this:

Something.new do |something|
something.first_name = "Erik"
something.last_name = "Veenstra"
end

Personally, I always try to freeze the object once it is
created: (This has advantages beyond the scope of this writing.)

attr_writer :first_name
attr_writer :last_name

def initialize(&block)
block.call(self)

raise ArgumentError, "first name isn't set" unless @first_name
raise ArgumentError, "last name isn't set" unless @last_name

freeze
end

There's some more advantages. For example, the object can often
be collected earlier. Consider this example, in the main of the
application:

direction = :forward

EV::CommandLine.new do |cl|
cl.option("-r", "--reverse") {direction = :backward}
end.parse(ARGV)

...instead of...

direction = :forward
cl = EV::CommandLine.new
cl.option("-r", "--reverse") {direction = :backward}
cl.parse(ARGV)

When will cl be collected, in the second piece of code? Never?

It's just my opinion. Feel free to ignore me...

gegroet,
Erik V. - http://www.erikveen.dds.nl/
 
R

Robert Klemme

2007/10/11 said:
It works both ways. Blocks are pretty, so people use them.

There are in fact important differences to both approaches. First,
OptionParser's #initialize is free to yield whatever it decides to the
block. So you are not guaranteed to receive the new OptionParser
instance. This is important to keep in mind because the
implementation might change at a later point so the lib could yield a
different instance which should, of course, support the documented
interface.

Second, with the block form OptionParser#initialize is able to detect
when the user initialization has finished and can do finalizing work.
It could, for example, invoke #freeze to make sure the configuration
is not changed any more. Or it can verify that client code properly
initialized it (avoiding duplicate or ambiguous option settings for
example).

An alternative that some may find nicer would implement OptionParser()
(a method) in Kernel and have that receive the block.

Kind regards

robert
 
F

Florian Frank

Anonymous said:
So I'm new to Ruby and I'm trying to use OptionParser to parse in some
command line arguments. I looked at some documentation on OptionParser and
there is something that's confusing me to no end. In all the examples a
block is being passed in to the constructor of the OptionParser object. The
constructor simply makes a yield call, passing self as an argument.

What is the point of this? Can we not convert

opts = OptionParser.new() {|opts| body}

To

opts = OptionParser.new()
body

? This seems more natural to me. But the former seems like a common Ruby
idiom so I'm wondering what I am missing.
You might want to use the shortcut

option_parser = OptionParser.new do |o|
o.foo = "foo"
o.bar = "bar"
...
end

during initialization of the object instead of using the long name, that
will be used later in the program after you forgot, what "o" meant.

I don't know if this is the case with OptionParser, but many methods
also want to do some housekeeping after the execution of the block, for
example, open(...) do |f| ... end closes the opened file. A constructor
might want to check his configuration (given in the block) and raise an
exception, if errors were found. This is better than creating a wrongly
configured object, that only will throw an exception later in the program.
 
T

Todd Benson

So I'm new to Ruby and I'm trying to use OptionParser to parse in some
command line arguments. I looked at some documentation on OptionParser and
there is something that's confusing me to no end. In all the examples a
block is being passed in to the constructor of the OptionParser object. The
constructor simply makes a yield call, passing self as an argument.

What is the point of this? Can we not convert

opts = OptionParser.new() {|opts| body}

To

opts = OptionParser.new()
body

? This seems more natural to me. But the former seems like a common Ruby
idiom so I'm wondering what I am missing.

Thanks,

Jaimrk

You are asking a bunch of questions here, but I'll try my hand at one of them.

yield self

It pretty much does exactly what the statement says it does. Why
would you do this upon initializing an object? I suppose, because you
might want something else to accomplish something during
initialization.

Blocks also give you control of scope and give the programmer a sense
of finalization. File#open for example.

Todd
 
R

Rick DeNatale

There are in fact important differences to both approaches. First,
OptionParser's #initialize is free to yield whatever it decides to the
block. So you are not guaranteed to receive the new OptionParser
instance. This is important to keep in mind because the
implementation might change at a later point so the lib could yield a
different instance which should, of course, support the documented
interface.

Second, with the block form OptionParser#initialize is able to detect
when the user initialization has finished and can do finalizing work.
It could, for example, invoke #freeze to make sure the configuration
is not changed any more. Or it can verify that client code properly
initialized it (avoiding duplicate or ambiguous option settings for
example).

I'm pretty sure that I've seen a variant on this in which initialize
evaluates the block with instance_eval rather than yield or call.
This allows the block to invoke private methods during initialization.

IIRC the Ruby tk bindings so this.
 
7

7stud --

Anonymous said:
So I'm new to Ruby and I'm trying to use OptionParser to parse in some
command line arguments.

In ruby, a lot of methods can be called with or without a block. When a
method is called with a block, it may do some extra stuff. I think what
the other posters are saying is that the initialize() method in
OptionParser, which is called when you call new(), may be defined to do
some extra things when it's called with a block.

Here's an example of a method doing some extra things when it's called
with a block:

class Dog
def age
@age
end

def age=(num)
@age = num
end

def initialize
if block_given?
yield self

if @age == nil
puts "You must set the dog's age."
elsif @age > 20
puts "Error. That dog is dead."
elsif @age < 0
puts "Error. That dog isn't born yet."
end

end
end
end



d = Dog.new
d.age = -3
puts "That dog's age is #{d.age}."
d.age = 22
puts "That dog's age is #{d.age}."

d = Dog.new do |dog|
dog.age = 22
end

--output:--
That dog's age is -3.
That dog's age is 22.
Error. That dog is dead.
 
B

Brian Adkins

Personally, I always try to freeze the object once it is
created: (This has advantages beyond the scope of this writing.)

So, most of your objects are immutable?
 
B

Brian Adkins


Have you thought of programming in Haskell? ;)

I'm familiar with some of the benefits of avoiding state with respect
to functional programming languages, but to impose that strictly on
Ruby seems to be working against the grain. I do code many functions
(i.e. not associated with a meaningful instance) in a functional style
w/o side effects, but when working with objects, having methods modify
the state of objects seems to be very natural and productive in Ruby.

I'm curious though because of my recent research into Haskell, SML,
etc.. Do you have an example of this style of programming (frozen
objects) in Ruby that you feel is compelling?

Brian Adkins
 
R

Robert Klemme

2007/10/11 said:
Have you thought of programming in Haskell? ;)
:)

I'm familiar with some of the benefits of avoiding state with respect
to functional programming languages, but to impose that strictly on
Ruby seems to be working against the grain. I do code many functions
(i.e. not associated with a meaningful instance) in a functional style
w/o side effects, but when working with objects, having methods modify
the state of objects seems to be very natural and productive in Ruby.

Actually programming completely without side effects defies the core
of the OO paradigm. Objects simply do not make much sense if methods
cannot produce side effects.
I'm curious though because of my recent research into Haskell, SML,
etc.. Do you have an example of this style of programming (frozen
objects) in Ruby that you feel is compelling?

Look at all the math in Ruby - I believe it makes very much sense the
way it is done today. But any collection type would be rather
inefficient when you always had to create a new object for every
change - at least in the Ruby world. I believe that some functional
language runtime systems are pretty smart and can optimize this to a
certain extend.

It's all about balance. Use side effects when appropriate, use
immutables when appropriate.

Kind regards

robert
 
R

Rick DeNatale

Actually programming completely without side effects defies the core
of the OO paradigm. Objects simply do not make much sense if methods
cannot produce side effects.

I'd agree. Multi-paradigm programming is one thing, but this seems
more like counter paradigm programming.

Sort of like speaking English fluent with grammar French.
 

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

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top