How to map hash items to method parameters? - ruby

I have a method with a lengthy list of optional arguments, such as:
def foo(foo = nil, bar = nil, baz = nil, qux = nil)
# no-op
end
I thought that calling the method and passing a split hash as a parameter would map the hash items to parameters by matching the key with the method parameter:
params = { bar: 'bar', foo: 'foo' }
foo(*params)
Unfortunately, when I examine the local variables after calling the method with a split hash, I get exactly what I'd expect if I passed in a split array, but it's not what I was hoping for:
foo == [:bar, 'bar'] # hoped: foo == 'foo'
bar == [:foo, 'foo'] # hoped: bar == 'bar'
What am I lacking here?

(this answer refers to the version of Ruby that was current when the question was asked. See edit for the situation today.)
Ruby does not support passing arguments by name. The splat operator (*) expands an arbitrary enumerable by calling to_ary on it and splices the result into the argument list. In your case, the enumerable you pass in is a hash, which gets transformed into an array of key-value pairs:
[2] pry(main)> params.to_a
=> [[:bar, "bar"], [:foo, "foo"]]
So the first two arguments of the function will be the values [:bar, "bar"] and [:foo, "foo"] (regardless of their parameter name!).
If you want to have something similar to keyword arguments in Ruby, you can make use of the fact that you don't need the braces when passing a hash to a function as the last argument:
def foo(opts = {})
bar = opts[:bar] || <default>
foo = opts[:foo] || <default>
# or with a lot of parameters:
opts = { :bar => <default>, :foo => <default>, ... }.merge(opts)
end
foo(foo: 3) # equivalent to foo({ foo: 3 })
EDIT:
As of version 2.0, Ruby now supports named arguments with a dedicated syntax. Thanks to user jgoyon for pointing that out.

This problem well known as named parameters. Because Ruby doesn't have named parameters so here's classical way to deal with it:
def foo(options = {})
options = {:foo => nil, :bar => nil, :baz => nil, qux => nil}.merge(options)
..
end
or using ActiveSupport::CoreExtensions::Hash::ReverseMerge from Rails
def foo(options = {})
options.reverse_merge!{:foo => nil, :bar => nil, :baz => nil, qux => nil}
..
end
In future version Ruby 2.0 will possible to use named parameters
def foo(foo: nil, bar: nil, baz: nil, qux: nil)
puts "foo is #{foo}, bar is #{bar}, baz is #{baz}, ..."
..
end

Related

define_method with predefined keyword arguments

