как передать итератор Ruby в качестве параметра? - PullRequest
1 голос
/ 21 октября 2010

Я хотел бы написать метод, который возвращает значения в одном месте и передать его в качестве параметра другому методу, который будет вызывать его с блоком. Я убежден, что это можно сделать, но почему-то я не могу найти правильный синтаксис.

Вот пример (нерабочего) кода для иллюстрации того, чего я пытаюсь достичь:

def yielder
  yield 1
  yield 2
  yield 3
end

def user(block)
  block.call { |x| puts x }
end

# later...
user(&yielder)

$ ruby x.rb
x.rb:2:in `yielder': no block given (yield) (LocalJumpError)
from x.rb:12:in `<main>'

FWIW, в моем реальном коде yielder и user находятся в разных классах.


Обновление

Спасибо за ваши ответы. Как упоминал Эндрю Гримм, я хочу, чтобы метод итератора принимал параметры. Мой оригинальный пример не учитывал эту деталь. Этот фрагмент предоставляет итератор, который считает до определенного числа. Чтобы это работало, я сделал внутренний блок явным. Это делает то, что я хочу, но это немного некрасиво. Если кто-то может улучшить это, мне было бы интересно узнать, как это сделать.

def make_iter(upto)
  def iter(upto, block)
    (1 .. upto).each do |v|
      block.call(v)
    end
  end
  lambda { |block| iter(upto, block) }
end

def user(obj)
  obj.call Proc.new { |x| puts x }
end

# later...
user(make_iter(3))

Ответы [ 4 ]

4 голосов
/ 21 октября 2010

Это не использует лямбда или несвязанный метод, но это самый простой способ ...

def f
  yield 1
  yield 2
end

def g x
  send x do |n|
    p n
  end
end

g :f
2 голосов
/ 21 октября 2010

Когда вы пишете &yielder, вы звоните yielder и затем пытаетесь применить оператор & (convert-to-Proc) к результату. Конечно, звонить yielder без блока нельзя. Вам нужно получить ссылку на сам метод. Просто измените эту строку на user(method :yielder), и она будет работать.

1 голос
/ 21 октября 2010

Я думаю, это может соответствовать тому, что вы хотите сделать:

def yielder
  yield 1
  yield 2
  yield 3
end

def user(meth)
  meth.call { |x| puts x }
end

# later...
user( Object.method(:yielder) )

Некоторая связанная информация здесь: http://blog.sidu.in/2007/11/ruby-blocks-gotchas.html

0 голосов
/ 27 сентября 2018

Как уже указывалось, основная проблема заключается в том, что при попытке передать функцию в качестве параметра Ruby выполняет ее - в качестве побочного эффекта от скобок, являющегося необязательным.

Мне понравилась простота метода символов, о котором упоминалось ранее, но я боялся бы, что в будущем я сам забуду, что для передачи этой работы нужно передать итератор в качестве символа. Когда читаемость является желаемой функцией, вы можете заключить свой итератор в объект, который вы можете передавать, не опасаясь неожиданного выполнения кода.

Анонимный объект как итератор

То есть: использование анонимного объекта с одной функцией в качестве итератора. Довольно быстро, чтобы читать и понимать. Но из-за ограничений в способе работы Ruby с областью действия итератор не может легко получить параметры: любые параметры, полученные в функции iterator, не будут автоматически доступны в each.

def iterator
    def each
        yield("Value 1")
        yield("Value 2")
        yield("Value 3")
    end
end

def iterate(my_iterator)
    my_iterator.each do |value|
        puts value
    end
end

iterate iterator

Proc объект как итератор

Использование объекта Proc в качестве итератора позволяет легко использовать любые переменные, переданные в конструктор итератор . Темная сторона: это начинает выглядеть странно. Чтение блока Proc.new не является незамеченным для неопытного глаза. Кроме того: неспособность использовать yield делает это немного уродливым ИМХО.

def iterator(prefix:)
    Proc.new { |&block|
        block.call("#{prefix} Value 1")
        block.call("#{prefix} Value 2")
        block.call("#{prefix} Value 3")
    }
end

def iterate(my_iterator)
    my_iterator.call do |value|
        puts value
    end
end

iterate iterator(prefix: 'The')

Лямбда в качестве итератора

Идеально, если вы хотите запутать свой код так сильно, чтобы никто, кроме вас, не мог его прочитать.

def iterator(prefix:)
    -> (&block) {
        block.call("#{prefix} Value 1")
        block.call("#{prefix} Value 2")
        block.call("#{prefix} Value 3")
    }
end

def iterate(my_iterator)
    my_iterator.call do |value|
        puts value
    end
end

iterate iterator(prefix: 'The')

Класс как итератор

И, наконец, хороший старый подход. Немного многословно для инициализации на мой вкус, но с небольшим или вообще без неожиданного эффекта.

class Iterator
    def initialize(prefix:)
        @prefix = prefix
    end

    def each
        yield("#{@prefix} Value 1")
        yield("#{@prefix} Value 2")
        yield("#{@prefix} Value 3")
    end
end

def iterate(my_iterator)
    my_iterator.each do |value|
        puts value
    end
end

iterate Iterator.new(prefix: 'The')
...