Lifler, muhtemelen doğrudan uygulama düzeyinde kodda kullanmayacağınız bir şeydir. Bunlar, daha sonra daha yüksek seviyeli kodda kullanabileceğiniz diğer soyutlamaları oluşturmak için kullanabileceğiniz bir akış kontrolü ilkelidir.
Muhtemelen Ruby'deki fiberlerin 1 numaralı kullanımı, Enumerator
Ruby 1.9'da çekirdek bir Ruby sınıfı olan s'yi uygulamaktır . Bunlar inanılmaz derecede faydalıdır.
Eğer çekirdek sınıfları üzerinde hemen hemen herhangi bir yineleyici yöntemi çağrısı Ruby 1.9 içinde, olmayan bir blok geçen bir döner Enumerator
.
irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>
Bunlar Enumerator
Numaralandırılabilir nesnelerdir ve each
yöntemleri, bir blokla çağrılmış olsaydı, orijinal yineleme yöntemiyle verilecek olan öğeleri verir. Az önce verdiğim örnekte, Numaralandırıcı 3,2,1 veren reverse_each
bir each
yönteme sahip. Tarafından döndürülen Numaralandırıcı chars
"c", "b", "a" (vb.) Sonucunu verir. AMA, orijinal yineleme yönteminin aksine Numaralandırıcı, next
tekrar tekrar çağırırsanız öğeleri tek tek de döndürebilir :
irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"
"Dahili yineleyiciler" ve "harici yineleyiciler" i duymuş olabilirsiniz (her ikisinin de iyi bir açıklaması "Dörtlü Çete" Tasarım Kalıpları kitabında verilmiştir). Yukarıdaki örnek, Numaralandırıcıların dahili bir yineleyiciyi harici bir yineleyiciye dönüştürmek için kullanılabileceğini göstermektedir.
Bu, kendi numaralandırıcılarınızı oluşturmanın bir yoludur:
class SomeClass
def an_iterator
# note the 'return enum_for...' pattern; it's very useful
# enum_for is an Object method
# so even for iterators which don't return an Enumerator when called
# with no block, you can easily get one by calling 'enum_for'
return enum_for(:an_iterator) if not block_given?
yield 1
yield 2
yield 3
end
end
Hadi deneyelim:
e = SomeClass.new.an_iterator
e.next # => 1
e.next # => 2
e.next # => 3
Bekle bir dakika ... orada tuhaf bir şey var mı? Sen yazdığı yield
ifadeleri an_iterator
doğrusal kodu olarak ancak Listeleyicisi onlara çalıştırabilir birer birer . Yapılan aramalar arasında next
, uygulama an_iterator
"dondurulur". Her aradığınızda next
, aşağıdaki yield
ifadeye doğru çalışmaya devam eder ve ardından tekrar "donar".
Bunun nasıl uygulandığını tahmin edebilir misiniz? Numaralandırıcı, çağrıyı an_iterator
bir fiberde sarar ve fiberi askıya alan bir bloğu geçer . Böylece an_iterator
bloğa her dönüşünde, üzerinde çalıştığı fiber askıya alınır ve yürütme ana iş parçacığı üzerinde devam eder. Bir dahaki sefere çağırdığınızda next
, kontrolü fibere aktarır, blok geri döner ve an_iterator
kaldığı yerden devam eder.
Lifler olmadan bunu yapmak için neyin gerekli olduğunu düşünmek öğretici olacaktır. Hem iç hem de dış yineleyiciler sağlamak isteyen HER sınıf, çağrılar arasındaki durumu takip etmek için açık kod içermelidir next
. Bir sonrakine yapılacak her çağrı, bu durumu kontrol etmeli ve bir değer döndürmeden önce onu güncellemelidir. Fiberlerle, herhangi bir dahili yineleyiciyi otomatik olarak harici bir yineleyiciye dönüştürebiliriz.
Bunun fiber persay ile ilgisi yoktur, ancak Numaralandırıcılar ile yapabileceğiniz bir şeyden daha bahsetmeme izin verin: bunlar, dışındaki diğer yineleyiciler için daha yüksek mertebeden Numaralandırılabilir yöntemleri uygulamanıza izin verir each
. Bir düşünün: Normalde tüm Enumerable yöntemleri, dahil map
, select
, include?
, inject
, ve benzeri tüm unsurları üzerinde çalışmak tarafından vermiştir each
. Peki ya bir nesnenin dışında başka yineleyiciler varsa each
?
irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]
Yineleyiciyi blok olmadan çağırmak bir Numaralandırıcı döndürür ve ardından bununla ilgili diğer Numaralandırılabilir yöntemleri çağırabilirsiniz.
Liflere geri take
dönersek, Enumerable'daki yöntemi kullandınız mı?
class InfiniteSeries
include Enumerable
def each
i = 0
loop { yield(i += 1) }
end
end
Bu each
yöntemi çağıran bir şey varsa , asla geri dönmemesi gerekiyor, değil mi? Şuna bir bak:
InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Bunun kaputun altında lif kullanıp kullanmadığını bilmiyorum, ama olabilir. Lifler, sonsuz listeleri ve bir serinin tembel değerlendirmesini uygulamak için kullanılabilir. Numaralandırıcılar ile tanımlanan bazı tembel yöntemlere bir örnek için, burada bazılarını tanımladım: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Ayrıca lifleri kullanarak genel amaçlı bir koroutin tesisi de oluşturabilirsiniz. Henüz programlarımın hiçbirinde coroutine kullanmadım, ancak bu bilmek iyi bir kavram.
Umarım bu size olasılıklar hakkında bir fikir verir. Başlangıçta söylediğim gibi, lifler düşük seviyeli bir akış kontrol ilkelidir. Programınızda çoklu kontrol akışı "konumlarını" (bir kitabın sayfalarındaki farklı "yer imleri" gibi) korumayı ve bunlar arasında istenildiği gibi geçiş yapmayı mümkün kılarlar. Rasgele kod bir fiberde çalışabileceğinden, bir fiber üzerindeki 3. taraf kodunu çağırabilir ve ardından onu "dondurabilir" ve kontrol ettiğiniz koda geri döndüğünde başka bir şey yapmaya devam edebilirsiniz.
Şöyle bir şey hayal edin: birçok istemciye hizmet verecek bir sunucu programı yazıyorsunuz. Bir müşteri ile tam bir etkileşim, bir dizi adımdan geçmeyi içerir, ancak her bağlantı geçicidir ve bağlantılar arasındaki her müşteri için durumu hatırlamanız gerekir. (Web programlama gibi geliyor mu?)
Bu durumu açıkça depolamak ve bir istemci her bağlandığında bunu kontrol etmek yerine (yapmaları gereken bir sonraki "adımın" ne olduğunu görmek için), her istemci için bir fiber tutabilirsiniz. Müşteriyi belirledikten sonra, fiberlerini alır ve yeniden başlatırsınız. Sonra her bağlantının sonunda, fiberi askıya alır ve tekrar depolarsınız. Bu şekilde, tüm adımlar da dahil olmak üzere tam bir etkileşim için tüm mantığı uygulamak için düz satırlı kod yazabilirsiniz (tıpkı programınız yerel olarak çalıştırıldığında doğal olarak yapacağınız gibi).
Eminim böyle bir şeyin pratik olmamasının pek çok nedeni vardır (en azından şimdilik), ama yine size sadece bazı olasılıkları göstermeye çalışıyorum. Kim bilir; Konsepti bir kez edindikten sonra, henüz kimsenin aklına gelmeyen tamamen yeni bir uygulama bulabilirsin!