I want to define a method that takes keyword arguments. I would like it to raise when keyword arguments are not provided, and I can code this myself - but ideally I would like to let Ruby do that for me. Also I would like to be able to inspect the freshly defined method using Method#parameters. If I use a shorthand double-splat (like **kwargs) the actual structure I expect is not visible to parameters.
I can of course do this:
define_method(:foo) do | foo:, bar: |
# ...
end
which achieves the desired result:
method(:foo).parameters
# => [[:keyreq, :foo], [:keyreq, :bar]]
but I cannot pass those arguments programmatically, they have to be literally placed in the code. Is there a way I could bypass this?
You have to use eval to define arguments dynamically (not just keyword arguments), e.g. using class_eval:
class MyClass
name = :foo
args = [:bar, :baz]
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{name}(#{args.map { |a| "#{a}:" }.join(', ')}) # def foo(bar:, baz:)
[#{args.join(', ')}] # [bar, baz]
end # end
METHOD
end
MyClass.new.foo(bar: 1, baz: 2)
#=> [1, 2]
MyClass.instance_method(:foo).parameters
#=> [[:keyreq, :bar], [:keyreq, :baz]]

Syntactic sugar forces me to use an ugly statement

def foo(bar)
'return value'
end
foo 'bar' # => "return value"
def foo=(bar)
'return value'
end
foo = 'bar' # => "bar"
send :foo=, 'bar' # => "return value"
I want foo = 'bar' to return "return value" but not to use send for this purpose. How can I do this?
Update
I need a desired behavior in my gem. Here is an example:
car = Car.new
car.gear # => :first
car.next_gear # => :second
car.gear # => :second
car.gear = :fourth # => false
car.gear # => :second
car.gear = :third # => :third
car.gear # => :third
Assignments always return the right hand side of an assignment.
Have a look at the ruby documentation for details:
Methods that end with an equals sign indicate an assignment method.
For assignment methods the return value is ignored, the arguments are
returned instead.
Having said that, foo = bar also assigns to a local variable foo instead of using the foo= method. Again, this is defined in the ruby docs:
When using method assignment you must always have a receiver. If you
do not have a receiver Ruby assumes you are assigning to a local
variable
You can test that by running
local_variables #=> []
def foo=(bar);end
foo = 42
local_variables #=> [:foo]
You see that the local variable foo was created. Better use self.foo = 'bar'.
To address your specific problem with your gem: Follow Neil's advice and use an extra method like change_gear for what you want to do. He gave you good council in his comments.
It's a Ruby gotcha: the return value of accessor methods get ignored.
This code will make it more clear what is actually happening:
#!/usr/bin/env ruby
def foo(bar)
p "called :foo w/ #{bar.inspect}"
end
def foo=(bar)
p "called :foo= with #{bar.inspect}"
end
ret = (foo :bar1) # calls foo(bar)
p "ret: #{ret}" # "ret: called :foo w/ :bar1"
ret = (foo = :bar2) # assigns a local variable foo = 'bar2'
p "ret: #{ret}" # "ret: bar2"
ret = (send :foo=, :bar3) # calls foo=(bar), returns what p returns
p "ret: #{ret}" # "ret: called :foo= with :bar3"
ret = (self.foo = :bar4) # calls foo=(bar), returns ???
p "ret: #{ret}" # "ret: bar4"
Basically, the Ruby parser (in 2.1 at least) behaves as if self.foo= was calling an accessor method (even if it actually isn't assigning anything), and will always return the value passed to it irrespective of what you sent it, rather than the accessor's return value.
Demonstration:
#!/usr/bin/env ruby
class << self
attr_accessor :foo
def foo=(bar)
p "called :foo= with #{bar.inspect}"
#foo = :baz
end
end
ret = (self.foo = :bar)
p "ret: #{ret} vs #foo: #{#foo.inspect}"
Outputs:
"called :foo= with :bar"
"ret: bar vs #foo: :baz"
Edit: hat #tessi for the reference:
Methods that end with an equals sign indicate an assignment method. For assignment methods the return value is ignored, the arguments are returned instead.
I think the reason why it's failing is that local variable names take precedence over method names when they are defined.
So you need to use send so that self knows it's looking for a method instead of a variable.
You need to do this:
self.foo = 'bar'

Pass a splatted param list to a nested function

I have a set of nested functions that each take an arbitrary list of arguments:
def foo *args
bar args
end
def bar *args
baz args
end
def baz *args
end
Calling foo with a set of args like :a => :foo, :b => :bar gives us a single element array after the splat:
[{:a => :foo, :b => :bar}]
And then passing that along to the nested function, and again through a splat, makes for this:
[[{:a => :foo, :b => :bar}]]
Is it appropriate to pass args[0] along to the nested function, or is there some kind of reverse splat that I should be using instead?
If you want to relay splatted arguments to another function, just splat them again (the operator behaves the opposite way when used in a method call (vs. method definition))
def foo(*args)
bar *args
end

Ruby: How to map a function to a hash

I can't figure out how to assign a function call to a ruby hash.
What I want to do is to assign a function to a hash key, and later
call this function using the classic hash lookout syntax.
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => Foo() }
puts "Executing"
test["foo"]
This code fails, function Foo is called after puts "Assign", during hash creation, and nothing happens after puts "Executing"
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => Foo }
puts "Executing"
test["foo"]
with this code I receive an uninitialized constant Foo (NameError).
Finally with
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => :Foo }
puts "Executing"
test["foo"]
I get not outputs.
Any suggestions?
Thanks to all for answres and suggestions.
What I'm going to do is to test
if a hash based approach to call function is faster than
an equivalent code based on if / case statements.
funcs["foo"].call
fatser than
if func_name == "foo" then
Foo()
elsif ...
...
end
or
case func_name
when "foo"
Foo()
when ...
...
end
Obviously for a big number of functions (~150) and hundreds of
calling cycles
you could use lambda's instead of methods. Two options here:
hash = {:foo => lambda { puts 'bar } }
hash[:foo].call
the second (more complicated) is this:
irb(main):001:0> class Hash
irb(main):002:1> alias :orig_anc :'[]'
irb(main):003:1>
irb(main):004:1* def [](key)
irb(main):005:2> if orig_anc(key).is_a? Proc
irb(main):006:3> orig_anc(key).call
irb(main):007:3> else
irb(main):008:3* orig_anc(key)
irb(main):009:3> end
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> h = {:hello => 'world', :foo => lambda { puts 'bar' }}
=> {:hello=>"world", :foo=>#<Proc:0x843224c#(irb):12 (lambda)>}
irb(main):013:0> h[:hello]
=> "world"
irb(main):014:0> h[:foo]
bar
=> nil
irb(main):015:0>
The second one just allows you to skip using 'call' method
There is no easy possibility to make your function execute simply by retrieving the hash key withput overriding Hash's [] method, as Vlad pointed out, i.e.
def foo
puts "hi"
end
... # magic
test["foo"] # outputs hi
won't work. What you can do, though, is assign the method reference using Object#method and then invoke it using call:
def foo
puts "hi"
end
test = { "foo" => method(:foo) }
test["foo"].call # => hi
First things first, start method names with lower case letters. Then, moving to first example, Ruby is eager so test = { "foo" => Foo() } Foo is called.
In the second example, variables starting with an uppercase letter are considered constants, so Ruby looks for one and do not bother looking for a method. Take into account that if foo where a method then foo would be called as in the first example.
Third example: test["foo"] returns :Foo, a symbol. Nowhere in your code Foo() is invoked.
*Suggestions
1.- Take a look at identifiers nomenclature in Ruby.
2.- You may took a look at lambda, Proc and Object#method
3.- If you have some money to spare and don't mind buying a pair of books, give Programming Ruby and Metaprogramming in Ruby a chance (both can be bought in PragProg.
You could try using the hash constructor with a block, so
def foo
puts "hi"
end
test = Hash.new do |hash, key|
send(key) if respond_to?(key)
end
test["foo"] # prints "hi", returns nil

Named Parameters in Ruby Structs

I'm pretty new to Ruby so apologies if this is an obvious question.
I'd like to use named parameters when instantiating a Struct, i.e. be able to specify which items in the Struct get what values, and default the rest to nil.
For example I want to do:
Movie = Struct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This doesn't work.
So I came up with the following:
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
if (args.length == 1 and args.first.instance_of? Hash) then
args.first.each_pair do |k, v|
if members.include? k then
self[k] = v
end
end
else
super *args
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This seems to work just fine, but I'm not sure if there's a better way of doing this, or if I'm doing something pretty insane. If anyone can validate/rip apart this approach, I'd be most grateful.
UPDATE
I ran this initially in 1.9.2 and it works fine; however having tried it in other versions of Ruby (thank you rvm), it works/doesn't work as follows:
1.8.7: Not working
1.9.1: Working
1.9.2: Working
JRuby (set to run as 1.9.2): not working
JRuby is a problem for me, as I'd like to keep it compatible with that for deployment purposes.
YET ANOTHER UPDATE
In this ever-increasing rambling question, I experimented with the various versions of Ruby and discovered that Structs in 1.9.x store their members as symbols, but in 1.8.7 and JRuby, they are stored as strings, so I updated the code to be the following (taking in the suggestions already kindly given):
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
return super unless (args.length == 1 and args.first.instance_of? Hash)
args.first.each_pair do |k, v|
self[k] = v if members.map {|x| x.intern}.include? k
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This now appears to work for all the flavours of Ruby that I've tried.
Synthesizing the existing answers reveals a much simpler option for Ruby 2.0+:
class KeywordStruct < Struct
def initialize(**kwargs)
super(*members.map{|k| kwargs[k] })
end
end
Usage is identical to the existing Struct, where any argument not given will default to nil:
Pet = KeywordStruct.new(:animal, :name)
Pet.new(animal: "Horse", name: "Bucephalus") # => #<struct Pet animal="Horse", name="Bucephalus">
Pet.new(name: "Bob") # => #<struct Pet animal=nil, name="Bob">
If you want to require the arguments like Ruby 2.1+'s required kwargs, it's a very small change:
class RequiredKeywordStruct < Struct
def initialize(**kwargs)
super(*members.map{|k| kwargs.fetch(k) })
end
end
At that point, overriding initialize to give certain kwargs default values is also doable:
Pet = RequiredKeywordStruct.new(:animal, :name) do
def initialize(animal: "Cat", **args)
super(**args.merge(animal: animal))
end
end
Pet.new(name: "Bob") # => #<struct Pet animal="Cat", name="Bob">
With newer versions of Ruby you can use keyword_init: true:
Movie = Struct.new(:title, :length, :rating, keyword_init: true)
Movie.new(title: 'Title', length: '120m', rating: 'R')
# => #<struct Movie title="Title", length="120m", rating="R">
The less you know, the better. No need to know whether the underlying data structure uses symbols or string, or even whether it can be addressed as a Hash. Just use the attribute setters:
class KwStruct < Struct.new(:qwer, :asdf, :zxcv)
def initialize *args
opts = args.last.is_a?(Hash) ? args.pop : Hash.new
super *args
opts.each_pair do |k, v|
self.send "#{k}=", v
end
end
end
It takes both positional and keyword arguments:
> KwStruct.new "q", :zxcv => "z"
=> #<struct KwStruct qwer="q", asdf=nil, zxcv="z">
A solution that only allows Ruby keyword arguments (Ruby >=2.0).
class KeywordStruct < Struct
def initialize(**kwargs)
super(kwargs.keys)
kwargs.each { |k, v| self[k] = v }
end
end
Usage:
class Foo < KeywordStruct.new(:bar, :baz, :qux)
end
foo = Foo.new(bar: 123, baz: true)
foo.bar # --> 123
foo.baz # --> true
foo.qux # --> nil
foo.fake # --> NoMethodError
This kind of structure can be really useful as a value object especially if you like more strict method accessors which will actually error instead of returning nil (a la OpenStruct).
Have you considered OpenStruct?
require 'ostruct'
person = OpenStruct.new(:name => "John", :age => 20)
p person # #<OpenStruct name="John", age=20>
p person.name # "John"
p person.adress # nil
You could rearrange the ifs.
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
# I think this is called a guard clause
# I suspect the *args is redundant but I'm not certain
return super *args unless (args.length == 1 and args.first.instance_of? Hash)
args.first.each_pair do |k, v|
# I can't remember what having the conditional on the same line is called
self[k] = v if members.include? k
end
end
end
Based on #Andrew Grimm's answer, but using Ruby 2.0's keyword arguments:
class Struct
# allow keyword arguments for Structs
def initialize(*args, **kwargs)
param_hash = kwargs.any? ? kwargs : Hash[ members.zip(args) ]
param_hash.each { |k,v| self[k] = v }
end
end
Note that this does not allow mixing of regular and keyword arguments-- you can only use one or the other.
If your hash keys are in order you can call the splat operator to the rescue:
NavLink = Struct.new(:name, :url, :title)
link = {
name: 'Stack Overflow',
url: 'https://stackoverflow.com',
title: 'Sure whatever'
}
actual_link = NavLink.new(*link.values)
#<struct NavLink name="Stack Overflow", url="https://stackoverflow.com", title="Sure whatever">
If you do need to mix regular and keyword arguments, you can always construct the initializer by hand...
Movie = Struct.new(:title, :length, :rating) do
def initialize(title, length: 0, rating: 'PG13')
self.title = title
self.length = length
self.rating = rating
end
end
m = Movie.new('Star Wars', length: 'too long')
=> #<struct Movie title="Star Wars", length="too long", rating="PG13">
This has the title as a mandatory first argument just for illustration. It also has the advantage that you can set defaults for each keyword argument (though that's unlikely to be helpful if dealing with Movies!).
For a 1-to-1 equivalent with the Struct behavior (raise when the required argument is not given) I use this sometimes (Ruby 2+):
def Struct.keyed(*attribute_names)
Struct.new(*attribute_names) do
def initialize(**kwargs)
attr_values = attribute_names.map{|a| kwargs.fetch(a) }
super(*attr_values)
end
end
end
and from there on
class SimpleExecutor < Struct.keyed :foo, :bar
...
end
This will raise a KeyError if you missed an argument, so real nice for stricter constructors and constructors with lots of arguments, data transfer objects and the like.
this doesn't exactly answer the question but I found it to work well if you have say a hash of values you wish to structify. It has the benefit of offloading the need to remember the order of attributes while also not needing to subClass Struct.
MyStruct = Struct.new(:height, :width, :length)
hash = {height: 10, width: 111, length: 20}
MyStruct.new(*MyStruct.members.map {|key| hash[key] })
Ruby 2.x only (2.1 if you want required keyword args). Only tested in MRI.
def Struct.new_with_kwargs(lamb)
members = lamb.parameters.map(&:last)
Struct.new(*members) do
define_method(:initialize) do |*args|
super(* lamb.(*args))
end
end
end
Foo = Struct.new_with_kwargs(
->(a, b=1, *splat, c:, d: 2, **kwargs) do
# must return an array with values in the same order as lambda args
[a, b, splat, c, d, kwargs]
end
)
Usage:
> Foo.new(-1, 3, 4, c: 5, other: 'foo')
=> #<struct Foo a=-1, b=3, splat=[4], c=5, d=2, kwargs={:other=>"foo"}>
The minor downside is that you have to ensure the lambda returns the values in the correct order; the big upside is that you have the full power of ruby 2's keyword args.

Resources