Blok dan hasil di Ruby

275

Saya mencoba memahami blok yielddan cara kerjanya di Ruby.

Bagaimana yielddigunakan? Banyak aplikasi Rails yang saya lihat digunakan yielddengan cara yang aneh.

Bisakah seseorang menjelaskan kepada saya atau menunjukkan kepada saya ke mana harus pergi untuk memahaminya?

Matt Elhotiby
sumber
2
Anda mungkin tertarik pada jawaban untuk fitur hasil Ruby sehubungan dengan ilmu komputer . Meskipun ini adalah pertanyaan yang agak berbeda dari pertanyaan Anda, ini mungkin bisa menjelaskan masalah ini.
Ken Bloom

Jawaban:

393

Ya, pada awalnya agak membingungkan.

Di Ruby, metode dapat menerima blok kode untuk melakukan segmen kode yang sewenang-wenang.

Ketika metode mengharapkan blok, ia memanggilnya dengan memanggil yieldfungsi.

Ini sangat berguna, misalnya, untuk mengulangi daftar atau untuk menyediakan algoritma kustom.

Ambil contoh berikut:

Saya akan mendefinisikan Personkelas yang diinisialisasi dengan nama, dan memberikan do_with_namemetode yang ketika dipanggil, hanya akan meneruskan nameatribut, ke blok yang diterima.

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

    def do_with_name 
        yield( @name ) 
    end
end

Ini akan memungkinkan kami untuk memanggil metode itu dan melewati blok kode arbitrer.

Misalnya, untuk mencetak nama yang akan kita lakukan:

person = Person.new("Oscar")

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

Akan mencetak:

Hey, his name is Oscar

Perhatikan, blok menerima, sebagai parameter, variabel yang disebut name(NB Anda dapat memanggil variabel ini apa pun yang Anda suka, tetapi masuk akal untuk menyebutnya name). Ketika kode memanggil yielditu mengisi parameter ini dengan nilai @name.

yield( @name )

Kami dapat menyediakan blok lain untuk melakukan tindakan yang berbeda. Misalnya, balikkan nama:

#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"

Kami menggunakan metode yang persis sama ( do_with_name) - itu hanya blok yang berbeda.

Contoh ini sepele. Penggunaan yang lebih menarik adalah memfilter semua elemen dalam array:

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

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

=> ["tuesday", "thursday"]

Atau, kami juga dapat menyediakan algoritme pengurutan khusus, misalnya berdasarkan ukuran string:

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

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

Saya harap ini membantu Anda untuk memahaminya dengan lebih baik.

BTW, jika bloknya opsional Anda harus menyebutnya seperti:

yield(value) if block_given?

Jika tidak opsional, aktifkan saja.

EDIT

@hmak membuat repl.it untuk contoh-contoh ini: https://repl.it/@makstaks/blocksandyieldsrubyexample

OscarRyz
sumber
bagaimana cara mencetaknya racsOjika the_name = ""
Paritosh Piplewar
2
Maaf, namanya adalah variabel instan yang diinisialisasi dengan "Oscar" (tidak begitu jelas dalam jawabannya)
OscarRyz
Bagaimana dengan kode seperti ini? person.do_with_name {|string| yield string, something_else }
f.ardelian
7
Jadi dalam istilah Javascripty, ini adalah cara standar untuk meneruskan panggilan balik ke metode tertentu, dan menyebutnya. Terima kasih untuk penjelasannya!
yitznewton
Dalam cara yang lebih umum - blok adalah gula sintaksis "ditingkatkan" untuk pola Strategi. karena penggunaan tipikal adalah menyediakan kode untuk melakukan sesuatu dalam konteks operasi lain. Tetapi perangkat tambahan ruby ​​membuka jalan ke hal-hal keren seperti menulis DSL menggunakan blok untuk menyampaikan konteks sekitar
Roman Bulgakov
25

