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}
?
on 2013-02-03 05:32
on 2013-02-03 10:03
> 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. :) 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 :D (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 ;) 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.
on 2013-02-03 10:05
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. ;)
on 2013-02-03 10:36
Am 03.02.2013 09:07, schrieb Arup Rakshit:
> Adding myself to this thread, from personal interest. :)
That's not necessary, just read the posts.
on 2013-02-04 09:22
On Sun, Feb 3, 2013 at 3:05 AM, Marc Heiler <lists@ruby-forum.com> 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. ;) Not a chance. I much prefer the new way. My brain is quite happy TYVM. :) 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.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.