Named parameters in ruby?

Hi all,

Ruby does not support named parameter for now as all of you know.
There is really three solutions for this

  • hash as parameter
  • class as parameter
  • bindings

Hash parameters:

def do_some( params )
a = params[ :a ]
b = params[ :b ]
end

do_some :a => 1, :b =>2

Class parameters (ugly, written in couple minutes)

class A
def metaclass
class << self; self; end
end

def set_var( name, val )
    eval "@#{name} = val", binding
    self.metaclass.send( :define_method, name ) do
        eval "@#{name}"
    end
end
def set_vars( params )
    params.each{ |key,val| self.set_var( key, val ) }
end

end

def do_some( params )
a = params.a
b = params.b
end

A.new
a.set_vars( :a => 1. :b =>2 )
do_some a

Bindings way:

def do_block( &block )
eval “a=1”, block.binding
eval “b=2”, block.binding
end

do_block( params ){
a = eval ‘a’
b = eval ‘b’
}

As we can see there is overtyping in all cases. We always need to map
incoming variables into locals or write ugly accessor every time.

So My question is:
Is there any way to set local variables for blocks outside of block
scope?

IMHO nice solution will looks like this:
def do_some( &block )
block.binding.set_var( ‘a’, 1 )
block.binding.set_var( ‘b’, 2 )
block.call
end

do_some{
puts a
puts b
}

On Wed, Oct 6, 2010 at 6:05 PM, xiMera [email protected] wrote:

def do_some( params )
a = params[ :a ]
b = params[ :b ]
end

do_some :a => 1, :b =>2

That’s really the best solution available today.

       eval "@#{name}"

b = params.b
end

A.new

Where do you store result of A.new?

a.set_vars( :a => 1. :b =>2 )
do_some a

I don’t think anybody would do this, after all it’s much too
convoluted. Also, why do you (ab)use a class for storing temporary
state? You could as well do

A = Struct.new :a, b:
do_some A[1,2]

or

do_some OpenStruct.new(:a=>1,:b=>2)

But even that is much more complicated than the Hash version. Plus,
you do not get rid of the assignment in the block which you call
“ugly” further on.

}
Local assignments in the block are even worse. Also, this won’t run
because you pass one argument while the method expects none.

As we can see there is overtyping in all cases. We always need to map
incoming variables into locals or write ugly accessor every time.

So My question is:
Is there any way to set local variables for blocks outside of block
scope?

IMHO nice solution will looks like this:

Frankly, I can’t see what’s nice about this approach.

def do_some( &block )
block.binding.set_var( ‘a’, 1 )
block.binding.set_var( ‘b’, 2 )
block.call
end

do_some{
puts a
puts b
}

And what do you need that for? You can do this already today:

def do_some
yield 1,2
end

do_some do |a,b|
puts a
puts b
end

Your solution with the “injection” of local variables cannot work
because the variables are not known inside the block so Ruby will
error out. Also, it is not obvious where the values are coming from
so reading this code will be made harder.

I think your alternative solutions need a bit more polishing. :slight_smile:

Kind regards

robert

xiMera wrote:

A.new
a.set_vars( :a => 1. :b =>2 )
do_some a

I think you mean:

a = A.new
a.set_vars ( :a => 1, :b => 2)
do_some a

How is this better than

do_some :a => 1, :b => 2

?

Note that if you are using ruby 1.9, then you can also write

do_some a: 1, b: 2

which (from the caller’s point of view) is getting pretty close to named
parameters.

At the callee’s side, multiple assignment keeps things pretty clean:

def do_some(v={})
a, b, c = v[:a], v[:b], v[:c]
end

Or you can use values_at, which is a bit longer in this case.

a, b, c = v.values_at(:a, :b, :c)

Note that ruby needs to know at parse time that a,b and c are local
variables, so it can reserve slots for them in the activation record,
and this assignment achieves that.

Any solution using eval to create local variables would be very
inefficient, if it could be made to work at all.

irb(main):010:0> def foo
irb(main):011:1> eval(“x = 5”)
irb(main):012:1> puts x
irb(main):013:1> end
=> nil
irb(main):014:0> foo
NameError: undefined local variable or method x' for main:Object from (irb):12:infoo’
from (irb):14
from :0

def do_some(v={})
 a, b, c = v[:a], v[:b], v[:c]
end

One thing to remember about this idiom is what happens when you try
and default the values:

(see http://gist.github.com/616543 for the below code in a prettier
format)

def foo(hash = { :a => 1, :b => 2 })
a = hash[:a]
b = hash[:b]
puts a.inspect
puts b.inspect
end

foo

#=>

1

2

foo(:a => 10)

#=>

10

nil

So you need something like

def foo(hash = {})
hash = { :a => 1, :b => 2 }.merge(hash)
a = hash[:a]
b = hash[:b]
puts a.inspect
puts b.inspect
end

foo

#=>

1

2

foo(:a => 10)

#=>

10

2

That whole requirement of doing merge is a little messy, I suppose;
one reason that proper named/keyword arguments would be nice. Not too
much of a problem, though, really.