Di Ruby, metode dapat memeriksa untuk melihat apakah mereka dipanggil sedemikian rupa sehingga blok diberikan selain argumen normal. Biasanya ini dilakukan dengan menggunakan block_given?metode tetapi Anda juga bisa merujuk ke blok sebagai Proc eksplisit dengan awalan sebuah ampersand ( &) sebelum nama argumen terakhir.

Jika suatu metode dipanggil dengan sebuah blok maka metode tersebut dapat yieldmengontrol ke blok (panggil blok) dengan beberapa argumen, jika diperlukan. Pertimbangkan contoh metode ini yang menunjukkan:

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 =)

Atau, menggunakan sintaks argumen blok khusus:

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 =)
maerics
sumber
Baik untuk mengetahui berbagai cara memicu blok.
LPing
22

Sangat mungkin bahwa seseorang akan memberikan jawaban yang benar-benar terperinci di sini, tetapi saya selalu menemukan posting ini dari Robert Sosinski sebagai penjelasan yang bagus tentang seluk-beluk antara blok, procs & lambdas.

Saya harus menambahkan bahwa saya yakin posting yang saya tautkan khusus untuk ruby ​​1.8. Beberapa hal telah berubah di ruby ​​1.9, seperti variabel blok menjadi lokal ke blok. Di 1.8, Anda akan mendapatkan sesuatu seperti berikut:

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

Sedangkan 1,9 akan memberi Anda:

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

Saya tidak memiliki 1,9 pada mesin ini sehingga di atas mungkin ada kesalahan di dalamnya.

theIV
sumber
Deskripsi yang bagus di artikel itu, butuh waktu berbulan-bulan untuk mencari tahu semuanya sendiri =)
maerics
Saya setuju. Saya rasa saya tidak tahu setengah dari hal-hal yang dijelaskan sampai saya membacanya.
theIV
Tautan yang diperbarui sekarang adalah 404. Inilah tautan Wayback Machine .
klenwell
@klenwell, terima kasih atas bantuannya, saya telah memperbarui tautannya lagi.
theIV
13

Saya ingin semacam menambahkan mengapa Anda melakukan hal-hal seperti itu untuk jawaban yang sudah bagus.

Tidak tahu bahasa apa yang Anda gunakan, tetapi dengan asumsi itu adalah bahasa statis, hal semacam ini akan terlihat familier. Ini adalah bagaimana Anda membaca file dalam 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();
    }
  }
}

Mengabaikan hal-hal yang mengalir secara keseluruhan, Idenya adalah ini

  1. Inisialisasi sumber daya yang perlu dibersihkan
  2. gunakan sumber daya
  3. pastikan untuk membersihkannya

Ini adalah bagaimana Anda melakukannya di ruby

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

Sangat berbeda. Hancurkan yang ini

  1. beri tahu kelas File cara menginisialisasi sumber daya
  2. beri tahu kelas file apa yang harus dilakukan dengannya
  3. menertawakan orang-orang java yang masih mengetik ;-)

Di sini, alih-alih menangani langkah satu dan dua, Anda pada dasarnya mendelegasikannya ke kelas lain. Seperti yang Anda lihat, itu secara dramatis menurunkan jumlah kode yang harus Anda tulis, yang membuat hal-hal lebih mudah dibaca, dan mengurangi kemungkinan hal-hal seperti kebocoran memori, atau kunci file tidak dibersihkan.

Sekarang, ini tidak seperti Anda tidak dapat melakukan sesuatu yang serupa di java, pada kenyataannya, orang telah melakukannya selama beberapa dekade sekarang. Ini disebut pola Strategi . Perbedaannya adalah bahwa tanpa blok, untuk sesuatu yang sederhana seperti contoh file, strategi menjadi berlebihan karena jumlah kelas dan metode yang perlu Anda tulis. Dengan blok, ini adalah cara yang sangat sederhana dan elegan untuk melakukannya, sehingga tidak masuk akal untuk tidak menyusun kode seperti itu.

