Передача методов, которые принимают блоки - PullRequest
1 голос
/ 26 марта 2020

Как правильно передать метод, который принимает блок?

Т.е. трактовать метод как переменную, чтобы его можно было использовать следующим образом:

(@glob&.each || crawl_dir(@base_dir)) do |file|
        puts "#{file}"
      end

A простой пример, который можно опробовать:

> require 'csv'
=> true

> CSV {|v|v}
=> <#CSV io_type:$stdout encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">

> a=CSV
=> CSV

> a==CSV
=> true

> a {|v|v}
Traceback (most recent call last):
        1: from (irb):14
NoMethodError (undefined method `a' for main:Object)

Возможно ли это?

Ответы [ 3 ]

2 голосов
/ 26 марта 2020

В вашем примере вы используете разные вещи, которые все называются CSV.

CSV {|v|v}

Здесь вы вызываете метод с именем CSV с блоком. Вы получаете возвращаемое значение этого метода обратно. Такие методы обычно используются в качестве конвертеров. Например, есть метод Integer(), который принимает аргумент (например, String), который преобразуется в объект Integer. Обычно эти методы вызываются так же, как и класс объекта, который они возвращают.

a=CSV

Здесь вы присваиваете значение константы CSV (которая является классом CSV) для a переменная. В своем коде вы можете использовать константу CSV или переменную a для ссылки на объект класса.

В этих случаях, когда у вас одно и то же имя, ссылаются на разные вещи (класс и метод соответственно), Ruby может различать guish, какой из них вызывать / возвращать в зависимости от того, как вы его используете. Только вы явно и однозначно вызываете «вещь» (то есть, передавая блок или любые другие аргументы), Ruby вызовет метод.

Во всех других случаях, если вы ссылаетесь на вещь с имя, начинающееся с заглавной буквы, Ruby ожидает его от константы и возвращает его указанный объект (который в данном случае является объектом класса CSV).

a {|v|v}

Здесь вы получаете ошибку поскольку Ruby пытается вызвать метод с именем a (который не существует). Даже если это сработает, переменная a на данный момент является ссылкой на класс CSV (который не может быть вызван напрямую).

(Примечание: для вызова метода, имя которого у вас есть сохраненный в переменной, вы можете использовать метод send, например, my_receiver.send(a).)

Теперь, чтобы решить вашу начальную проблему, вы можете создать объект метода и использовать его для вызова нужного метода, например

method_proc = @glob&.method(:each) || method(:crawl_dir).curry(@base_dir)
method_proc.call do |file|
  puts file
end

Однако, это не очень идиоматизм c Ruby. Здесь ссылки на методы редко переносятся (а не в Javascript или Python, где вы регулярно делаете это с переданными функциональными объектами). Более идиоматическая c реализация в Ruby может быть:

def handle_file(file)
  puts file
end

if @glob.respond_to(:each)
  @glob.each { |file| handle_file(file) }
else
  crawl_dir(@base_dir) { |file| handle_file(file) }
end

Еще более идиоматическая c будет, если ваш метод crawl_dir будет (необязательно) возвращать объект Enumerator, в котором Если вы могли бы упростить вызывающий код.

Здесь я предполагаю, что @glob является либо nil, либо Enumerable объектом (таким как Array или Enumerator, который, таким образом, отвечает непосредственно на each). Это позволяет нам еще больше упростить код.

def crawl_dir(directory)
  # Return an Enumerator object for the current method invocation if no
  # block was passed, similar to how the standard `Enumerable#each` method
  # works.
  return enum_for(__method__) unless block_given?

  # the rest of the method here...
end

enumerable = @glob || crawl_dir(@base_dir)
enumerable.each do |file|
  puts file
end

Здесь происходит то, что вы берете @glob (который мы предполагаем как Enumerable объект, если он существует, и, таким образом, ответ делает * 1054). *) или crawl_dir(@base_dir) без блока. Из вашего crawl_dir метода вы получите обратно Enumerator объект, который снова будет Enumerable. Затем вы можете l oop над этим объектом с помощью each. Таким образом, оба ваших объекта имеют одинаковый тип возвращаемого значения, поэтому могут использоваться одинаково.

1 голос
/ 26 марта 2020

Если вы хотите получить ссылку на метод, вы можете использовать метод Object#method. Затем вы можете вызвать этот метод, используя метод Method#call или .() syntacti c sugar:

require 'csv'

a = method(:CSV)

a.() {|v| v }
#=> #<CSV io_type:$stdout encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
0 голосов
/ 26 марта 2020

Вы всегда можете использовать tap для любого объекта:

a.tap do |v|
  p v
end
...