Pass Arguments by Reference to a Block with the Splat Operator

Pass arguments by reference to a block with the splat operator

  1. In Ruby when you write x = value you are creating a new local variable x whether it existed previously or not (if it existed the name is simply rebound and the original value remains untouched). So you won't be able to change a variable in-place this way.

  2. Integers are immutable. So if you send an integer there is no way you can change its value. Note that you can change mutable objects (strings, hashes, arrays, ...):

    def method
    a = [1, 2, "hello"]
    yield(*a)
    p a
    end

    method { |x,y,z| z[1] = 'u' }
    # [1, 2, "hullo"]

Note: I've tried to answer your question, now my opinion: updating arguments in methods or blocks leads to buggy code (you have no referential transparency anymore). Return the new value and let the caller update the variable itself if so inclined.

Why do Ruby procs/blocks with splat arguments behave differently than methods and lambdas?

There are two types of Proc objects: lambda which handles argument list in the same way as a normal method, and proc which use "tricks" (Proc#lambda?). proc will splat an array if it's the only argument, ignore extra arguments, assign nil to missing ones. You can partially mimic proc behavior with lambda using destructuring:

->((x, y)) { [x, y] }[1]         #=> [1, nil]
->((x, y)) { [x, y] }[[1, 2]] #=> [1, 2]
->((x, y)) { [x, y] }[[1, 2, 3]] #=> [1, 2]
->((x, y)) { [x, y] }[1, 2] #=> ArgumentError

How do I pass an array to a method that accepts an attribute with a splat operator?

Just put a splat when calling the method?

sum(*[1,2,3])

Map splat arguments over method parameters

def f(params,*args)
# elements to be assigned to splat parameter
splat = args.count - params.count + 1

# will throw an error if splat < 0 as that means not enough inputs given
params.map{ |p|

[ p[1] , ( p.first == :rest ? args.shift(splat) : args.shift ) ]

}
end

Examples

def splatter(x,*y,z)
# some code
end

f(method(:splatter).parameters, 1,2,3,4)
#=>[[:x, 1], [:y, [2, 3]], [:z, 4]]

def splatter(x,y,*z)
# some code
end

f(method(:splatter).parameters, 1,2,3,4)
# => [[:x, 1], [:y, 2], [:z, [3, 4]]]

def splatter(x,*z)
# some code
end

f(method(:splatter).parameters, 1)
# => [[:x, 1], [:z, []]]

splat operator on hash for keyword arguments in ruby method definition

Yeah... that's not a thing you can do.

**foo in an argument list is how you collect a kwargs hash, so it can't also be how you inject one.

More importantly, the main point of kwargs is that they explode the hash into local variables -- that can't work if it's expanding a hash at runtime.

The closest you could get would be something like:

def initialize(**values)
values = DEFAULTS.merge(values)
raise "..." unless (values.keys - DEFAULTS.keys).empty?
@a = values[:a]
@b = values[:b]
end

Get value into Ruby code block without it being the last statement

You can implicitly return values using instance variables, like it is done in Rails:

def bar
yield
puts "The block has assigned @value to #{@value}"
end

bar do
@value = 42
nil # Do not return value intentionally
end

This code outputs:

The block has assigned @value to 42

UPDATE

Another nice option is to use helper methods, many frameworks do it too:

def bar
def render(value)
@value = value
end

yield
puts "The block has rendered #{@value}"
end

bar do
render 42
nil # Do not return value intentionally
end

This code outputs:

The block has rendered 42

UPDATE 2

Please see an important addition from @Marek Lipka https://stackoverflow.com/a/19542149/203174

Examples of using splat operator *

I can show you a few examples of how it's used.

Say you have a method which takes a few arguments

def foo(a,b,c)

And you want to call this method, provide the values for arguments using an array. You can write:

foo(*[1,2,3])

Another situation is you want to monkey patch a record but don't want to break the original functionality. For example, overwriting save in a rails model:

def save(*args)
# do something custom here
super(*args)
end

This is to say "I don't care about the arguments to this function, but I want to make sure they are all passed to the super call. "

How to annotate type using the splat operator

Because it seems that in the above use-case, macro versions of reflection functions couldn't reach the right argument types, using original function instead of macro, could be helpful:

f(x, y) = x^2 + y^2
vec = [1.0, 2.0, 'a']
@code_warntype(f(vec[1:2]...)) # => Nothing
code_warntype(f,map(typeof,vec[1:2]))
# Variables:
# x::Float64
# y::Float64
# .....

This logic is true for all reflection macros, using their variant function with a (function, collection of types).

references:

  1. The macro @code_warntype has function variant: @code_warntype
  2. How macros generated: macro generator
  3. Util function to reach types: gen_call_with_extracted_types


Related Topics



Leave a reply



Submit