Ini bukan satu-satunya cara blok digunakan, tetapi yang lain (seperti pola Builder, yang dapat Anda lihat di form_for api in rails) cukup mirip sehingga harus jelas apa yang terjadi setelah Anda membungkus kepala Anda di sekitar ini. Ketika Anda melihat blok, biasanya aman untuk mengasumsikan bahwa pemanggilan metode adalah apa yang ingin Anda lakukan, dan blok tersebut menggambarkan bagaimana Anda ingin melakukannya.

Matt Briggs
sumber
5
Mari sederhanakan itu sedikit: File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" enddan tertawa lebih keras pada orang-orang Jawa.
Michael Hampton
1
@MichaelHampton, tertawa setelah Anda membaca file beberapa gigabytes.
akostadinov
@akostadinov Tidak ... itu membuat saya ingin menangis!
Michael Hampton
3
@MichaelHampton Atau, lebih baik lagi: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(ditambah tidak ada masalah memori)
Dana Gugatan Monica
12

Saya menemukan artikel ini sangat berguna. Secara khusus, contoh berikut:

#!/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

yang akan menghasilkan output sebagai berikut:

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

Jadi intinya setiap kali panggilan dibuat ke yieldruby akan menjalankan kode di doblok atau di dalam {}. Jika parameter disediakan untuk yieldini, ini akan diberikan sebagai parameter untuk doblok.

Bagi saya, ini adalah pertama kalinya saya benar-benar mengerti apa yang dilakukan dobalok. Ini pada dasarnya adalah cara bagi fungsi untuk memberikan akses ke struktur data internal, baik untuk iterasi atau untuk konfigurasi fungsi.

Jadi ketika di rel Anda menulis yang berikut:

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

Ini akan menjalankan respond_tofungsi yang menghasilkan doblok dengan formatparameter (internal) . Anda kemudian memanggil .htmlfungsi pada variabel internal ini yang pada gilirannya menghasilkan blok kode untuk menjalankan renderperintah. Perhatikan bahwa .htmlhanya akan menghasilkan jika format file yang diminta. (teknis: fungsi-fungsi ini sebenarnya digunakan block.callbukan yieldseperti yang Anda lihat dari sumber tetapi fungsinya pada dasarnya sama, lihat pertanyaan ini untuk diskusi.) Ini menyediakan cara bagi fungsi untuk melakukan beberapa inisialisasi kemudian mengambil input dari kode panggilan dan kemudian lanjutkan pemrosesan jika diperlukan.

Atau dengan kata lain, ini mirip dengan fungsi yang mengambil fungsi anonim sebagai argumen dan kemudian memanggilnya dalam javascript.

zelanix
sumber
8

Di Ruby, sebuah blok pada dasarnya adalah sepotong kode yang dapat diteruskan ke dan dieksekusi dengan metode apa pun. Blok selalu digunakan dengan metode, yang biasanya memasukkan data ke mereka (sebagai argumen).

Blok banyak digunakan dalam permata Ruby (termasuk Rails) dan dalam kode Ruby yang ditulis dengan baik. Mereka bukan objek, karenanya tidak dapat ditugaskan ke variabel.

Sintaks Dasar

Blok adalah bagian dari kode yang dilampirkan oleh {} atau do..end. Dengan konvensi, sintaks kurung kurawal harus digunakan untuk blok baris tunggal dan sintaks do..end harus digunakan untuk blok multisaluran.

