Próbuję zrozumieć bloki i yield
ich działanie w Ruby.
Jak yield
stosować? Wiele aplikacji Rails, na które patrzyłem, używało yield
w dziwny sposób.
Czy ktoś może mi wyjaśnić lub pokazać, gdzie mam je zrozumieć?
Próbuję zrozumieć bloki i yield
ich działanie w Ruby.
Jak yield
stosować? Wiele aplikacji Rails, na które patrzyłem, używało yield
w dziwny sposób.
Czy ktoś może mi wyjaśnić lub pokazać, gdzie mam je zrozumieć?
Odpowiedzi:
Tak, na początku jest to trochę zagadkowe.
W języku Ruby metody mogą odbierać blok kodu w celu wykonania dowolnych segmentów kodu.
Gdy metoda oczekuje bloku, wywołuje go, wywołując yield
funkcję.
Jest to bardzo przydatne, na przykład, do iteracji po liście lub zapewnienia niestandardowego algorytmu.
Weź następujący przykład:
Mam zamiar zdefiniować Person
klasę zainicjowaną nazwą i podać do_with_name
metodę, która po wywołaniu przekaże name
atrybut do otrzymanego bloku.
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Pozwoliłoby nam to wywołać tę metodę i przekazać dowolny blok kodu.
Na przykład, aby wydrukować nazwę, zrobilibyśmy:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Wydrukowałby:
Hey, his name is Oscar
Zauważ, że blok otrzymuje jako parametr zmienną o nazwie name
(uwaga: możesz wywoływać tę zmienną w dowolny sposób, ale warto ją wywołać name
). Po wywołaniu kodu yield
wypełnia ten parametr wartością @name
.
yield( @name )
Możemy zapewnić kolejny blok, aby wykonać inną akcję. Na przykład odwróć nazwę:
#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"
Zastosowaliśmy dokładnie tę samą metodę ( do_with_name
) - to tylko inny blok.
Ten przykład jest trywialny. Bardziej interesujące zastosowania to filtrowanie wszystkich elementów w tablicy:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
Lub możemy również dostarczyć niestandardowy algorytm sortowania, na przykład na podstawie rozmiaru ciągu:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Mam nadzieję, że to pomoże ci lepiej to zrozumieć.
BTW, jeśli blok jest opcjonalny, powinieneś go nazwać:
yield(value) if block_given?
Jeśli nie jest opcjonalny, po prostu go wywołaj.
EDYTOWAĆ
@hmak utworzył repl.it dla tych przykładów: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
jeśli the_name = ""
"Oscar"
(nie jest bardzo jasne w odpowiedzi)
person.do_with_name {|string| yield string, something_else }
W języku Ruby metody mogą sprawdzić, czy zostały wywołane w taki sposób, że oprócz zwykłych argumentów podano blok. Zwykle odbywa się to za pomocą block_given?
metody, ale można również odwołać się do bloku jako jawnego Proc, poprzedzając znak ampersand ( &
) przed ostateczną nazwą argumentu.
Jeśli metoda jest wywoływana z blokiem, metoda może yield
sterować blokiem (wywołać blok) z pewnymi argumentami, jeśli to konieczne. Rozważ tę przykładową metodę, która pokazuje:
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 =)
Lub używając specjalnej składni argumentu blokowego:
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 =)
Całkiem możliwe, że ktoś udzieli tu naprawdę szczegółowej odpowiedzi, ale zawsze uważałem ten post Roberta Sosińskiego za doskonałe wyjaśnienie subtelności między blokami, procami i lambdami.
Powinienem dodać, że moim zdaniem post, do którego prowadzę link, jest specyficzny dla ruby 1.8. Niektóre rzeczy uległy zmianie w Ruby 1.9, na przykład zmienne blokowe są lokalne dla bloku. W wersji 1.8 otrzymasz coś takiego:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Podczas gdy 1.9 zapewni Ci:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
Nie mam 1.9 na tym komputerze, więc powyższe może zawierać błąd.
Chciałem w pewnym sensie dodać, dlaczego tak robisz w odpowiedzi na i tak świetne odpowiedzi.
Nie mam pojęcia, z jakiego języka pochodzisz, ale zakładając, że jest to język statyczny, tego rodzaju rzeczy będą wyglądały znajomo. W ten sposób czytasz plik w Javie
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();
}
}
}
Ignorując cały łańcuch związany z strumieniem, Chodzi o to
Tak to robisz w rubinie
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Zupełnie inaczej. Rozbicie tego
Tutaj, zamiast zajmować się krokiem pierwszym i drugim, w zasadzie delegujesz to do innej klasy. Jak widać, radykalnie zmniejsza to ilość kodu, który musisz napisać, co ułatwia czytanie i zmniejsza ryzyko wycieków pamięci lub braku możliwości usunięcia blokad plików.
Teraz nie jest tak, że nie można zrobić czegoś podobnego w java, w rzeczywistości ludzie robią to od dziesięcioleci. To się nazywa wzór strategii . Różnica polega na tym, że bez bloków, w przypadku czegoś tak prostego jak przykład pliku, strategia staje się nadmierna z powodu ilości klas i metod, które musisz napisać. W przypadku bloków jest to tak prosty i elegancki sposób, aby to zrobić, że NIE ma sensu NIE strukturyzować kodu w ten sposób.
Nie jest to jedyny sposób, w jaki używane są bloki, ale inne (jak wzorzec Konstruktora, który można zobaczyć w form_for api w szynach) są na tyle podobne, że powinno być oczywiste, co się dzieje, gdy obejdziesz się tym. Kiedy widzisz bloki, zwykle bezpiecznie jest założyć, że wywołanie metody jest tym, co chcesz zrobić, a blok opisuje, jak chcesz to zrobić.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
i śmiejmy się jeszcze mocniej z ludzi z Java.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(plus brak problemów z pamięcią)
Uważam, że ten artykuł jest bardzo przydatny. W szczególności następujący przykład:
#!/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
co powinno dać następujące dane wyjściowe:
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
Zasadniczo za każdym razem, gdy zostanie wykonane wywołanie yield
ruby, uruchomi kod w do
bloku lub w środku {}
. Jeśli podano parametr, yield
to zostanie on podany jako parametr do do
bloku.
Dla mnie to był pierwszy raz, kiedy naprawdę zrozumiałem, co do
robią bloki. Zasadniczo jest to sposób, w jaki funkcja umożliwia dostęp do wewnętrznych struktur danych, zarówno w celu iteracji, jak i konfiguracji funkcji.
Kiedy więc w szynach piszesz:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Uruchomi to respond_to
funkcję, która zwróci do
blok z parametrem (wewnętrznym) format
. Następnie wywołujesz .html
funkcję dla tej zmiennej wewnętrznej, co z kolei daje blok kodu do uruchomienia render
polecenia. Pamiętaj, że .html
przyniesie to tylko wtedy, gdy jest to wymagany format pliku. (technicznie: te funkcje faktycznie block.call
nie używają tego, yield
co widać ze źródła, ale funkcjonalność jest zasadniczo taka sama, zobacz to pytanie w celu omówienia.) Zapewnia to sposób, aby funkcja wykonała pewną inicjalizację, a następnie pobierała dane wejściowe z kodu wywołującego i następnie w razie potrzeby kontynuować przetwarzanie.
Innymi słowy, jest podobny do funkcji przyjmującej anonimową funkcję jako argument, a następnie wywołującej ją w javascript.
W Ruby blok jest zasadniczo fragmentem kodu, który można przekazać i wykonać dowolną metodą. Bloki są zawsze używane z metodami, które zwykle przekazują im dane (jako argumenty).
Bloki są szeroko stosowane w klejnotach Ruby (w tym Railsach) oraz w dobrze napisanym kodzie Ruby. Nie są obiektami, dlatego nie można ich przypisywać do zmiennych.
Blok to fragment kodu ujęty w {} lub do..end. Zgodnie z konwencją w przypadku bloków jednowierszowych należy stosować składnię nawiasów klamrowych, aw przypadku bloków wieloliniowych należy stosować składnię do..end.
{ # This is a single line block }
do
# This is a multi-line block
end
Każda metoda może otrzymać blok jako domyślny argument. Blok jest wykonywany przez instrukcję return w ramach metody. Podstawowa składnia to:
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.
Po osiągnięciu instrukcji fedrowania metoda medytacji daje kontrolę nad blokiem, kod w bloku jest wykonywany, a kontrola jest zwracana do metody, która wznawia wykonywanie natychmiast po instrukcji fed.
Gdy metoda zawiera instrukcję dochodu, oczekuje otrzymania bloku w czasie wywołania. Jeśli blok nie zostanie podany, wyjątek zostanie zgłoszony po osiągnięciu instrukcji dochodu. Możemy uczynić blok opcjonalnym i uniknąć generowania wyjątku:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
Nie można przekazać wielu bloków do metody. Każda metoda może otrzymać tylko jeden blok.
Zobacz więcej na: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
Czasami używam „wydajności” w ten sposób:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
że użytkownik nie musi wykonywać jakiegoś zadania, jeśli użytkownik nie musi tego robić. Powinieneś jednak wyjaśnić swoje ...
Chciałbym tu wspomnieć o dwóch kwestiach dotyczących wydajności. Po pierwsze, podczas gdy wiele odpowiedzi tutaj mówi o różnych sposobach przekazania bloku do metody wykorzystującej wydajność, porozmawiajmy również o przepływie sterowania. Jest to szczególnie istotne, ponieważ możesz dać WIELU CZASÓW blokowi. Spójrzmy na przykład:
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
Po wywołaniu każdej metody wykonuje się linia po linii. Teraz, gdy przejdziemy do bloku 3. razy, ten blok zostanie wywołany 3 razy. Za każdym razem, gdy wywołuje plon. Ta wydajność jest powiązana z blokiem związanym z metodą, która wywołała każdą metodę. Należy zauważyć, że za każdym razem, gdy wywoływana jest wydajność, zwraca kontrolę z powrotem do bloku każdej metody w kodzie klienta. Po zakończeniu wykonywania blok wraca do bloku 3. razy. I dzieje się to 3 razy. Tak więc blok w kodzie klienta jest wywoływany 3 odrębnymi okazjami, ponieważ fedrunek jest jawnie nazywany 3 oddzielnymi czasami.
Moja druga uwaga dotyczy enum_for i fed. enum_for tworzy instancję klasy Enumerator, a ten obiekt Enumerator odpowiada również na fed.
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"
Zauważ więc, że za każdym razem, gdy wywołujemy rodzaje za pomocą zewnętrznego iteratora, wywoła to wydajność tylko raz. Następnym razem, gdy go nazwiemy, wywoła następną wydajność i tak dalej.
Jest ciekawa ciekawostka w odniesieniu do enum_for. Dokumentacja online zawiera następujące informacje:
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
Jeśli nie podasz symbolu jako argumentu dla enum_for, ruby podłączy moduł wyliczający do każdej metody odbiornika. Niektóre klasy nie mają każdej metody, na przykład klasa String.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Tak więc, w przypadku niektórych obiektów wywoływanych za pomocą enum_for, musisz wyraźnie określić, jaka będzie twoja metoda wyliczania.
Wydajność może być użyta jako blok bezimienny do zwrócenia wartości w metodzie. Rozważ następujący kod:
Def Up(anarg)
yield(anarg)
end
Możesz utworzyć metodę „W górę”, która ma przypisany jeden argument. Możesz teraz przypisać ten argument, aby uzyskać wynik, który wywoła i wykona powiązany blok. Możesz przypisać blok po liście parametrów.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Gdy wywołanie metody Up daje argument z argumentem, jest on przekazywany do zmiennej blokowej w celu przetworzenia żądania.