Pergunta

Esta questão não é sobre como usar Enumerators em Ruby 1.9.1 mas estou curioso como eles funcionam. Aqui está um código:

class Bunk
  def initialize
    @h = [*1..100]
  end

  def each
    if !block_given?
      enum_for(:each)
    else
      0.upto(@h.length) { |i|
        yield @h[i]
      }
    end
  end
end

No código acima eu posso usar e = Bunk.new.each, e depois e.next, e.next para obter cada elemento sucessivo, mas como exatamente é isso suspensão de execução e, em seguida, retomar no ponto certo?

Estou ciente de que, se o rendimento na 0.upto é substituído por Fiber.yield então é fácil de entender, mas que não é o caso aqui. É um yield velho liso, então como é que funciona?

Eu olhei para enumerator.c mas é neigh sobre incompreensível para mim. Talvez alguém poderia fornecer uma implementação em Ruby, usando fibras, não 1.8.6 estilo recenseadores baseado em continuação, que torna tudo claro?

Foi útil?

Solução

Aqui está um recenseador rubi claro que utiliza fibras e deve praticamente se comportar como o original:

class MyEnumerator
  include Enumerable

  def initialize(obj, iterator_method)
    @f = Fiber.new do
      obj.send(iterator_method) do |*args|
        Fiber.yield(*args)
      end
      raise StopIteration
    end
  end

  def next
    @f.resume
  end

  def each
    loop do
      yield self.next
    end
  rescue StopIteration
    self
  end
end

E antes que alguém se queixa de exceções como controle de fluxo:. O verdadeiro Enumerator levanta StopIteration no final, também, então eu apenas emulado o comportamento original

Uso:

>> enum = MyEnumerator.new([1,2,3,4], :each_with_index)
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc>
>> enum.next
=> [1, 0]
>> enum.next
=> [2, 1]
>> enum.to_a
=> [[3, 2], [4, 3]]

Outras dicas

Na verdade, em seu e = Bunk.new.each a cláusula else não é executado inicialmente. Em vez dos if! Block_given 'executa cláusula e retorna um objeto enumerador. O objeto enumerador faz manter um objeto de fibra internamente. (Pelo menos é o que parece no enumerator.c)

Quando você chama e.each ele está chamando um método em um recenseador que utiliza uma fibra internamente para manter o controle de seu contexto de execução. Este método chama o método Bunk.each usando o contexto de execução fibras. A chamada Bunk.each aqui faz EXECUT a cláusula else e os rendimentos até o valor.

Eu não sei como rendimento é implementado ou como uma fibra rastreia o contexto de execução. Eu não olhei para esse código. Quase toda a magia recenseador e fibra é implementado em C.

Você está realmente perguntando como fibras e rendimento são implementadas? O nível de detalhe que você está procurando?

Se estou fora da base por favor me corrigir.

Como os outros cartazes observou, eu acredito que ele cria seu próprio "fibra" privado [em 1,9]. Em 1.8.7 (ou 1.8.6 se você usar a jóia backports) de alguma forma ou de outra ele faz a mesma coisa (talvez porque os tópicos em 1.8 são o equivalente de fibras, ele só usa-los?)

Assim, em 1,9 e 1.8.x, se você encadear vários deles juntos a.each_line.map.each_with_index {}

Na verdade, flui através de toda essa cadeia com cada linha, como uma espécie de tubo na linha de comando

http://pragdave.blogs.pragprog.com /pragdave/2007/12/pipelines-using.html

HTH.

Eu acho que isso seria mais preciso. Chamando cada um na recenseador deve ser o mesmo que chamar o método iterator originais. Assim, gostaria de alterar um pouco a solução original para isso:

class MyEnumerator
  include Enumerable

   def initialize(obj, iterator_method)
    @f = Fiber.new do
      @result = obj.send(iterator_method) do |*args|
       Fiber.yield(*args)
      end
      raise StopIteration
    end
   end

   def next(result)
     @f.resume result
   end

   def each
     result = nil
     loop do
      result = yield(self.next(result))
     end
     @result
   end
end
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top