Pergunta

Estou tentando entender blocos e yield e como eles funcionam em Ruby.

Como é yield usado?Muitas das aplicações Rails, eu olhei para o uso yield de uma forma estranha.

Alguém pode me explicar ou me mostrar onde ir para compreendê-los?

Foi útil?

Solução

Sim, é um pouco intrigante no começo.

No Ruby, os métodos podem receber um bloco de código para realizar segmentos arbitrários de código.

Quando um método espera um bloco, ele chama -o chamando o yield função.

Isso é muito útil, por exemplo, para iterar em uma lista ou fornecer um algoritmo personalizado.

Veja o seguinte exemplo:

Eu vou definir um Person classe inicializada com um nome e forneça um do_with_name método que, quando invocado, apenas passaria o name atributo, ao bloco recebido.

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

Isso nos permitiria chamar esse método e passar um bloco de código arbitrário.

Por exemplo, para imprimir o nome que faríamos:

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

Imprimiria:

Hey, his name is Oscar

Observe, o bloco recebe, como um parâmetro, uma variável chamada name (NB você pode chamar essa variável de tudo o que quiser, mas faz sentido chamá -lo name). Quando o código invoca yield preenche este parâmetro com o valor de @name.

yield( @name )

Poderíamos fornecer outro bloco para executar uma ação diferente. Por exemplo, reverta o nome:

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

Usamos exatamente o mesmo método (do_with_name) - É apenas um bloco diferente.

Este exemplo é trivial. Usos mais interessantes são filtrar todos os elementos em uma matriz:

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

Ou também podemos fornecer um algoritmo de classificação personalizado, por exemplo, com base no tamanho da string:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

Espero que isso ajude você a entender melhor.

BTW, se o bloco for opcional, você deve chamá -lo como:

yield(value) if block_given?

Se não for opcional, basta invocar.

Outras dicas

No Ruby, os métodos podem verificar se foram chamados de tal maneira que um bloco foi fornecido além dos argumentos normais. Normalmente isso é feito usando o block_given? Método, mas você também pode se referir ao bloco como um PROC explícito prefixando um ampersand (&) antes do nome do argumento final.

Se um método for invocado com um bloco, o método pode yield Controle para o bloco (ligue para o bloco) com alguns argumentos, se necessário. Considere este método de exemplo que demonstra:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

Ou, usando a sintaxe de argumento de bloco especial:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

É bem possível que alguém vai oferecer uma verdadeira resposta detalhada aqui, mas eu sempre achei este post a partir de Robert Sosinski para ser uma grande explicação das sutilezas entre os blocos, procs & lambdas.

Devo acrescentar que eu acredito que o post eu vou ligar é específica para o ruby 1.8.Algumas coisas mudaram no ruby 1.9, tal como o bloco de variáveis de ser local para o bloco.No 1.8, que você deseja obter algo como o seguinte:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

Enquanto 1.9 dar-lhe-ia:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

Eu não tenho 1.9 sobre esta máquina, então o acima pode ter um erro.

Eu queria acrescentar por que você faria as coisas dessa maneira às já ótimas respostas.

Não faço ideia de que idioma você vem, mas assumindo que é uma linguagem estática, esse tipo de coisa parecerá familiar. É assim que você lê um arquivo em java

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

Ignorando toda a coisa do fluxo, a ideia é esta

  1. Inicialize o recurso que precisa ser limpo
  2. use recurso
  3. Certifique -se de limpar

É assim que você faz isso em rubi

File.open("readfile.rb", "r") do |infile|
    while (line = infile.gets)
        puts "#{counter}: #{line}"
        counter = counter + 1
    end
end

Muito diferente. Quebrando este

  1. informe à classe de arquivo como inicializar o recurso
  2. Diga à classe de arquivo o que fazer com isso
  3. ria dos caras Java que ainda estão digitando ;-)

Aqui, em vez de lidar com a etapa um e dois, você basicamente delega isso em outra classe. Como você pode ver, isso reduz drasticamente a quantidade de código que você deve escrever, o que facilita a leitura das coisas e reduz as chances de coisas como vazamentos de memória ou bloqueios de arquivo não serem liberados.

Agora, não é como se você não pudesse fazer algo semelhante em Java, de fato, as pessoas fazem isso há décadas agora. É chamado de Estratégia padronizar. A diferença é que, sem blocos, para algo simples, como o exemplo do arquivo, a estratégia se torna exagerada devido à quantidade de classes e métodos que você precisa escrever. Com os blocos, é uma maneira tão simples e elegante de fazê -lo, que não faz sentido não estruturar seu código dessa maneira.

Esta não é a única maneira de os blocos são usados, mas os outros (como o padrão do construtor, que você pode ver na API form_for em trilhos) são semelhantes o suficiente para que seja óbvio o que está acontecendo depois de envolver sua cabeça. Quando você vê blocos, geralmente é seguro assumir que a chamada do método é o que você deseja fazer, e o bloco está descrevendo como você deseja fazê -lo.

eu encontrei Este artigo para ser muito útil. Em particular, o exemplo a seguir:

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

que deve fornecer a seguinte saída:

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

