named parameters in ruby?

X

xiMera

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

Robert Klemme

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 )
=A0a =3D params[ :a ]
=A0b =3D params[ :b ]
end

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

That's really the best solution available today.
Class parameters (ugly, written in couple minutes)

class A
=A0 =A0def metaclass
=A0 =A0 =A0 =A0class << self; self; end
=A0 =A0end

=A0 =A0def set_var( name, val )
=A0 =A0 =A0 =A0eval "@#{name} =3D val", binding
=A0 =A0 =A0 =A0self.metaclass.send( :define_method, name ) do
=A0 =A0 =A0 =A0 =A0 =A0eval "@#{name}"
=A0 =A0 =A0 =A0end
=A0 =A0end
=A0 =A0def set_vars( params )
=A0 =A0 =A0 =A0params.each{ |key,val| self.set_var( key, val ) }
=A0 =A0end
end


def do_some( params )
=A0a =3D params.a
=A0b =3D params.b
end

A.new

Where do you store result of A.new?
a.set_vars( :a =3D> 1. :b =3D>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 =3D Struct.new :a, b:
do_some A[1,2]

or

do_some OpenStruct.new:)a=3D>1,:b=3D>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 )
=A0 eval "a=3D1", block.binding
=A0 eval "b=3D2", block.binding
end

do_block( params ){
=A0a =3D eval 'a'
=A0b =3D 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:
=A0 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 )
=A0block.binding.set_var( 'a', 1 )
=A0block.binding.set_var( 'b', 2 )
=A0block.call
end

do_some{
=A0 puts a
=A0 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. :)

Kind regards

robert

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

Brian Candler

xiMera said:
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
 
A

Adam Prescott

def do_some(v=3D{})
=C2=A0a, b, c =3D 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 =3D { :a =3D> 1, :b =3D> 2 })
a =3D hash[:a]
b =3D hash[:b]
puts a.inspect
puts b.inspect
end

foo

#=3D>
# 1
# 2

foo:)a =3D> 10)

#=3D>
# 10
# nil

So you need something like

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

foo

#=3D>
# 1
# 2

foo:)a =3D> 10)

#=3D>
# 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.


xiMera said:
A.new
a.set_vars( :a =3D> 1. :b =3D>2 )
do_some a

I think you mean:

=C2=A0a =3D A.new
=C2=A0a.set_vars ( :a =3D> 1, :b =3D> 2)
=C2=A0do_some a

How is this better than

=C2=A0do_some :a =3D> 1, :b =3D> 2

?

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

=C2=A0do_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=3D{})
=C2=A0a, b, c =3D v[:a], v[:b], v[:c]
end

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

=C2=A0a, b, c =3D 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 =3D 5")
irb(main):012:1> puts x
irb(main):013:1> end
=3D> nil
irb(main):014:0> foo
NameError: undefined local variable or method `x' for main:Object
=C2=A0from (irb):12:in `foo'
=C2=A0from (irb):14
=C2=A0from :0
 

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

Forum statistics

Threads
473,962
Messages
2,570,134
Members
46,690
Latest member
MacGyver

Latest Threads

Top