Easy way to handle positional and hashed parameters on a method?

Ideally, I’d like to be able to offer either of these on an API I’m
creating:

my_method(‘a’,‘b’,'c)

or

my_method(first: ‘a’, second: ‘b’, third: ‘c’)

I know I can construct the method definition as:

def my_method(*args)

end

and whatever way they pass things in will get there. But how can I
easily merge them into a hash for use in the rest of the method? I
want the end result to look like:

{:first => ‘a’, :second => ‘b’, :third => ‘c’}

no matter how they enter them.

And could you deal with something where a hash is entered as a
positional parameter, and trailing hash values as well?? Like:

mymethod(({z: 1, x: 2}),‘bazinga!’, third: 17)

to end up with:

{:first => {:z => 1, :x => 2}, :second => ‘basinga!’, :third => 17}

?

Adding myself to this thread, from personal interest. :slight_smile:

Ideally, I’d like to be able to offer either of these
on an API I’m creating:

my_method(‘a’,‘b’,‘c’)

You already gave an example of how to do this. It is via
the splat operator in ruby. You can test this in IRB:

require ‘pp’

def foo(*i)
pp i
end

foo(‘a’,‘b’,‘c’)

You can see that this will return an array:

[“a”, “b”, “c”]

That way you can have as many arguments to a method
as you like - they will all be collected into the
variable name you wrote down in the method definition.

So far, so good. Now to the other question you have:

my_method(first: ‘a’, second: ‘b’, third: ‘c’)

Ok, let’s first break down what happens here. I
will replace “my_method” with “foo”, I like foo()
as demo-name for methods.

So we have:

my_method(first: ‘a’, second: ‘b’, third: ‘c’)

which is:

foo(first: ‘a’, second: ‘b’, third: ‘c’)

which actually is this here:

foo({first: ‘a’, second: ‘b’, third: ‘c’})

In other words, this is a hash.

To make this compatible with Ruby 1.8.x, let’s make
it even more clear:

foo({:first => ‘a’, :second => ‘b’, :third => ‘c’})

Now, if you look at what this code above returns in
IRB (test it please!), you will notice it will return
this here:

[{:third=>“c”, :second=>“b”, :first=>“a”}]

In other words, it has simply put the hash that you
passed here into the first element of an array. So
you still have an array!

With splat operator, it seems as if you will always
have an array. :slight_smile:

Now you could, within the method definition, check
whether you have a hash.

if i.is_a? Array
if i[0].is_a? Hash
# do something special here

Ok? You just check whether your first element contains
a hash or not. If it does contain a hash, you can act
on this information, inside your method!

I know I can construct the method definition as:

def my_method(*args)

end

Yes, the splat operator.

and whatever way they pass things in will get there.

Yes, collected into an array! args in your example will
be an array.

But how can I easily merge them into a hash for use
in the rest of the method?

Simple. Check the given argument for .is_a? and
when you find a hash, reconstruct the method and
the variable. It is your method, you are in full
control of what it does and what it will return!

Input, Modify, Output - that is a the basic of
any function (or method in ruby, as functions
are attached to objects).

I want the end result to look like:
{:first => ‘a’, :second => ‘b’, :third => ‘c’}

You already have this! It just resides as the
first element of your array!

In fact, if you would have tested in IRB, you
could have seen it :smiley:

(Btw I just noticed that you used the new hash
syntax. I recommend to use the old hash syntax,
for CONSISTENCY. Right now you seem to switch
beween the two, I don’t think this is good at
all.)

no matter how they enter them.

Hmmmmmm. I am not sure if it is easily possible
to get the same input as output as far as the
new hash syntax is concerned. I would, right now,
simply claim that this is not possible.

And could you deal with something where a hash
is entered as a positional parameter, and
trailing hash values as well?? Like:

mymethod(({z: 1, x: 2}),‘bazinga!’, third: 17)

to end up with:

{:first => {:z => 1, :x => 2}, :second => ‘basinga!’, :third => 17}

No, you can not. There is no way for ruby to make a
real difference between the two different syntaxes
on the AST. Perhaps matz could say something about
this - I myself would not need what you need, because
I will forever more use the old way to use a hash :wink:
but if others really need the same input as the same
output, well then why not. But internally, I think
it is ALL converted into the same internal representation,
it just seems syntactic sugar to squeeze away a
few extra characters.

Yes, I just tested in IRB:

When I input:

foo({first: ‘a’, second: ‘b’, third: ‘c’})

It will return the CHANGED version:

[{:first=>“a”, :second=>“b”, :third=>“c”}]

I believe this is a design decision.

Internally, ruby will ALWAYS treat the new
hash syntax as the old hash syntax! So I
recommend to use the old hash syntax, your
brain will be happier in the long run. :wink:

Am 03.02.2013 09:07, schrieb Arup R.:

Adding myself to this thread, from personal interest. :slight_smile:

That’s not necessary, just read the posts.

On Sun, Feb 3, 2013 at 3:05 AM, Marc H. [email protected]
wrote:

Yes, I just tested in IRB:

All my examples were first run through IRB.

I believe this is a design decision.
I’d say it’s a backwards-compatibility decision, to make sure both
ways still work and not needlessly breaking old code.

Internally, ruby will ALWAYS treat the new
hash syntax as the old hash syntax! So I
recommend to use the old hash syntax, your
brain will be happier in the long run. :wink:

Not a chance. I much prefer the new way. My brain is quite happy TYVM.
:slight_smile:

What you answered was none of what I was actually asking for.

…And I really think I demonstrated I knew how splat works…

What I’d like is a DRY and functional way to do this.

The question is really, how can I convert an array that may contain
[‘a’, ‘b’, {:third => ‘c’}] to {:first => ‘a’, :second => ‘b’, :third
=> ‘c’}, plus all other variants of that first form, to the same
final form, as an arguments hash, in an elegant, functional fashion. I
can do it, but it doesn’t feel elegant this way.

And there is a way to convert the last form as well:

foo(({z: 1, x: 2}), ‘bazinga!’, third: 17) comes into *args as:

=> [{:z=>1, :x=>2}, “bazinga!”, {:third=>17}]

brute force conversion:

def splatargdeflate(args,argnames)
return args unless args.is_a?(Array)
return args if args.length < 1

if args.last.is_a?(Hash)
last_arg = args.pop
else
last_arg = {}
end

arg_hash={}

argnames.each_index do |i|
arg_hash[argnames[i]] = args.shift
end

arg_hash.merge!(last_arg)
args.unshift arg_hash
end

def foo(*args)
args = splatargdeflate(args,[:first, :second, :third])
puts args.inspect
end

Here’s the output for the example foo call from above:

puts foo(({z: 1, x: 2}), ‘bazinga!’, third: 17)
=> [{:first=>{:z=>1, :x=>2}, :second=>“bazinga!”, :third=>17}]

And here’s some more, to compare:

puts foo
=> []
puts foo(1)
=> [{:first=>1, :second=>nil, :third=>nil}]
puts foo(1,2)
=> [{:first=>1, :second=>2, :third=>nil}]
puts foo(‘a’, ‘b’, ‘c’)
=> [{:first=>“a”, :second=>“b”, :third=>“c”}]
puts foo(‘a’, ‘b’, third: ‘blah’)
=> [{:first=>“a”, :second=>“b”, :third=>“blah”}]
puts foo(‘a’, ‘b’, first: ‘butno’)
=> [{:first=>“butno”, :second=>“b”, :third=>nil}]
puts foo(nil, nil, nil)
=> [{:first=>nil, :second=>nil, :third=>nil}]
puts foo(1,2,3,4,5,6,7,8,9,10)
=> [{:first=>1, :second=>2, :third=>3}, 4, 5, 6, 7, 8, 9, 10]

And, for more amusement:

foo 1, 2, 3, 4, 5, 6, 7, 8, 9, first: ‘zip’, bazinga: ‘owie’
=> [{:first=>“zip”, :second=>2, :third=>3, :bazinga=>“owie”}, 4, 5, 6,
7, 8, 9]

The thing I really dislike about splatargsdeflate, and why I was
asking in the first place, is that it seems so procedural and as I
said above, brute force. I really wanted something much more
functional.

I’m just wondering if there’s is some cool ruby method that does
something akin to this already that I haven’t discovered, or if it is
something that can be map/reduced somehow. The only place I can see
doing that is in the loop through the argnames:

arg_hash = argnames.reduce(h={}){|h,n| h[n]=args.shift;h}

produces the same arg_hash, at some cost of readability, perhaps.

Anyway, thanks for your time and looking.