Então, essencialmente, cada vez que uma chamada é feita para yield Ruby executará o código no do bloco ou dentro {}. Se um parâmetro for fornecido para yield então isso será fornecido como um parâmetro para o do quadra.

Para mim, foi a primeira vez que entendi realmente o que o do Blocos estavam fazendo. É basicamente uma maneira de a função dar acesso a estruturas de dados internas, seja para iteração ou para configuração da função.

Então, quando nos trilhos você escreve o seguinte:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

Isso vai executar o respond_to função que produz o do Bloco com o (interno) format parâmetro. Você então chama o .html função nessa variável interna que, por sua vez, produz o bloco de código para executar o render comando. Observe que .html só renderá se for o formato de arquivo solicitado. (Technicity: essas funções realmente usam block.call não yield Como você pode ver do fonte Mas a funcionalidade é essencialmente a mesma, veja essa questão Para uma discussão.) Isso fornece uma maneira de a função executar alguma inicialização e, em seguida, obtenha a entrada do código de chamada e, em seguida, continue o processamento, se necessário.

Ou, em outra maneira, é semelhante a uma função que tem uma função anônima como um argumento e depois a chamando no JavaScript.

Em Ruby, um bloco é basicamente um pedaço de código que pode ser transmitido e executado por qualquer método.Os blocos são sempre usados com métodos, que, geralmente, dados de feed (como argumentos).

Os blocos são amplamente utilizados em Ruby gems (incluindo Trilhos) e no bem-escrito do código de Ruby.Eles não são objetos, portanto, não podem ser atribuídas a variáveis.

Sintaxe Básica

Um bloco é um pedaço de código delimitados por { } ou fazer..end.Por convenção, a chaveta sintaxe deve ser usada para uma linha de blocos e a fazer..o final sintaxe deve ser usada para multi-linha de blocos.

{ # This is a single line block }

do
  # This is a multi-line block
end 

Qualquer método pode receber um bloco como um argumento implícito.Um bloco é executado pelo rendimento de instrução dentro de um método.A sintaxe básica é:

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

Quando o rendimento instrução é atingido, a meditar rendimento método de controle para o bloco de código dentro do bloco é executado e o controle é devolvido para o método, que retoma a execução imediatamente após a declaração de rendimento.

Quando um método contém um rendimento de instrução, ele está esperando receber um bloco de chamada tempo.Se um bloco não for fornecido, será lançada uma exceção uma vez que a declaração de rendimento é atingido.Nós podemos fazer o bloco opcional e evitar uma exceção sendo gerado:

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

Não é possível passar vários blocos para um método.Cada método pode receber apenas um bloco.

Veja mais em: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html

Às vezes uso "rendimento" assim:

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

Rendimentos, para simplificar, permita o método que você cria para obter e ligar para os blocos. A palavra -chave de rendimento especificamente é o local em que o 'material' no bloco será executado.

Há dois pontos que quero fazer sobre o rendimento aqui. Primeiro, enquanto muitas respostas aqui falam sobre diferentes maneiras de passar um bloco para um método que usa rendimento, vamos também falar sobre o fluxo de controle. Isso é especialmente relevante, pois você pode render várias vezes a um bloco. Vamos dar uma olhada em um exemplo:

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block

Quando cada método é chamado, ele executa linha por linha. Agora, quando chegarmos ao bloco 3.Times, esse bloco será invocado 3 vezes. Cada vez que invoca o rendimento. Esse rendimento está vinculado ao bloco associado ao método que chamou de cada método. É importante observar que cada rendimento de tempo é chamado, ele retorna o controle de volta ao bloco de cada método no código do cliente. Quando o bloco terminar de executar, ele retorna ao bloco 3.Times. E isso acontece 3 vezes. Portanto, esse bloco no código do cliente é chamado em três ocasiões separadas, uma vez que o rendimento é explicitamente chamado de 3 vezes separado.

Meu segundo ponto é sobre enum_for e rendimento. enum_ para instancia a classe Enumerator e esse objeto enumerador também responde ao rendimento.

class Fruit
  def initialize
    @kinds = %w(orange apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "apple" 

Portanto, observe toda vez que invocamos tipos com o iterador externo, ele invocará o rendimento apenas uma vez. Na próxima vez que chamarmos, ele invocará o próximo rendimento e assim por diante.

Há um boato interessante em relação a enum_for. A documentação on -line declara o seguinte:

enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

Se você não especificar um símbolo como um argumento para enum_for, Ruby conectará o enumerador ao método do receptor. Algumas classes não têm um método, como a classe String.

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

Assim, no caso de alguns objetos invocados com enum_for, você deve ser explícito sobre qual será o seu método de enumeração.

Colheita pode ser usado como bloco sem nome para retornar um valor no método. Considere o seguinte código:

Def Up(anarg)
  yield(anarg)
end

Você pode criar um método "UP" que é atribuído um argumento. Agora você pode atribuir esse argumento para render que ligará e executará um bloco associado. Você pode atribuir o bloco após a lista de parâmetros.

Up("Here is a string"){|x| x.reverse!; puts(x)}

Quando o método UP chama o rendimento, com um argumento, ele é passado para a variável de bloco para processar a solicitação.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top