{ # This is a single line block }

do
  # This is a multi-line block
end 

Metode apa pun dapat menerima blok sebagai argumen implisit. Blok dieksekusi oleh pernyataan hasil dalam suatu metode. Sintaks dasarnya adalah:

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.

Ketika pernyataan hasil tercapai, metode meditasi menghasilkan kontrol ke blok, kode di dalam blok dieksekusi dan kontrol dikembalikan ke metode, yang melanjutkan eksekusi segera setelah pernyataan hasil.

Ketika suatu metode berisi pernyataan hasil, itu diharapkan untuk menerima blok pada waktu panggilan. Jika blok tidak disediakan, pengecualian akan dilemparkan setelah pernyataan hasil tercapai. Kami dapat membuat blok opsional dan menghindari pengecualian yang muncul:

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

Output:
Today we will practice zazen. 

Tidak mungkin untuk melewatkan beberapa blok ke suatu metode. Setiap metode hanya dapat menerima satu blok.

Lihat lebih lanjut di: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html


sumber
Ini adalah (hanya) jawaban yang benar-benar membuat saya mengerti apa itu blok dan hasil, dan bagaimana menggunakannya.
Eric Wang
5

Saya kadang menggunakan "hasil" seperti ini:

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

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Samet Sazak
sumber
Ok, tapi kenapa? Ada banyak alasan, seperti yang Loggerharus dilakukan beberapa tugas jika pengguna tidak perlu. Anda harus menjelaskan milik Anda ...
Ulysse BN
4

Hasilkan, sederhananya, biarkan metode yang Anda buat untuk mengambil dan memanggil blok. Kata kunci hasil secara khusus adalah tempat di mana 'barang' di blok akan dilakukan.

ntarpey
sumber
1

Ada dua poin yang ingin saya sampaikan tentang hasil di sini. Pertama, sementara banyak jawaban di sini berbicara tentang berbagai cara untuk melewatkan blok ke metode yang menggunakan hasil, mari kita juga bicara tentang aliran kontrol. Ini sangat relevan karena Anda dapat menghasilkan GANDA kali ke blok. Mari kita lihat sebuah contoh:

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

Ketika setiap metode dipanggil, ia mengeksekusi baris demi baris. Sekarang ketika kita sampai ke blok 3.times, blok ini akan dipanggil 3 kali. Setiap kali ia meminta hasil. Hasil itu terkait dengan blok yang terkait dengan metode yang disebut metode masing-masing. Penting untuk memperhatikan bahwa setiap hasil waktu dipanggil, ia mengembalikan kontrol ke blok masing-masing metode dalam kode klien. Setelah blok selesai dieksekusi, ia kembali ke blok 3.times. Dan ini terjadi 3 kali. Sehingga blok dalam kode klien dipanggil pada 3 kesempatan terpisah karena hasil secara eksplisit disebut 3 waktu terpisah.

Poin kedua saya adalah tentang enum_for dan hasil. enum_for instantiates kelas Enumerator dan objek Enumerator ini juga merespons untuk menghasilkan.

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" 

Jadi perhatikan setiap kali kita memohon jenis dengan iterator eksternal, itu akan memanggil hasil hanya sekali. Lain kali kita menyebutnya, itu akan memanggil hasil berikutnya dan seterusnya.

Ada berita menarik yang menarik sehubungan dengan enum_for. Dokumentasi online menyatakan sebagai berikut:

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

Jika Anda tidak menentukan simbol sebagai argumen untuk enum_for, ruby ​​akan menghubungkan enumerator ke masing-masing metode penerima. Beberapa kelas tidak memiliki metode masing-masing, seperti kelas String.

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

Dengan demikian, dalam kasus beberapa objek yang dipanggil dengan enum_for, Anda harus secara eksplisit mengenai apa metode penghitungan Anda nantinya.

Donato
sumber
0

Hasil dapat digunakan sebagai blok tanpa nama untuk mengembalikan nilai dalam metode. Pertimbangkan kode berikut:

Def Up(anarg)
  yield(anarg)
end

Anda dapat membuat metode "Atas" yang diberikan satu argumen. Anda sekarang dapat menetapkan argumen ini untuk menghasilkan yang akan memanggil dan mengeksekusi blok terkait. Anda dapat menetapkan blok setelah daftar parameter.

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

Ketika metode Naik memanggil hasil, dengan argumen, itu diteruskan ke variabel blok untuk memproses permintaan.

gkstr1
sumber