I am trying to create a quick set method for my multi component variable (in my real code it is a Vector2d).
I would like to use an overwritten version of the = method.
For example:
def my_vector=(x, y)
#my_vector = Vector2d.new(x, y)
end
But it is not working because when I try to call this method I receive an error:
my_vector=1, 2 # => wrong number of arguments (given 1, expected 2)
my_vector=(1, 2) # => syntax error, unexpected ',', expecting ')'
my_vector=[1, 2] # => wrong number of arguments (given 1, expected 2)
This is my test suite:
# With one param it works
def my_var=(value)
#var = value
end
self.my_var=1
puts "var: " + #var.to_s
# With 2 params it doesn't work
def my_other_var=(value1, value2)
#other_var_component_1 = value1
#other_var_component_2 = value2
end
# Nothing works:
# self.my_other_var=1, 2
# self.my_other_var=(1, 2)
# self.my_other_var=[1, 2]
puts "other_var_component_1: " + #other_var_component_1.to_s
puts "other_var_component_2: " + #other_var_component_2.to_s
As #eugen correctly says, you cannot pass two arguments to a setter like
self.my_vector = 1, 2
The nearest thing you can achieve is to unpack the argument using pattern matching:
def myvector=(arg)
arg => [x, y]
#my_vector = Vector2d.new(x, y)
end
foo.my_vector = [1, 2]
An alternative is to define a simple helper method v so you can write
foo.my_vector = v 1, 2
You're defining a method named my_vector= (or my_other_var= in your 2nd example).
If you're just trying to call it with
my_vector = 1, 2
ruby will interpret that as an assignment to the my_vector variable.
You'll have to use send in order to call the method you've defined, as in
self.send :my_vector=, 1, 2
That's kind of ugly, but I don't see another way around this.
Slight modification to the supplied code to use an array argument instead:
def my_other_var=(values)
#other_var_component_1 = values[0]
#other_var_component_2 = values[1]
end
self.my_other_var=[1, 2]
"other_var_component_1: " + #other_var_component_1.to_s
#=> other_var_component_1: 1
"other_var_component_2: " + #other_var_component_2.to_s
#=> other_var_component_2: 2
Related
I am trying to create a method where I need to pass multiple name parameters with default values but I am getting syntax error, unexpected keyword_next error. How can I rectify it?
Eg method
def action(prev = nil, next = nil)
if prev.present?
# do something
elsif next.present?
# do something
else
# do something else
end
end
How can I make the above code work?
next is a reserved word in Ruby, as it is used to skip one iteration in enumerables. For example in the following code:
my_array = [1, 2, 3, 4]
my_array.each do |number|
next if number == 2
puts number
end
which will oputput:
1
3
4
This means that you can not use it as a variable/parameter name. To fix your code, you just need to rename the variable. For example:
def action(prev = nil, following = nil)
if prev.present?
# do something
elsif following.present?
# do something
else
# do something else
end
end
If I have a method with a parameter list like such:
def func(param1, param2, param3, para4, param5, param6)
what would be the best way to get rid of the long list? There are suggestion to create a separate object, but I'm not sure how I would do that.
You can use the variadic * splat in Ruby:
def f(*p)
p1,p2,p3,p4,p5,p6,p7,p8=*p
p p
p p8
end
f(*(1..8).to_a)
# [1, 2, 3, 4, 5, 6, 7, 8]
# 8
There are tradeoffs here. You loose the interpreter's help in counting the args actually passed to the method.
By doing:
p1,p2,p3,p4,p5,p6,p7,p8=*p
any arguments more than 8 to the method will be silently ignored and if less than 8, the shortfall will be silently assigned nil:
> f(1,2,3)
[1, 2, 3]
nil
If that is OK, the * is a useful shortcut.
You can also use an empty array as a default arg:
def f2(p=[])
p1,p2,p3,p4,p5,p6,p7,p8=*p
p p
p p8
end
Then call without the splat since now the method is expecting one argument, not a variable number:
f2(1..8)
With the same comment regarding the array length apply: arguments lengths greater than 8 are silently ignored and less than 8 are silently assigned nil
You can add argument checking to variable length arguments if desired:
def f(*p)
args_expected=8
raise ArgumentError, "\"#{__method__}\": wrong number of arguments (given %d expected %d)" % [p.length, args_expected] if p.length !=args_expected
p1,p2,p3,p4,p5,p6,p7,p8=*p
end
> f(1,2,3)
ArgumentError: "f": wrong number of arguments (given 3 expected 8)
But now you are adding words and complexity back.
There are good reasons to use named positional arguments if you are expecting a fixed number of arguments. The function is easy to change and you are allowing the interpreter help you.
A suggestion would be to use a single hash with the parameters as keys/options.
def func(hash_param = {})
param1 = hash_param[:param1]
param2 = hash_param[:param2]
param3 = hash_param[:param3]
param4 = hash_param[:param4]
param5 = hash_param[:param5]
param6 = hash_param[:param6]
#rest_of_code
end
hash = {
param1: 'foo',
param2: 'bar',
param3: 'baz',
param4: 'qux',
param5: 'alice',
param6: 'bob'
}
func(hash)
If some specific keys inside the hash are not defined, they will simply return nil.
For example:
def recurse(value)
if value < 5
self.send(__method__, value + 1)
else
value
end
end
This works, but it's a bit ugly.
Basically I'm looking for a prettier way to call the currently executing method, without referring to it explicitly by name.
If there is a less-cryptic syntax for this, I would probably use it (to avoid the name duplication, reduce effort required for renaming a function, etc). If there isn't a nicer syntax for this, I'll just hard-code the name like normal.
It's a comment rather, as #sagarpandya82 mentioned, you can omit some redundant parts and use both variants. I would refactor it a bit:
def recurse(value)
return value unless value < 5 # return value if value >= 5
send(__method__, value + 1) # or just recurse(value + 1)
end
Non-recursion version with a block:
def non_recurse(value)
if value >= 5
yield value
else
(value..5).each do |i|
yield i
end
end
end
non_recurse(3) {|i| puts i}
#=> 3, 4, 5
non_recurse(6) {|i| puts i}
#=> 6
If you really want to use __method__, your method is correct and reasonably readable. To comply with usual Ruby guidelines, you could just remove returns and use 2 spaces as indent (as mentioned by #sagarpandya82 in the comments):
def recurse(value)
if value < 5
self.send(__method__, value + 1)
else
value
end
end
I don't see any reason to use self.send(__method__) here, so you could write :
def recurse(value)
if value < 5
recurse(value + 1)
else
value
end
end
Actually, I'd say that you don't need recursion at all. All your method does is to keep adding 1 to the value until it reaches 5. If the value is bigger than 5, it returns the value :
For integers:
def no_recurse(value)
[value, 5].max
end
no_recurse(4)
# 5
no_recurse(-3)
# 5
no_recurse(7)
# 7
no_recurse(-2**1000)
# 5
no_recurse(4.5)
# 5 # <- That's wrong
For floats, you'd just need to add the decimal part to 5. This will work for any number:
def no_recurse(value)
[value, 5 + value % 1].max
end
no_recurse(4.5)
# 5.5
no_recurse(5.5)
# 5.5
no_recurse(6)
# 6
no_recurse(-7)
# 5
Coming from a python background, I'm used to running functions providing only the parameters that I need:
def f(a=1,b=2)
print a,b
f(b=3) #prints "1 3"
Ruby seems to provide the same syntax for optional parameters:
def f(a=1, b=2)
puts "#{a} #{b}"
end
But running f(b=3) prints 3 2 instead.
My guess is that it's evaluating b=3 before the function call occurs, and passing it as the first argument.
Is this interpretation correct, and is there a way to call a function providing arbitrary arguments?
f(b=3) means a local variable b create with value assignment as 3, then it passed as an argument. Son in your method actual parameter a got value 3, and b goes with its default value 2.
Thus running f(b=3) prints 3 2 instead.
In Ruby when you are calling a method, you are just passing the values to the methods actual argument, no scope to set the value of its actual parameters from method calling area.
You should use Keyword Arguments to meet your need. I wouldn't write the code, as #sawa did it for you.
Notice that all arguments are evaluated prior to a method call. If you do f(b = 3), then value assignment b = 3 takes place first, whose return value is 3, and that is used as the argument, so it is the same as doing f(3). Since you provide only one argument, it will be interpreted as the first argument a, and the default value would be used for b.
You are using the wrong construction. This is the way to do it:
def f a: 1, b: 2
puts "#{a} #{b}"
end
f(b: 3)
# => 1 3
It is using keyword arguments introduced in Ruby 2.0, as is explained in a page that Arup's answer links to.
Here is a workaround for Ruby 1.8, Ruby 1.9.
def f h = {}
h[:a] ||= 1
h[:b] ||= 2
puts "#{h[:a]} #{h[:b]}"
end
f(:b => 3) # Ruby 1.8
f(b: 3) # Ruby 1.9
# => 1 3
I'm trying to write a method that acts as a setter and takes some extra arguments besides the assigned value. Silly example:
class WordGenerator
def []=(letter, position, allowed)
puts "#{letter}#{allowed ? ' now' : ' no longer'} allowed at #{position}"
end
def allow=(letter, position, allowed)
# ...
end
end
Writing it as an indexer works and I can call it like this:
gen = WordGenerator.new
gen['a', 1] = true
# or explicitly:
gen.[]=('a', 1, true)
But when I try any of the following, the interpreter complains:
gen.allow('a', 1) = false # syntax error
gen.allow=('a', 1, false) # syntax error
Why won't this work, am I missing the obvious?
It doesn't work because the parser doesn't allow it. An equals sign is allowed in expressions of the form identifier = expression, expression.identifier = expression (where identifier is \w+), expression[arguments] = expression and expression.[]= arguments and as part of a string or symbol or character literal (?=). That's it.
gen.send(:allow=, 'a', 1, false) would work, but at that point you could as well just give the method a name that doesn't include a =.
I have come across this and decided to pass my arguments as an array or hash.
E.g.:
def allow=(arguments)
puts arguments[:letter]
puts arguments[:position]
puts arguments[:allowed]
end
object.allow={:letter=>'A',:position=>3,:allowed=>true}