Próbuję zrozumieć bloki i yieldich działanie w Ruby.
Jak yieldstosować? Wiele aplikacji Rails, na które patrzyłem, używało yieldw dziwny sposób.
Czy ktoś może mi wyjaśnić lub pokazać, gdzie mam je zrozumieć?
Próbuję zrozumieć bloki i yieldich działanie w Ruby.
Jak yieldstosować? Wiele aplikacji Rails, na które patrzyłem, używało yieldw 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 yieldfunkcję.
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ć Personklasę zainicjowaną nazwą i podać do_with_namemetodę, która po wywołaniu przekaże nameatrybut 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 yieldwypeł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
racsOjeś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 yieldsterować 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}" endi ś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 yieldruby, uruchomi kod w dobloku lub w środku {}. Jeśli podano parametr, yieldto zostanie on podany jako parametr do dobloku.
Dla mnie to był pierwszy raz, kiedy naprawdę zrozumiałem, co dorobią 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_tofunkcję, która zwróci doblok z parametrem (wewnętrznym) format. Następnie wywołujesz .htmlfunkcję dla tej zmiennej wewnętrznej, co z kolei daje blok kodu do uruchomienia renderpolecenia. Pamiętaj, że .htmlprzyniesie to tylko wtedy, gdy jest to wymagany format pliku. (technicznie: te funkcje faktycznie block.callnie używają tego, yieldco 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.