Untuk Fibers kami punya contoh klasik: menghasilkan angka Fibonacci
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Mengapa kita membutuhkan Serat di sini? Saya dapat menulis ulang ini hanya dengan Proc yang sama (sebenarnya penutupan)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
Begitu
10.times { puts fib.resume }
dan
prc = clsr
10.times { puts prc.call }
akan mengembalikan hasil yang sama.
Lantas apa saja keunggulan serat. Hal apa yang dapat saya tulis dengan Serat yang tidak dapat saya lakukan dengan lambda dan fitur Ruby keren lainnya?
Jawaban:
Serat adalah sesuatu yang mungkin tidak akan pernah Anda gunakan secara langsung dalam kode level aplikasi. Mereka adalah primitif kontrol aliran yang dapat Anda gunakan untuk membuat abstraksi lain, yang kemudian Anda gunakan dalam kode tingkat yang lebih tinggi.
Mungkin penggunaan serat # 1 di Ruby adalah untuk mengimplementasikannya
Enumerator
, yang merupakan kelas inti Ruby di Ruby 1.9. Ini sangat berguna.Di Ruby 1.9, jika Anda memanggil hampir semua metode iterator pada kelas inti, tanpa melewatkan satu blok, itu akan mengembalikan
Enumerator
.Ini
Enumerator
adalah objek Enumerable, daneach
metode mereka menghasilkan elemen yang akan dihasilkan oleh metode iterator asli, jika dipanggil dengan sebuah blok. Dalam contoh yang baru saja saya berikan, Pencacah yang dikembalikan olehreverse_each
memilikieach
metode yang menghasilkan 3,2,1. Pencacah dikembalikan denganchars
hasil "c", "b", "a" (dan seterusnya). TAPI, tidak seperti metode iterator asli, Enumerator juga dapat mengembalikan elemen satu per satu jika Anda memanggilnyanext
berulang kali:Anda mungkin pernah mendengar tentang "iterator internal" dan "iterator eksternal" (penjelasan yang baik tentang keduanya diberikan dalam buku Pola Desain "Gang of Four"). Contoh di atas menunjukkan bahwa Enumerator dapat digunakan untuk mengubah iterator internal menjadi yang eksternal.
Ini adalah salah satu cara untuk membuat enumerator Anda sendiri:
Ayo coba:
Tunggu sebentar ... apakah ada yang aneh di sana? Anda menulis
yield
pernyataan dalaman_iterator
kode garis lurus, tetapi Pencacah dapat menjalankannya satu per satu . Di antara panggilan kenext
, eksekusian_iterator
"dibekukan". Setiap kali Anda meneleponnext
, panggilan terus berjalan hinggayield
pernyataan berikut , lalu "berhenti" lagi.Dapatkah Anda menebak bagaimana ini diterapkan? Pencacah membungkus panggilan ke
an_iterator
dalam serat, dan melewati blok yang menangguhkan serat . Jadi setiap kalian_iterator
menghasilkan ke blok, serat yang dijalankannya ditangguhkan, dan eksekusi berlanjut pada utas utama. Lain kali Anda memanggilnext
, itu melewati kontrol ke serat, blok kembali , danan_iterator
berlanjut di tempat yang ditinggalkannya.Akan menjadi pelajaran untuk memikirkan apa yang diperlukan untuk melakukan ini tanpa serat. SETIAP kelas yang ingin menyediakan iterator internal dan eksternal harus berisi kode eksplisit untuk melacak status antara panggilan ke
next
. Setiap panggilan ke next harus memeriksa status itu, dan memperbaruinya sebelum mengembalikan nilai. Dengan serat, kita dapat secara otomatis mengubah iterator internal menjadi yang eksternal.Ini tidak ada hubungannya dengan serat persay, tetapi izinkan saya menyebutkan satu hal lagi yang dapat Anda lakukan dengan Pencacah: mereka memungkinkan Anda untuk menerapkan metode Enumerable tingkat tinggi ke iterator lain selain
each
. Pikirkan tentang hal ini: biasanya semua metode Enumerable, termasukmap
,select
,include?
,inject
, dan sebagainya, semua pekerjaan pada unsur-unsur yang dihasilkan oleheach
. Tetapi bagaimana jika suatu objek memiliki iterator lain selaineach
?Memanggil iterator tanpa blok akan mengembalikan Enumerator, dan kemudian Anda dapat memanggil metode Enumerable lainnya.
Kembali ke serat, sudahkah Anda menggunakan
take
metode dari Enumerable?Jika ada yang memanggil
each
metode itu, sepertinya itu tidak akan pernah kembali, bukan? Lihat ini:Saya tidak tahu apakah ini menggunakan serat di bawah kap, tetapi bisa. Serat dapat digunakan untuk mengimplementasikan daftar tak terbatas dan evaluasi seri yang lambat. Untuk contoh beberapa metode malas yang didefinisikan dengan Pencacah, saya telah mendefinisikan beberapa di sini: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Anda juga dapat membangun fasilitas coroutine untuk keperluan umum menggunakan serat. Saya belum pernah menggunakan coroutine di program saya, tapi itu konsep yang bagus untuk diketahui.
Saya harap ini memberi Anda gambaran tentang kemungkinannya. Seperti yang saya katakan di awal, serat adalah primitif kontrol aliran tingkat rendah. Mereka memungkinkan untuk mempertahankan beberapa "posisi" aliran kontrol dalam program Anda (seperti "penanda" yang berbeda di halaman buku) dan beralih di antara mereka sesuai keinginan. Karena kode arbitrer dapat berjalan di fiber, Anda dapat memanggil kode pihak ketiga pada fiber, lalu "membekukan" dan melanjutkan melakukan hal lain saat memanggil kembali ke kode yang Anda kontrol.
Bayangkan sesuatu seperti ini: Anda sedang menulis program server yang akan melayani banyak klien. Interaksi lengkap dengan klien melibatkan melalui serangkaian langkah, tetapi setiap koneksi bersifat sementara, dan Anda harus mengingat status untuk setiap klien antar koneksi. (Terdengar seperti pemrograman web?)
Daripada menyimpan status tersebut secara eksplisit, dan memeriksanya setiap kali klien terhubung (untuk melihat "langkah" berikutnya yang harus mereka lakukan adalah), Anda dapat mempertahankan fiber untuk setiap klien. Setelah mengidentifikasi klien, Anda akan mengambil fiber mereka dan memulainya kembali. Kemudian di akhir setiap sambungan, Anda akan menangguhkan serat dan menyimpannya lagi. Dengan cara ini, Anda dapat menulis kode garis lurus untuk mengimplementasikan semua logika untuk interaksi lengkap, termasuk semua langkah (seperti yang biasa Anda lakukan jika program Anda dibuat untuk dijalankan secara lokal).
Saya yakin ada banyak alasan mengapa hal seperti itu mungkin tidak praktis (setidaknya untuk saat ini), tetapi sekali lagi saya hanya mencoba menunjukkan kepada Anda beberapa kemungkinan. Siapa tahu; setelah Anda mendapatkan konsepnya, Anda mungkin menemukan beberapa aplikasi yang sama sekali baru yang belum pernah terpikirkan oleh orang lain!
sumber
chars
atau enumerator lain hanya dengan penutupan?Enumerable
akan menyertakan beberapa metode "malas" di Ruby 2.0.take
tidak membutuhkan serat. Sebaliknya,take
cukup istirahat selama hasil ke-n. Saat digunakan di dalam blok,break
kembalikan kontrol ke bingkai yang mendefinisikan blok.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
Tidak seperti closure, yang memiliki titik masuk dan keluar yang ditentukan, fiber dapat mempertahankan status dan pengembaliannya (hasil) berkali-kali:
mencetak ini:
Penerapan logika ini dengan fitur ruby lainnya akan kurang terbaca.
Dengan fitur ini, penggunaan fiber yang baik adalah dengan melakukan penjadwalan kooperatif manual (sebagai pengganti Thread). Ilya Grigorik memiliki contoh yang baik tentang bagaimana mengubah pustaka asinkron (
eventmachine
dalam hal ini) menjadi apa yang tampak seperti API sinkron tanpa kehilangan keuntungan dari penjadwalan IO dari eksekusi asinkron. Ini tautannya .sumber
physical meaning
dalam contoh yang lebih sederhana