I am trying to pass an argument to Proc0#call but Sorbet fails:
foo do |data|
# Do stuff
end
sig { params(block: T.proc.void).void }
def foo(&block)
x = { foo: :bar }
block.call(x)
end
When I run Sorbet, I see this error:
Too many arguments provided for method Proc0#call. Expected: 0, got: 1
https://srb.help/7004
How do I avoid this using strict mode?
Now I see it! The signature passes the param as arg0
sig {params(blk: T.proc.params(arg0: Hash).void).void}
See: https://sorbet.org/docs/procs
Related
It seems like for a vast majority of methods that take blocks, if you have a block, proc, or lambda stored in a variable you can pass it in. For example
mapper = -> (foo) { foo * foo }
(1..5).map(&mapper) # => [1, 4, 9, 16, 25]
The method Class::new can be initialized using a block to create a class, as such:
klass = Class.new do
def foo
'bar'
end
end
klass.new.foo # => "bar"
However, I cannot do something like this
class_body = lambda do
def foo
'bar'
end
end
klass = Class.new(&class_body)
I get ArgumentError (wrong number of arguments (given 1, expected 0)). There is a way around this by doing something like this,
klass = Class.new { class_body.call }
klass.new.foo
But it seems like this shouldn't be necessary. Is there a more idiomatic way to instantiate a class with a block as an argument other than something like
def define_class(&class_body)
Class.new { class_body.call }
end
As both the documentation of Class::new and the error message clearly show, the block is passed one argument [bold emphasis mine]:
new(super_class=Object) { |mod| ... } → a_class
[…]
If a block is given, it is passed the class object, and the block is evaluated in the context of this class like class_eval.
Also, in the error message you posted:
ArgumentError (wrong number of arguments (given 1, expected 0))
The error message is saying that your lambda was passed one argument, but it is expecting none.
The fix is easy: add a parameter to your lambda:
class_body = -> * do
def foo
'bar'
end
end
klass = Class.new(&class_body)
Or, if you don't absolutely require a lambda, you could use a non-lambda Proc:
class_body = proc do
def foo
'bar'
end
end
klass = Class.new(&class_body)
I'm having trouble figuring out how to stub only one of two calls to a method. Here's an example:
class Example
def self.foo
{ a: YAML.load_file('a.txt'), # don't stub - let it load
b: YAML.load_file('b.txt') } # stub this one
end
end
RSpec.describe Example do
describe '.foo' do
before do
allow(YAML).to receive(:load_file).with('b.txt').and_return('b_data')
end
it 'returns correct hash' do
expect(described_class.foo).to eq(a: 'a_data', b: 'b_data')
end
end
end
The test fails because I have stubbed a call to YAML.load_file with the args for the second call ('b.txt'), not the first one it encounters('a.txt'). I thought argument matching would address this but it does not.
Failures:
1) Example.foo returns correct hash
Failure/Error:
{ a: YAML.load_file('a.txt'),
b: YAML.load_file('b.txt') }
Psych received :load_file with unexpected arguments
expected: ("b.txt")
got: ("a.txt")
Please stub a default value first if message might be received with other args as well.
Is there a way I can allow the first call to YAML.load_file to go through but only stub the second call? How would I do this?
There is a and_call_original option (see rspec docs).
Applied to your example, this should do what you are looking for:
before do
allow(YAML).to receive(:load_file).and_call_original
allow(YAML).to receive(:load_file).with('b.txt').and_return('b_data')
end
I would like to add a method to a class C
class C
end
I defined a proc:
impl = proc{|x,y| puts "x=#{x} - y=#{y}"}
I added the proc to the class as a method foo:
C.send(:define_method, :foo, lambda do |args = impl.parameters.map { |arg| arg[1] }|
puts "foo is called"
impl.call(args)
end
)
When I call foo as C.new.foo(1,2), I get an error:
ArgumentError: wrong number of arguments (2 for 0..1)
To avoid this, I need to call foo like C.new.foo([1,2]). Someone can tell me how to avoid this problem?
Answering the question stated:
C.send(:define_method, :foo, lambda do |*args|
args = impl.parameters.map(&:last) if args.empty?
puts "foo is called, args are: #{args.inspect}"
impl.call(*args)
end)
C.new.foo(1,2)
#⇒ foo is called, args are: [1, 2]
# x=1 - y=2
Since you want to pass an arbitrary amount of parameters to the method, the lambda should receive splat argument.
Also, defaulting to impl.parameters.map(&:last) makes not much sense, since the defaults will be symbols [:x, :y].
I have an instance method thats accepting 4 inputs
class Foo
def baz(a, b, c, d)
puts 'something'
end
end
I am writing spec for the instance method and i have
let(:resp) {
Foo.new.baz (
a: '97334',
b: '38',
c: '0001110000',
d: 'N')
}
I tried changing it to the one below but no luck
let(:resp) {
Foo.new.baz '97334', '38', '0001110000','N')
}
why am i getting wrong number of arguments at instance method in Foo class?
Thanks
Try:
let(:resp) {
Foo.new.baz(
'97334',
'38',
'0001110000',
'N'
)
}
You are passing keyword arguments which could be thought of as passing one Hash. So your code is basically equivalent to:
params = Hash.new
params[:a] = '97334'
params[:b] = '38'
params[:c] = '0001110000'
params[:d] = 'N'
let(:resp){
Foo.new.baz(params)
}
This example above obviously only has 1 argument.
Another way to solve this would be parameterizing the function call:
class Foo
def baz(a:, b:, c:, d:)
puts 'something'
end
end
In general this is preferred when there are many args to a function, because it does not require the function user to know the specific order of the function arguments, only which arguments are needed.
In ruby <= 1.9, there are no named parameters, but you can achieve the same effect with a hash. The reason you get an error saying you only have one argument is because ruby assumes you are passing a single hash rather than 4 parameters. You can make it work like so:
class Foo
def baz(myhash)
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
In ruby 2.0, named parameters exist, but they can only be optional:
class Foo
def baz(a: "hi", b: "mum")
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
Foo.new.baz() #=> "hi, mum"
In ruby >= 2.1, named params can be optional OR required. you can make named params required by specifying them without default values like so:
class Foo
def baz(a:, b:)
puts "#{myhash[:a]}, #{myhash[:b]}"
end
end
Foo.new.baz(a: "hello", b: "world) #=> "hello, world"
In all of these rubies, your last code snippet should work if you remove the extra parenthesis:
class Foo
def baz(a, b, c, d)
puts 'something'
end
end
Foo.new.baz '97334', '38', '0001110000','N'
I want to use overloading feature in Ruby like many other languages, but Ruby itself does not support this feature.
Do I have to implement it using the way that define a method with *args argument and determine the number and types of the arguments inside the method? Some like:
class A
def foo(*args)
case (args.length)
when 1
do something
when 2
do something-else
....
end
end
end
You can see, it is really ugly than directly overloading.
I want to know whether there is any keywords or some other manners (like a meta-programming module) that could allow me to define an overloading method in a more elegant way.
You could try some meta programming to reach your target.
See the following code:
class OverloadError < ArgumentError; end
class Class
=begin rdoc
=end
def define_overload_method( methodname, *methods )
methods.each{ | proc |
define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
}
define_method(methodname){|*x|
if respond_to?("#{methodname}_#{x.size}")
send "#{methodname}_#{x.size}", *x
else
raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
end
}
end
end
class X
define_overload_method :ometh,
Proc.new{ "Called me with no parameter" },
Proc.new{ |p1| "Called me with one parameter (#{p1.inspect})" },
Proc.new{ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end
x = X.new
p '----------'
p x.ometh()
p x.ometh(1)
p x.ometh(1,2)
p x.ometh(1,2,3) #OverloadError
You can define your overloaded method with define_overload_method. Parameters are the method name and a list of procedures. The method methodname is created and calls the corresponding method. Which method is determined by the number of parameters (Not type!).
An alternative syntax would be:
class OverloadError < ArgumentError; end
class Class
def def_overload( methodname)
define_method(methodname){|*x|
if respond_to?("#{methodname}_#{x.size}")
send "#{methodname}_#{x.size}", *x
else
raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
end
}
end
def overload_method( methodname, proc )
define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
end
end
class X
def_overload :ometh
overload_method :ometh, Proc.new{ "Called me with no parameter" }
overload_method :ometh, Proc.new{ |p1| "Called me with one parameter (#{p1.inspect})" }
overload_method :ometh, Proc.new{ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end
def_overload defines the frame for your overloaded methods, overload_method defines one 'overload-method'.
But as already mentioned by Holger:
You should try to adapt to the Ruby way. There is a reason why there is no overloading in Ruby. Methods should only do one thing, not magically decide to do vastly different things just because of different arguments. Instead try to take advantage of Duck Typing and if in doubt, use different methods with meaningful names.
I was curious how I could implement a version with type sensitive overloading. Here it is:
class OverloadError < ArgumentError; end
class Class
def def_overload( methodname)
define_method(methodname){|*x|
methname = "xxx"
methname = "#{methodname}_#{x.size}#{x.map{|p| p.class.to_s}.join('_')}"
if respond_to?(methname)
send methname, *x
elsif respond_to?("#{methodname}_#{x.size}")
send "#{methodname}_#{x.size}", *x
else
raise OverloadError, "#{methodname} not defined for #{x.size} parameters"
end
}
end
def overload_method( methodname, *args, &proc )
types = []
args.each{|arg| types << arg.to_s}
define_method("#{methodname}_#{proc.arity}#{types.join('_')}".to_sym, &proc )
end
end
class X
def_overload :ometh
overload_method(:ometh){ "Called me with no parameter" }
overload_method(:ometh, String ){ |p1| "Called me with one string parameter (#{p1.inspect})" }
overload_method(:ometh ){ |p1| "Called me with one parameter (#{p1.inspect})" }
overload_method(:ometh){ |p1,p2| "Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end
When you call it with
p x.ometh(1)
p x.ometh('a')
You get
"Called me with one parameter (1)"
"Called me with one string parameter (\"a\")"
You can test for the existence of each argument separately as they are set to nil if not passed (assuming they are passed in order!).
If you insist on very different arguments I suggest an hash argument with symbols for each argument you intend.. and approriate tests.
** UPDATE **
Also you could also rename the methods that overload with more specific names, such as
def perform_task_with_qualifier_1
there are few gems that provide this feature to your ruby code
functional-ruby
defn(:greet, :male) {
puts "Hello, sir!"
}
defn(:greet, :female) {
puts "Hello, ma'am!"
}
foo.greet(:male) => "Hello, sir!"
foo.greet(:female) => "Hello, ma'am!"
you can find more Elixir like pattern matching features from here
contracts.ruby
Contract 1 => 1
def fact x
x
end
Contract C::Num => C::Num
def fact x
x * fact(x - 1)
end
this gem helps to right beautiful defensive code. there are some criticisms about performance. so benchmark and decide. more examples
The defining characteristic of overloading is that dispatch happens statically. In Ruby, dispatch always happens dynamically, there is no other way. Therefore, overloading is not possible in Ruby.