When Passing a Ruby Array as an Argument, Why Does '<<' Append While '+=' Does Not

When passing a Ruby array as an argument, why does `` append while `+=` does not?

The main difference between the #<<, and #+ s that #<< is just the Array's instance method, so you just add a value to the specified instance of Array

arr = []
arr.__id__ # => 68916130
arr << 10
arr.__id__ # => 68916130

but in form of #+ is used assignment operator, which replace reference to a variable with a new instance, and that new instance shall not be passed into uplevel of the #add_to_array function.

arr = []
arr.__id__ # => 68916130
arr += [10]
arr.__id__ # => 68725310

NOTE: That += implies the #+ method plus assignment operator =, however ruby interpreter treats it as a specific operator not as a sum.

Additionals

The form arr = arr + [10] isn't work properly also.

def add_to_array(value, arr)
arr = arr + [value]
end
build_results()
# => []

Array assign vs. append behavior

You redefine local variable in the first method.

This is the same as

visited = []
local_visited = visited
local_visited = ['A']
visited
# => []

And in the second method:

visited = []
local_visited = visited
local_visited << 'A'
visited
# => ["A"]

How do I pass multiple arguments to a ruby method as an array?

Ruby handles multiple arguments well.

Here is a pretty good example.

def table_for(collection, *args)
p collection: collection, args: args
end

table_for("one")
#=> {:collection=>"one", :args=>[]}

table_for("one", "two")
#=> {:collection=>"one", :args=>["two"]}

table_for "one", "two", "three"
#=> {:collection=>"one", :args=>["two", "three"]}

table_for("one", "two", "three")
#=> {:collection=>"one", :args=>["two", "three"]}

table_for("one", ["two", "three"])
#=> {:collection=>"one", :args=>[["two", "three"]]}

(Output cut and pasted from irb)

Yield within Set to eliminate in an Array

