Order of optional parameters in initializer - ruby

Why am I getting the following error
a.rb:4: syntax error, unexpected '=', expecting ')'
...alize(a = "default val", b, c = [])
a.rb:18: syntax error, unexpected `end', expecting end-of-input
on the following code
class A
attr_reader :a, :b, :c
def initialize(a = "default val", b, c = [])
#a = a
#b = b
#c = c
end
def self.open(a)
args = {
b: "2",
c: [1, 2, 3]
}.values
A.new(a, *args)
end
end
when trying to call a property
a2 = A.open("something")
p a2.a
Removing the last default value from initializer =[] solves the problem. Reordering the arguments so the parameters with default values go at the end of initialize helps too.
def initialize(a = "default val", b, c)
or
def initialize(b, c=[], a = "default val") and A.new(*args, a) (but I suppose this is wrong)
As I remember there was a rule about ordering of optional params.

If you define optional parameters before AND after mandatory parameters, in some cases it will be impossible to decide how a goven list or arguments should map to the defined parameters.
In your case, when defining this method:
class A
def initialize(a = "default val", b, c = [])
#...
end
end
How would you handle this when giving two arguments,. i.e.
A.new 'hello', 'world'
You could then assign
a = 'hello'
b = 'world'
c = []
but you could equally set
a = 'default val'
b = 'hello'
c = 'world'
Given this unambiguity, Ruby rejects those constructs. You thus have to define all optional parameters either at the front or the back of your parameter list, while it is commonly accepted standard to define optional arguments only at the end.
If you want to be more specific about which arguments should be set with a large number of optional parameters, you can also use keyword arguments. Since you have to specify the name of the arguments when calling the method here, the order of mandatory and optional keyword arguments doesn't matter.

Related

using a string (from gets) directly as a variable name

Starting out my Towers of Hanoi assignment, I have
a = [6,5,4,3,2,1]
b = []
c = []
puts "Type a, b, or c"
from = gets.chomp
# the user types a lower-case a
popped = from.pop
Now this obviously fails because pop is not a string method.
So other than
if from == a
popped = a.pop
elsif from == b
popped = b.pop
, is there a nice ruby shortcut to get the pop I intend?
You can use eval:
a = [6,5,4,3,2,1]
b = []
c = []
puts "Type a, b, or c"
from = gets.chomp
popped = eval(from).pop
But eval is typically seen as a bad idea for security, performance, and debugging reasons.
options = {
:a => [6,5,4,3,2,1]
:b => []
:c => []
}
puts "Type a, b, or c"
from = gets.chomp
popped = options[from.to_sym].pop
I have to strongly advise you avoid the use of the method using eval above, as it allows the user to input arbitrary code. Use a hash instead to store all your options.

How can I access the arguments?

I want a method to be used within another method and returns the arguments without the need of mentioning the argument names, something like return_arguments in the following.
def foo a, b, *c
... # part a
p return_arguments
... # part b
end
foo(1, "blah blah", :a, :b)
... # return from part a
# => [1, "blah blah", :a, :b]
... # return from part b
Is this possible? I figured out that binding, local_variables, and eval may be used, but am not sure how to distinguish the arguments from other local variables defined in part a above. Is there a particular ordering rule for local_variables? If so, maybe I can use that together with arity to extract the arguments.
It's definitely inefficient, but here's a solution (although you probably should be using a hash like #tadman mentioned):
set_trace_func proc { |e,_,_,i,b,_|
if e == 'call'
p = method(i).parameters
#arguments = p.map {|p| eval(p[1].to_s,b)}
end
}
def foo a, b, *c
a = 50
b = 3
return #arguments
a = 11
b = 2
end
p foo(1, "blah blah", :a, :b)
Use local_variables first thing in the method, i.e. before setting any local variables, and save the result for later use.
Probably the easiest thing to do would be something like this:
def foo(*args)
a, b, *c = *args
# (...)
p args
# (...)
end
Or conversely:
def foo(a, b, *c)
args = [a, b, *c]
# (...)
p args
# (...)
end
I'm not sure why you are opposed to mentioning the argument names at least once. It's most straightforward and readable to either assign your "arguments" inside your method (letting to keep the full array) or to reconstruct the array from the parsed arguments.

Why is the splat/unary operator changing the assigned value a when p is called before *a = ""?

To give a little context around how I understand the problem.
Using splat collect on a string sends :to_a or :to_ary to the String
class String
def method_missing method, *args, &block
p method #=> :to_ary
p args #=> []
p block #=> nil
end
end
*b = "b"
So I was thinking that redefining the :to_ary method would be what I'm after.
class String
def to_ary
["to_a"]
end
end
p *a = "a" #=> "a"
p a #=> "a"
*b = "b"
p b #=> ["to_a"]
Now this confuses me to no end.
Printing the result from the *a = "a" changes the value assigned to a?
To demonstrate further
class String
def to_ary
[self.upcase!]
end
end
p *a = "a" #=> "a"
p a #=> "a"
*b = "b"
p b #=> ["B"]
Very interesting question! Ruby takes this expression:
p *a = "a"
and translates it to something like this:
temp = (a = "a")
p *temp
So the first thing that happens is that a gets assigned to "a", and then the result of the assignment expression which is "a" gets splatted and sent to p. Since p's default behaviour when sent multiple arguments is just to iterate over and print each one, you only see "a" appear.
In short, it follows a "assign then splat" order of evaluation. So a gets assigned to "a" before the string gets splatted.
When you don't have a function call however, it is interpreted as something like this:
# *a = "a" gets interpreted as:
temp = "a"
a = *temp
This follows a "splat then assign" order of evaluation. So a gets assigned after the string gets splatted.
You can see what's being received by a function by going like this:
def foo *args
puts args.inspect
end
foo *a = "a" # outputs ["a"]
a # outputs "a"
Hope this clears up what's going on!
In short (thanks to Mark Reed):
p *a = "a" # interpreted as: p(*(a = "a"))
*a = "a" # interpreted as: a = *("a")

What are the different possibilities of passing parameters into ruby methods? param/hashlist/array/aproc?

I am trying to understand Ruby in more depth and was reading:
http://www.zenspider.com/Languages/Ruby/QuickRef.html#25
However, I dont understand what the following means in that definition:
parameters := ( [param]* [, hashlist] [*array] [&aProc] )
I know "param" is any number of parameters specified, and then i get lost what the remainder means?
For example, I have:
def doIt(param1, param2, param3)
end
and in this case [param]* is equal to param1, param2, param3...so where does hashlist come in? and *array and &aProc?
Could someone please clarify this for me
If the last argument of a method is a non-empty hash literal, you can pass it like this
def foo(x, y, the_hash)
p the_hash['key2']
end
foo(0, 0, :key1 => 'val1', 'key2' => 42) # 42
instead of the normal way:
foo(0, 0, { :key1 => 'val1', 'key2' => 42 }) # 42
Usually, the hash is defaulted to {} (def foo(x, y, the_hash = {})) so passing an empty hash fits to this scheme.
Additionally, you can specify one "catch-all" (splat) argument which will become an array of all arguments not already assigned to normal arguments:
def foo(p1, *rest)
p rest
end
foo(0) # "[]"
foo(0, 23, 42) # "[23, 42]"
Or, e.g.
def foo(p1, *rest, p2)
p rest
end
foo(0, 100) # "[]"
foo(0, 100, 23, 42) # "[100, 23]"
You cannot have splat arguments before arguments with default value. Therefore, the hash argument syntax and the splat argument are rarely used in combination.
Finally, in your method definition you can have as last argument an identifier prefixed with & which will point to the block at the method invocation (its Proc object) or be nil if there is none. This is normally not needed if you just want to invoke the block -- you can use yield for that.
def foo(&p)
p.call
end
foo { puts "hello" } # hello
vs.
def foo
yield
end
foo { puts "hello" } # hello

Why doesn't terse way of defining new hashes in Ruby work (they all refer to same object)

I need to establish a number of Hashes, and I didn't want to list one per line, like this
a = Hash.new
b = Hash.new
I also new that apart from for Fixnums, I could not do this
a = b = Hash.new
because both a and b would reference the same object. What I could to is this
a, b, = Hash.new, Hash.new
if I had a bunch it seemed like I could also do this
a, b = [Hash.new] * 2
this works for strings, but for Hashes, they still all reference the same object, despite the fact that
[Hash.new, Hash.new] == [Hash.new] * 2
and the former works.
See the code sample below, the only error message triggered is "multiplication hash broken". Just curious why this is.
a, b, c = [String.new] * 3
a = "hi"
puts "string broken" unless b == ""
puts "not equivalent" unless [Hash.new, Hash.new, Hash.new] == [Hash.new] * 3
a, b, c = [Hash.new, Hash.new, Hash.new]
a['hi'] = :test
puts "normal hash broken" unless b == {}
a, b, c = [Hash.new] * 3
a['hi'] = :test
puts "multiplication hash broken" unless b == {}
In answer to the original question, an easy way to initialize multiple copies would be to use the Array.new(size) {|index| block } variant of Array.new
a, b = Array.new(2) { Hash.new }
a, b, c = Array.new(3) { Hash.new }
# ... and so on
On a side note, in addition to the assignment mix-up, the other seeming issue with the original is that it appears you might be making the mistake that == is comparing object references of Hash and String. Just to be clear, it doesn't.
# Hashes are considered equivalent if they have the same keys/values (or none at all)
hash1, hash2 = {}, {}
hash1 == hash1 #=> true
hash1 == hash2 #=> true
# so of course
[Hash.new, Hash.new] == [Hash.new] * 2 #=> true
# however
different_hashes = [Hash.new, Hash.new]
same_hash_twice = [Hash.new] * 2
different_hashes == same_hash_twice #=> true
different_hashes.map(&:object_id) == same_hash_twice.map(&:object_id) #=> false
My understanding is this. [String.new] * 3 does not create three String objects. It creates one, and creates a 3-element array where each element points to that same object.
The reason you don't see "string broken" is that you have assigned a to a new value. So after the line a = "hi", a refers to a new String object ("hi") while b and c still refer to the same original object ("").
The same occurs with [Hash.new] * 3; but this time you don't re-assign any variables. Rather, you modify the one Hash object by adding the key/value [hi, :test] (via a['hi'] = :test). In this step you've modified the one object referred to by a, b, and c.
Here's a contrived code example to make this more concrete:
class Thing
attr_accessor :value
def initialize(value)
#value = value
end
end
# a, b, and c all refer to the same Thing object
a, b, c = [Thing.new(0)] * 3
# Here we *modify* that object
a.value = 5
# Verify b refers to the same object as a -- outputs "5"
puts b.value
# Now *assign* a to a NEW Thing object
a = Thing.new(10)
# Verify a and b now refer to different objects -- outputs "10, 5"
puts "#{a.value}, #{b.value}"
Does that make sense?
Update: I'm no Ruby guru, so there might be a more common-sense way to do this. But if you wanted to be able to use multiplication-like syntax to initialize an array with a bunch of different objects, you might consider this approach: create an array of lambdas, then call all of them using map.
Here's what I mean:
def call_all(lambdas)
lambdas.map{ |f| f.call }
end
a, b, c = call_all([lambda{Hash.new}] * 3)
You can verify that this approach works pretty easily:
x, y, z = call_all([lambda{rand(100)}] * 3)
# This should output 3 random (probably different) numbers
puts "#{x}, #{y}, #{z}"
Update 2: I like numbers1311407's approach using Array#new a lot better.

Resources