I know what this means:
def f(*args)
...
end
But what does this mean and why would you want to use it? Can it appear with named parameters, too?
def f(*)
...
end
def f(*) has the same effect as def f(*args), except that it does not name the globbed argument array. You might use it if you want the function to accept any number of arguments but don't actually need to refer to them within the function -- for example, if you are overriding a method but calling super without passing an explicit argument list, which results in the original arguments being passed to super.
You can write def f(a, b, *) as well.
Related
Given a class that has method which accepts a splat, for example a method you get from ActiveRecord::FinderMethods:
class Settle
def self.find(*args)
end
end
How should I call that method from another method, say another class, so that it has the exact same signature?
class Settler
def self.find(*args)
Settle.find(*args)
end
end
Or
class Settler
def self.find(*args)
Settle.find(args)
end
end
Or something else?
Note that the exact same signature is the important part: Settler.find should work exactly similar to Settle.find.
I am not, however, interested in code that allows the Settler.find signature to magically update whenever Settle.find changes into something completely different, like e.g. .find(scope, *args). In that case, updating the version in Settler is no problem.
It should be
Settle.find(*args)
This way, all the arguments passed into Settler.find, are passed also to Settle.find and stored in args array inside of it. So the args arrays inside of both methods hold the same value.
Passing arguments as 'splat' is quite simple - it's just passing an array as separate arguments. So if you have
ar = [arg1, arg2, arg3, argn]
calling
some_method(*ar)
is equivalent to
some_method(arg1, arg2, arg3, argn)
Here is an expectation that utilizes a custom RSpec matcher, yield_variables:
specify { expect{ |p| [3,4,5].my_each(&p) }.to yield_variables [3,4,5] }
my yield_variables matcher's matches? method utilizes a custom class called Probe (Probe is a stripped down version of RSpec's yield probe):
...
def matches? block
ap Probe.probe block
# note i am inspecting what is returned by Probe.probe with ap
end
...
# Probe class is what all my questions are about!
class Probe
attr_accessor :yielded_args
def initialize
self.yielded_args = []
end
def self.probe(block)
probe = new
block.call(probe)
probe.yielded_args
end
def to_proc
Proc.new { |*args| yielded_args << args }
end
end
Now my ap inside matches? reports this: [3,4,5] That is what I expect. However, I have no idea how the Probe class works!!
Problem 1) the matches? block
Normally, the argument we pass to matches? is what we expect the subject to return. i.e, I expect [3,4,5] to be passed into block.
Instead, |p| [3,4,5].my_each(&p) is passed into block, as a proc. Why is this?
Problem 2) block.call(probe)
I'm a bit shakey on procs so please explain slowly. But basically, here we take a new instance of my Probe class and 'send' the block to it, as an argument. That's how I'd explain it to the best of my abilities, but I might have it totally wrong so please explain slowly.
Problem 3) How is to_proc called?
How on earth is .to_proc called automatically? I believe it's called automatically by block.call(probe). Is to_proc an automatically called method like initialize? Is it automatically called whenever the class is sent to a proc? (Btw, the phrase the class is sent to a proc doesn't even make 100% sense to me - please explain. The block isn't passed into the class as an argument anymore. Or if the block is passed as an argument the block.call syntax feels really weird and backwards)
Problem 4) to_proc
How does to_proc have access to the expectation's subject i.e. |p| [3,4,5].my_each(&p) ? How is Proc.new 's *args automatically populated with every single possible yield argument, even though I've only passed in |p| ? How does Proc.new loop along with my my_each, incrementally placing all my args in an array? How does Proc.new have any knowledge of the subject |p| [3,4,5].my_each(&p)? How?? Explain please, and thanks.
Block based marchers work a little differently to other matchers. Typically you want to do something that would not be possible if the expression you were interested in was evaluated and the result passed to you.
For example the raises_error matcher wants to execute the block itself to see that the correct exception is raised and the change matcher wants to evaluate some other expression before and after to see if it changes in the specified way. This is why you are passed a block rather than the value.
The block you are passing to expect takes 1 argument and uses this as the block in the call to my_each, so when your Probe.probe method calls the block it has to pass something as the value. You've phrased as "sending the block to the probe instance" but it is the other way around: the block is called using probe as its argument.
The block executes and calls my_each. Inside here p is the instance of Probe. Prefixing an argument with a & tells ruby that this argument should be used as the method's block (the method being my_each). If the argument is not already a proc ruby calls to_proc on it. This is how Probe#to_proc is called
Assuming that my_each behaves in a similar way to normal each it will call its block (ie the return value of to_proc) once for each of the values in the array.
Normally your matcher would then compare the return value from Probe.probe to the actual value.
Every time I've seen empty parameters, it appeared in a method like this:
def method_name(arguments)
#stuff to be executed
end
And then a method is called. Now I've come across this:
x = something()
What am I looking at? I am aware it is a variable, but what is the empty part?
Imagine a situation where you have a variable something and a method something. Whenever you refer something() you are referring to the method.
def something
"Java"
end
something = "Ruby"
a = something #=> value of a is "Ruby"
a = something() #=> value of a is "Java" # got from method
Ruby method parameters are very flexible. According to the method definition, the parameters that are given in the call can be passed directly, defaulted if absent, or compressed to a single Array parameter.
This short program demonstrates. The way parameter checking works is
Parameters supplied in the call are first allocated to all the individual non-defaulted parameters in the method definition, from first to last.
If there are insufficient actual parameters to match all non-defaulted formal parameters, a wrong number of arguments (N for M) (ArgumentError) is raised.
If any actual parameters remain then they are then allocated to all defaulted parameters, from first to last.
If any actual parameters still remain, then they will be bundled into an array and allocated to a splat parameter, if one has been defined.
If any actual parameters still remain, and no splat parameters have been defined, a wrong number of arguments (N for M) (ArgumentError) is raised.
This program demonstrates some of those situations. The commented lines would raise the error described.
def method_name(arguments)
puts "method_name(#{arguments})"
end
def something()
puts 'something()'
end
def something_else(param = 99)
puts "something_else(#{param})"
end
def something_more(param = 99, *rest)
puts "something_else(#{param}, #{rest})"
end
#method_name()
method_name(1)
#something(1)
something()
something_else()
something_else(42)
something_more()
something_more(1)
something_more(1, 2)
output
method_name(1)
something()
something_else(99)
something_else(42)
something_more(99, [])
something_more(1, [])
something_more(1, [2])
In ruby, parentheses are optional, so in your example, calling method_name is the equivalent of calling method_name(), however your method requires an argument. Perhaps this would illustrate it better:
def method_name(arguments={})
#do stuff
end
This method has an optional parameter of a hash. You can call this method with any of: method_name, method_name(), or method_name(argument1: "something")
I am working on Ruby Koans about_message_passing.rb and got the code working for method_missing as follows:
def method_missing(method_name, *args, &block)
#messages << method_name
#object.__send__(method_name, *args, &block)
end
This code seems to work, but I do not quite understand why the splat in needed in *args and the & is needed with the block.
If I were defining a method, I understand that the * and & are used to denote an array argument and block argument respectively, but what does it mean when they are used with the send method to invoke a method on an object?
I'll take these one at a time. take method_missing out of this completely, since it just makes what's going on confusing. It's actually completely unrelated to that.
The splat * does 2 things. In the arguments of a method definition, it soaks up multiple arguments into an array. When used in method invocation it splats out an array into individual arguments. Using both allows you to forward any number of arguments to another method.
def foo(*args)
bar(*args)
end
def bar(a, b, c)
puts a
puts b
puts c
end
foo(1,2,3) # prints 1, 2 and then 3
Since you are basically forwarding all arguments, this is the same pattern.
The & is for the block argument. There can be exactly one of these per method invocation, it's the block that hangs off the end. It's a special argument, in that it doesn't go in the arguments directly. You can capture the block to a variable by capturing add &someblock as the last argument in a method definition.
Then you can pass a block in a method invocation using the same syntax.
def foo(&block)
bar(&block)
end
def bar
yield
end
foo { puts 'hello' } # prints hello
This allows you pass the hanging block to another method, without invoking it. It's not always required because you usually just use yield to execute whatever block was passed. But if you want to do something besides just execute it, you need to capture a reference to the block itself.
So if you combine these 2 things, you get the ultimate method forwarder. You capture all of any number of arguments, and any block that was hanging off the end, and send those to another method.
# forwards everything to the method `bar`
def foo(*args, &block)
bar(*args, &block)
end
Lastly, send is just a method. It expects a name of a method, followed by any number of arguments (not an array), and can optionally handle a hanging block.
In other words:
foo.send methodName, *args, &block
The splat in the method definition means "take all unmatched arguments and put them in an array" (in ruby 1.8 this was always the last arguments, but in 1.9 splats can occur in the middle).
Using it in a method call is the reverse: it means take this array and use its contents as the arguments
foo(a,b) #call foo with 2 arguments: a and b
foo([a,b]) #call foo with a single array argument
foo(*[a,b]) # call foo with 2 arguments: a and b
& is similar: in a method definition it captures the block and turns it into a proc, but in a method call it turns a proc (or proc like object - anything responding to to_proc will do) into the block for that method
You need both of these for method_missing because (in general) you want to pass along all the arguments and the block from the original method call.
To my knowledge, anytime you pass a block directly, it is with the syntax &block_name.
Also, the method signature for Object#send takes endless arguments, not an array. So by passing the splatted values *args, it is the same as if you had passed the comma-delimited args.
I know what this means:
def f(*args)
...
end
But what does this mean and why would you want to use it? Can it appear with named parameters, too?
def f(*)
...
end
def f(*) has the same effect as def f(*args), except that it does not name the globbed argument array. You might use it if you want the function to accept any number of arguments but don't actually need to refer to them within the function -- for example, if you are overriding a method but calling super without passing an explicit argument list, which results in the original arguments being passed to super.
You can write def f(a, b, *) as well.