In Ruby, when you are putting yield keyword inside any method(say #bar), you are explicitly telling #bar that, you will be using a block with the method #bar. So yield knows, inside the method block will be converted to a Proc object, and yield have to call that Proc object.

Example :

def bar
yield
end

p bar { "hello" } # "hello"
p bar # bar': no block given (yield) (LocalJumpError)

In the uniq_by method, we did not do anything to handle block argument. How is the passed argument handled by uniq_by method?

You did do, that is you put yield. Once you will put this yield, now method is very smart to know, what it supposed to so. In the line Messages.all.uniq_by { |h| h.body } you are passing a block { |h| h.body }, and inside the method definition of uniq_by, that block has been converted to a Proc object, and yield does Proc#call.

Proof:

def bar
p block_given? # true
yield
end

bar { "hello" } # "hello"

Better for understanding :

class Array
def uniq_by
seen = Set.new
select{ |x| seen.add?( yield( x ) ) }
end
end

is same as

class Array
def uniq_by
seen = Set.new
# Below you are telling uniq_by, you will be using a block with it
# by using `yield`.
select{ |x| var = yield(x); seen.add?(var) }
end
end

Read the doc of yield

Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling yield raises an exception. yield can take an argument; any values thus yielded are bound to the block's parameters. The value of a call to yield is the value of the executed code block.

In Ruby, why is it not enough to alias `method_missing` to catch call to undefined methods?

Generally speaking, you want method_missing (that is called by Ruby internally) to be an alias for mistrafe not vice versa. You have an implementation in mistrafe and you want reassign method_missing to be calling it.

That said, the following will work:

alias method_missing mistrafe

See the alias documentation.

Ruby required argument assignment logic: how does the ordering works?

Ruby provides a great deal of flexibility in how a method's arguments are defined. She cannot read your mind, however, so she'll raise an exception if the assignment of arguments to variables is incorrect or ambiguous. The basic rule is that Ruby can work out how the values passed to a method should be assigned to arguments if and only if you can work that out yourself, and conclude that there is only one way it can be done (subject to Rule 4 below). We can formalize this with the first three rules below.

If these rules are satisfied, it should be obvious from the discussion how the values passed to the method are assigned to arguments, though Rule 4, which is not obvious, is also needed.

  • Rule 1: required arguments must be preceded or followed by zero or more required variables, and nothing else (i.e., they must be stacked at the beginning and/or end of the list of values passed to the method).

  • Rule 2: If all of a method's arguments are required or have default values, the number of values passed to the method must be between n and n+m, where n is the number of required arguments and m is the number of arguments with defaults.

  • Rule 3: A splatted argument cannot precede a splatted argument or an argument with a default value.

  • Rule 4: If an argument a with a default value is followed by a splatted argument or another argument with a default value, and the above rules are satisfied, the first value passed to the method that has not already been assigned to a preceding argument is assigned to a.

Assignment of arguments to required variables

Ruby first checks that there is exactly one way that required arguments can be assigned to their associated variables.

Here are some examples where Ruby gives a thumbs-up.

def meth(w,x)
[w,x]
end
meth 1,2
#=> [1,2]

I gave the above for completeness.

def meth(w,x,*y,z)
[w,x,y,z]
end
meth 1,2,3,4,5,6
#=> [1, 2, [3, 4, 5], 6]
meth 1,2,3
#=> [1, 2, [], 3]

w and z are the first and last arguments, and x is preceded by one or more required arguments and nothing else.

def meth(w,x=1,y,z)
[w,x,y,z]
end
meth 1,2,3,4
#=> [1, 2, 3, 4]
meth 1,2,3
#=> [1, 1, 2, 3]

w and z are the first and last arguments, and y is followed by one or more required arguments and nothing else.

Now some examples where Ruby finds the assignment of variables to arguments is ambiguous, and therefore raises an exception (at compile-time).

def meth(*x,y,*z)
[x,y,z]
end
#=> syntax error, unexpected keyword_end, expecting end-of-input

The reason is obvious.

def meth(w,x=1,y,*z)
[w,x,y,z]
end
#=> syntax error, unexpected keyword_end, expecting end-of-input

Ruby doesn't know if x should equal its default or the second value passed to the method, which affects both y and *z.

Note that if the first (last) argument is required, the first (last) value passed to the method is assigned to that argument.

This establishes Rule #1.

When all arguments are required or have default values

def meth(w,x,y=3,z)
[w,x,y,z]
end
meth 1,2,3,4
#=> [1, 2, 3, 4]
meth 1,2,3
#=> [1, 2, 3, 3]
meth 1,2
#=> ArgumentError: wrong number of arguments (given 2, expected 3..4)
meth 1,2,3,4,5
#=> ArgumentError: wrong number of arguments (given 5, expected 3..4)

This establishes Rule #2.

Non-required arguments

If the method passes the test for required arguments, we can remove the associated variables from the argument list and then determine if Ruby would be happy with what's left (since the required arguments are stacked at the beginning and/or end of the argument list).

Two examples:

def meth(*w,*x)
end
#=> syntax error, unexpected *

def meth(*w,x=1)
end
#=> syntax error, unexpected '=', expecting ')'

This establishes Rule #3.

Remaining possibilities

Consider an argument with a default value that precedes either a splatted argument or another argument with a default value.

def meth(w=1,x=2,*y)
[w,x,y]
end
meth
#=> [1, 2, []]
meth 3
#=> [3, 2, []]
meth 3,4
#=> [3, 4, []]
meth 3,4,5
#=> [3, 4, [5]]
meth 3,4,5,6
#=> [3, 4, [5, 6]]

def meth(w=1,x=2)
[w,x]
end
meth
#=> [1, 2]
meth 3
#=> [3, 2]
meth 3,4
#=> [3, 4]

We see that the first values passed to the method will be assigned to the arguments with the default values. If no values are passed to the method both variables will be assigned to their default values.

This gives us Rule 4.

Design decision

Note that the Ruby monks could have decided to raise a compile-time exception for def meth(w=1,*x) and/or a run-time exception for def meth(w=1,x=2) when only one value is passed, but chose not to do so.

Common practice

As a matter of common practice, one nearly always sees the required arguments (if any) listed first, followed by arguments with default values (if any), followed by zero or one splatted arguments. There is no loss of functionality by ordering the variables in that way.



Related Topics



Leave a reply



Submit