Я намагаюся зрозуміти блоки yield
і як вони працюють в Ruby.
Як yield
застосовується? Багато програм Rails я дивився на використання yield
дивно.
Може хтось мені пояснить чи покаже мені, куди слід їх розуміти?
Я намагаюся зрозуміти блоки yield
і як вони працюють в Ruby.
Як yield
застосовується? Багато програм Rails я дивився на використання yield
дивно.
Може хтось мені пояснить чи покаже мені, куди слід їх розуміти?
Відповіді:
Так, спочатку це трохи спантеличено.
У Ruby методи можуть отримати блок коду для виконання довільних сегментів коду.
Коли метод очікує блоку, він викликає його, викликаючи yield
функцію.
Наприклад, це дуже зручно, наприклад, для перегляду списку або надання користувальницького алгоритму.
Візьмемо такий приклад:
Я збираюся визначити Person
клас, ініціалізований іменем, і надати do_with_name
метод, який при виклику просто передасть name
атрибут до отриманого блоку.
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Це дозволило б викликати цей метод і передати довільний блок коду.
Наприклад, для того, щоб надрукувати ім’я:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Буде надруковано:
Hey, his name is Oscar
Зауважте, блок отримує, як параметр, змінну, що називається name
(NB ви можете називати цю змінну все, що завгодно, але має сенс називати її name
). Коли код викликає, yield
він заповнює цей параметр значенням @name
.
yield( @name )
Ми могли б надати ще один блок для виконання іншої дії. Наприклад, зворотне ім’я:
#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"
Ми використовували абсолютно той самий метод ( do_with_name
) - це просто інший блок.
Цей приклад банальний. Більш цікавими звичками є фільтрування всіх елементів у масиві:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
Або ми також можемо надати користувальницький алгоритм сортування, наприклад на основі розміру рядка:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Я сподіваюся, що це допоможе вам зрозуміти це краще.
BTW, якщо блок є необов’язковим, слід назвати його так:
yield(value) if block_given?
Якщо це не обов'язково, просто запустіть його.
EDIT
@hmak створив repl.it для цих прикладів: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
якщо the_name = ""
"Oscar"
(не дуже зрозуміло у відповіді)
person.do_with_name {|string| yield string, something_else }
У Ruby методи можуть перевірити, чи були вони викликані таким чином, що блок був наданий на додаток до звичайних аргументів. Зазвичай це робиться за допомогою block_given?
методу, але ви також можете посилатися на блок як на явний Proc, встановивши префікс ampersand ( &
) перед остаточним іменем аргументу.
Якщо метод викликається блоком, то метод може yield
керувати блоком (викликати блок) за допомогою деяких аргументів, якщо це необхідно. Розглянемо цей приклад методу, який демонструє:
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 =)
Або, використовуючи синтаксис спеціального блочного аргументу:
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 =)
Цілком можливо, що хтось дасть справді детальну відповідь тут, але я завжди вважав, що цей пост від Роберта Сосінського є чудовим поясненням тонкощів між блоками, документами та лямбдами.
Варто додати, що я вважаю, що посада, з якою я посилаюсь, є специфічною для ruby 1.8. Деякі речі змінилися в рубіні 1.9, наприклад, що змінні блоку є локальними для блоку. У 1.8 ви отримаєте щось таке:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Тоді як 1.9 дасть вам:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
У мене немає 1,9 на цій машині, тому вищевказане може мати помилку в ньому.
Я хотів якось додати, чому б ти робив так, щоб вже чудові відповіді.
Поняття не маєте, з якої мови ви походите, але якщо припустити, що це статична мова, подібні речі виглядатимуть звично. Ось як ви читаєте файл у 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();
}
}
}
Ігноруючи всю річину, що прикувала річ, ідея така
Так ви робите це в рубіні
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Дико відрізняється. Порушення цього
Тут замість обробки першого та другого етапів ви делегуєте це в інший клас. Як бачимо, це значно зменшує кількість коду, який потрібно написати, що полегшує читання і зменшує шанси на такі речі, як витоки пам’яті, або блокування файлів не очищається.
Тепер це не так, як ти не можеш зробити щось подібне в Java, насправді люди роблять це вже десятиліттями. Це називається " Стратегія" . Різниця полягає в тому, що без блоків, для чогось такого простого, як приклад файлу, стратегія стає непосильною через кількість класів і методів, які потрібно написати. З блоками це такий простий і елегантний спосіб зробити це, що не має сенсу НЕ структурувати свій код таким чином.
Це не єдиний спосіб використання блоків, але інші (як зразок Builder, який ви можете побачити у form_for api в рейках) досить схожі, що повинно бути очевидним, що відбувається, коли ви обернете голову навколо цього. Коли ви бачите блоки, звичайно безпечно припустити, що виклик методу - це те, що ви хочете зробити, і блок описує, як ви хочете це зробити.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
і ще більше сміяйтесь над хлопцями Java.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(плюс проблеми з пам’яттю)
Я вважав цю статтю дуже корисною. Зокрема, наступний приклад:
#!/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
який повинен дати такий вихід:
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
Тому по суті кожен раз, коли здійснюється дзвінок на yield
рубін, запускається код у do
блоці або всередині {}
. Якщо параметр надається, yield
він буде наданий як параметр для do
блоку.
Для мене це був перший раз, коли я дійсно зрозумів, що do
роблять блоки. В основному це функція для доступу до внутрішніх структур даних, будь то для ітерації або для конфігурації функції.
Тож коли в рейках ви пишете наступне:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Це запустить respond_to
функцію, яка дає do
блок з (внутрішнім) format
параметром. Потім ви викликаєте .html
функцію цієї внутрішньої змінної, яка, в свою чергу, дає блок коду для запуску render
команди. Зауважте, що .html
вийде лише тоді, коли це запитуваний формат файлу. (технічність: ці функції фактично використовуються block.call
не так, yield
як ви можете бачити з джерела, але функціональність, по суті, однакова, див. це питання для обговорення.) Це забезпечує спосіб для функції виконати деяку ініціалізацію, а потім взяти вхід з викличного коду та потім при необхідності продовжуйте обробку.
Або, по-іншому, це схоже на функцію, яка приймає анонімну функцію як аргумент і потім викликає її в JavaScript.
У Ruby блок - це в основному фрагмент коду, який можна передавати та виконувати будь-яким методом. Блоки завжди використовуються з методами, які зазвичай подають їм дані (як аргументи).
Блоки широко використовуються в дорогоцінних каменях Ruby (включаючи Rails) і в добре написаному коді Ruby. Вони не є об'єктами, тому їх не можна присвоювати змінним.
Блок - це фрагмент коду, доданий {} або do..end. За умовою, фігурний синтаксис дужок повинен використовуватися для однорядних блоків, а синтаксис do..end - для багаторядкових блоків.
{ # This is a single line block }
do
# This is a multi-line block
end
Будь-який метод може отримати блок як неявний аргумент. Блок виконується оператором дохідності в межах методу. Основний синтаксис:
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.
Коли дохід твердження досягнуто, метод медитації передає контроль блоку, виконується код всередині блоку і контроль повертається до методу, який відновлює виконання відразу після заяви про вихід.
Коли метод містить заяву про вихід, він очікує отримання блоку під час виклику. Якщо блок не надається, виняток буде викинуто, коли буде досягнуто заяву про врожайність. Ми можемо зробити блок необов’язковим і уникнути підвищення винятку:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
Неможливо передати декілька блоків методу. Кожен метод може отримати лише один блок.
Детальніше дивіться на: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
Я іноді використовую "урожай" так:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
не повинен виконувати якесь завдання, потрібно виконувати якесь завдання. Ви повинні пояснити своє, хоча ...
Тут я хочу зауважити два моменти. По-перше, хоча тут багато відповідей говорять про різні способи передачі блоку методу, який використовує вихід, давайте також поговоримо про контрольний потік. Це особливо актуально, оскільки ви можете віддавати МНОГО кратних разів до блоку. Давайте подивимось на приклад:
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
Коли кожен метод викликається, він виконує рядок за рядком. Тепер, коли ми перейдемо до блоку 3-х разів, цей блок буде викликаний 3 рази. Кожен раз, коли він викликає врожайність. Цей вихід пов'язаний з блоком, пов'язаним із методом, який викликав кожен метод. Важливо зауважити, що кожного разу, коли викликається вихід, він повертає контроль назад до блоку кожного методу в коді клієнта. Після завершення виконання блоку він повертається до блоку 3 рази. І це трапляється 3 рази. Таким чином, цей блок у коді клієнта викликається 3 окремих випадків, оскільки вихід явно називається 3 окремими раз
Мій другий пункт про enum_for та врожайність. enum_for створює клас Enumerator, і цей об’єкт Enumerator також відповідає на вихід.
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"
Тож зауважте щоразу, коли ми запускаємо види із зовнішнім ітератором, він буде викликати вихід лише один раз. Наступного разу, коли ми його назвемо, це призведе до наступного врожаю тощо.
Є цікавий примружок щодо enum_for. Документація в Інтернеті визначає наступне:
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
Якщо ви не вказали символ як аргумент enum_for, ruby підключить перечислювач до кожного методу одержувача. У деяких класах немає кожного методу, як клас String.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Таким чином, у випадку деяких об’єктів, на які посилається enum_for, ви повинні мати чітке відношення до того, яким буде ваш метод перерахування.
Вихід може бути використаний як безіменний блок для повернення значення методу. Розглянемо наступний код:
Def Up(anarg)
yield(anarg)
end
Ви можете створити метод "Вгору", якому призначений один аргумент. Тепер ви можете призначити цей аргумент для отримання, який буде викликати та виконувати пов'язаний блок. Ви можете призначити блок після списку параметрів.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Коли метод Up викликає дохід, з аргументом, він передається до змінної блоку для обробки запиту.