1

I am wondering how I can pass an array and function within another function's arguments. In the code below I am getting a wrong number of arguments (given 0, expected 1).

It takes an array students and callback named callback. is_female is the function I am trying to call.

students = [
    {name: 'John', grade: 8, gender: 'M'},
    {name: 'Sarah', grade: 12, gender: 'F'}
]
def is_female(students)
  students.find_all{ |item| item[:gender] == 'F' }
end
def filter_gender(students, &callback)
  callback(students)
end

p filter_gender(students, is_female)

2 Answers 2

2

I am getting a wrong number of arguments (given 0, expected 1)

In many other languages, is_female() would invoke the method whereas is_female would return a reference to the method. In Ruby, the parentheses are optional and both variants are equivalent – they invoke the method.

Getting a reference to a method in Ruby is a little more involved: you have to call Object#method passing the method's name. The result is an instance of Method – an object wrapper around your method:

m = method(:is_female)
#=> #<Method: main.is_female>

m.paramters
#=> [[:req, :students]]

m.call(students)  # <- equivalent to is_female(students)
#=> [{:name=>"Sarah", :grade=>12, :gender=>"F"}]

This object can be assigned to variables or be passed to other methods as a positional or keyword argument just like any other object.

&callback however is a block argument, so the method object has to be converted to a block via &:

m = method(:is_female)
filter_gender(students, &m)

# or in one line:

filter_gender(students, &method(:is_female))

There are actually two steps: & first invokes Method#to_proc and then passes the result as the block argument. So within filter_gender, the callback variable doesn't refer to the method object, but to its proc. (another level of indirection)

Fortunately, Method and Proc have a similar interface for invocation: there's Proc#call, Proc#[] or prc.(): (syntactic sugar for call)

callback.call(students)

callback[students]

callback.(students)
#       ^
#  note the dot

Applied to your code:

def filter_gender(students, &callback)
  callback.call(students)
end

filter_gender(students, &method(:is_female))
#=> [{:name=>"Sarah", :grade=>12, :gender=>"F"}]

Some suggestions:

I'd move the filtering and retrieval of the student's gender out of the method and into filter_gender:

def is_female(gender)
  gender == 'F'
end

def filter_gender(students, &callback)
  students.select { |item| callback.call(item[:gender]) }
end

filter_gender(students, &method(:is_female))
#=> [{:name=>"Sarah", :grade=>12, :gender=>"F"}]

And instead of invoking callback explicitly, I'd just yield the gender to the block and call it using a regular block: (you can combine the above and below code in either way)

def filter_gender(students)
  students.select { |item| yield item[:gender] }
end

filter_gender(students) { |gender| is_female(gender) }
#=> [{:name=>"Sarah", :grade=>12, :gender=>"F"}]

Instead of a method you could create a Proc explicitly which can be passed as a block argument just via &:

is_female = -> (gender) { gender == 'F' }

filter_gender(students, &is_female)
#=> [{:name=>"Sarah", :grade=>12, :gender=>"F"}]

And last not least, you could create a Student class and implement a female? method:

class Student
  attr_accessor :name, :grade, :gender

  def initialize(name:, grade:, gender:)
    @name   = name
    @grade  = grade
    @gender = gender
  end

  def female?
    gender == 'F'
  end
end

students = [
  Student.new(name: 'John', grade: 8, gender: 'M'),
  Student.new(name: 'Sarah', grade: 12, gender: 'F')
]

students.select(&:female?)
#=> [#<Student:0x00007fa8d186e350 @name="Sarah", @grade=12, @gender="F">]
Sign up to request clarification or add additional context in comments.

1 Comment

OP, you were missing callback.call or callback.() for your code to work.. The &callback will capture the block in the callback variable but you have to call #call or the .() syntax to execute it.
0

In Ruby placing the method name in your code is equivalent to invoking that method (since parentheses are contextually optional), when what you want is a reference for the method. Methods can be referenced by a symbol (the method name prefixed with ':') or string containing the method's name. The method can be invoked by calling send with the method reference as the first argument, followed by any arguments the method requires.

Try the following:

students = [
    {name: 'John', grade: 8, gender: 'M'},
    {name: 'Sarah', grade: 12, gender: 'F'}
]

def is_female(students)
  students.find_all{ |item| item[:gender] == 'F' }
end

def filter_gender(students, callback)
  send(callback, students)
end

p filter_gender(students, :is_female)

I've used this successfully to implement a discrete event simulation Ruby gem, where the gem doesn't need any modification to run your model with arbitrary numbers of events and event names of your choosing.

2 Comments

That also returns wrong number of arguments (given 1, expected 2)
@StasysMeclazcke Did you copy and paste my code, or transcribe it (with possible errors)? What I posted is a copy/paste of code that ran on my computer, producing [{:name=>"Sarah", :grade=>12, :gender=>"F"}].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.