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
}

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

That's really the best solution available today.

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

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.

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'
}

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

···

On Wed, Oct 6, 2010 at 6:05 PM, xiMera <chuchelo@gmail.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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:in `foo'
  from (irb):14

···

from :0
--
Posted via http://www.ruby-forum.com/\.

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.

···

On Thu, Oct 7, 2010 at 9:17 PM, Brian Candler <b.candler@pobox.com> wrote:

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:in `foo'
from (irb):14
from :0
--
Posted via http://www.ruby-forum.com/\.