Блокове и процеси в Ruby

Започнах да уча Ruby и прочетох няколко урока и дори си купих книга („Програмиране на Ruby 1.9 – Ръководството за прагматични програмисти“) и попаднах на нещо ново, което не съм виждал преди в никоя от другите езици, които знам (работя като уеб разработчик на PHP).

Блокове и процеси. Мисля, че разбирам какво представляват, но това, което не разбирам, е защо са толкова страхотни и кога и защо трябва да ги използвам. Накъдето и да погледна казват, че блоковете и процедурите са страхотна функция в Ruby, но не ги разбирам.

Може ли някой тук да даде някакви обяснения на пълен Ruby-новак като мен?


person Nilks    schedule 24.08.2010    source източник
comment
Потърсете повече информация за Closures, това ще ви помогне да обясните как работят блоковете и процедурите и за какво са полезни.   -  person Doon    schedule 25.08.2010


Отговори (6)


Има много добри неща за блоковете. Изгледът на асансьора: Блоковете ни позволяват да предаваме действия по същия начин, по който обикновено предаваме данни.

Най-очевидното ниво е, че те ви позволяват да абстрахирате нещата във функции, които иначе не биха били възможни. Например, нека да разгледаме общ случай, когато имате списък с неща и искате да го филтрирате, за да включва само елементи, които отговарят на някакъв критерий:

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50};
int evenNumbers[50] = {0};
int copyIndex = 0;
for (int i = 0; i < 50; i++) {
    if (list[i] % 2 == 0) {
        evenNumbers[copyIndex++] = list[i];
    }
}

Ето как пишете това в Ruby:

list = 1..50
listCopy = list.select {|n| n.even?}

Цялата обща натовареност се премества от вашия код в метод със смислено име. Не ни интересува копирането на масива и преминаването през индекси и всичко това — просто искаме филтриран списък. И това е, което select ни дава. Блокът ни позволява да предадем нашата персонализирана логика в този стандартен метод.

Но итераторите не са единственото място, където тази "дупка в среден модел" е полезно. Например, ако подадете блок на File.open, той ще отвори файла, ще изпълни блока с файла и след това ще затвори файла вместо вас.

Друго нещо, което ни дават блоковете, е наистина мощна форма на обратни извиквания. Например, без блокове, може да се наложи да направим нещо подобно (въз основа на това как всъщност работят диалозите в Objective-C Cocoa):

class Controller
  def delete_button_clicked(item)
    item.add_red_highlight
    context = {:item => item}
    dialog = Dialog.new("Are you sure you want to delete #{item}?")
    dialog.ok_callback = :delete_OK
    dialog.ok_receiver = self
    dialog.cancel_callback = :cancel_delete
    dialog.cancel_receiver = self
    dialog.context = context
    dialog.ask_for_confirmation
  end

  def delete_OK(sender)
    delete(sender.context[:item])
    sender.dismiss
  end

  def cancel_delete(sender)
    sender.context[:item].remove_red_highlight
    sender.dismiss
  end
end

Йоуза. С блокове бихме могли да направим това вместо това (на базата на общ модел, използван в много Ruby библиотеки):

class Controller
  def delete_button_clicked(item)
    item.add_red_highlight
    Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response|
      response.ok { delete item }
      response.cancel { item.remove_red_highlight }
    end
  end
end

Това всъщност са две нива на блокове — блокът do...end и двата блока в стил {}. Но се чете доста естествено, нали? Това работи, защото блокът улавя контекста, в който е създаден, така че не е необходимо да предаваме self и item.

Що се отнася до Procs, те са просто обвивка на обекти за блокове. Не много за тях.

person Chuck    schedule 24.08.2010

Важно е да разглеждате блоковете не като използване на методи за започване на кодов блок, вие всъщност вземате блока и го използвате като параметър във функцията.

Така че, когато използвате метода each за итерация върху масив по следния начин:

superOverFlowArray.each { |flow| puts flow }

Изпращате блока { |flow| поставя flow } във всеки метод. Кодът казва на ruby ​​да изпрати текущата стойност на масива към този блок вместо |flow|.

Procs вземат блок и ги превръщат в променлива. Procs са екземпляри на обекти, които съдържат блокове. Блоковете се предават като параметър на proc и се изпълняват, когато извикате метода 'call' на този екземпляр на Proc.

И така, ето пример за Proc:

def category_and_title(category)
     Proc.new { |title| "The title is: " + title + " in category: " + category }
end

myFirstTitle = category_and_title("Police Drama")
mySecondTitle = category_and_title("Comedy")

puts myFirstTitle.call("Law and Order")
puts mySecondTitle.call("Seinfeld")
puts myFirstTitle.call("CSI")

Proc ще запомни оригиналната категория, която е била предадена, позволявайки удобен начин за групиране на типове.

person krx    schedule 24.08.2010

Блоковете се използват от много методи в Ruby класовете и се използват там, където в PHP бихте използвали обратно извикване.

[1,2,3,4,5].each {|i| print "#{i} "}

[1,2,3,4,5].each do |i|
  print "#{i} "
end

File.open('p014constructs.rb', 'r') do |f1|  
  while line = f1.gets  
    puts line
  end
end

PHP5 въведе анонимни функции; вместо да използвате обратно извикване, можете да използвате анонимна функция.

echo preg_replace_callback('~-([a-z])~', function ($match) {
  return strtoupper($match[1]);
}, 'hello-world');
person apaderno    schedule 24.08.2010
comment
Разбирам блоковете, когато използвам итератори и т.н., но това са всички други неща, които не разбирам. (Предимно процедури, предполагам) - person Nilks; 25.08.2010
comment
Добавете частта за блоковете като затваряния! (т.е. имате достъп до променливи извън обхвата на блока, което означава, че не е нужно да създавате куп временни структури и т.н.) - person Ana Betts; 25.08.2010
comment
какво означава, че блоковете на Ruby 1.8 нямат локални променливи. Мисля, че ще разбереш, че не си прав. - person horseyguy; 25.08.2010

Procs, известни също като затваряния или ламбда, е концепция в ruby, която може да изглежда объркваща в началото, особено за начинаещ. Накратко, procs ви позволяват да предавате кодови блокове с лекота. Пример, по-долу

    hello = Proc.new do |x, y|
      puts "i am a proc"
      sum = x+y
      puts "sum: #{sum}"
    end

сега, за да използвате този блок код, просто извикайте метода "call" на hello. Имайте предвид, че примерът по-горе получава аргументи x и y, които кодовият блок използва. Затова не забравяйте да подадете аргументи, когато извиквате обекта Proc, както направих по-долу:

    hello.call(2, 3)

дава следните резултати:

    i am a proc
    sum: 5

ура!! Това беше лесен начин за създаване на Proc обект. Колко полезно е това? Е, proc обектите ви позволяват да предавате части от код. Илюстрация по-долу го обяснява по-добре, като използва процедурата, създадена в примера по-горе. Нека създадем произволен клас,

    class SomeClass
      def initialize(&block)
        @block = block
      end

      def output_value(x, y)
        @block.call(x, y)
      end
    end

сега нека създадем екземпляр на SomeClass,

    some_class = SomeClass.new(&hello)

ЗАБЕЛЕЖЕТЕ, че знакът амперсанд "&" преди hello ви позволява да подадете proc обект като аргумент.

накрая, нека изведем стойност чрез предаване на 2 аргумента, както по-долу:

    some_class.output_value(1, 3)

резултатът, който получавате е следният:

    i am a proc
    sum: 4

Вижте!! толкова е просто. Успяхте да прехвърлите част от кода. Procs са толкова полезни, че ruby ​​ги използва често. Надявам се това да е било супер полезно :)

person Paa Yaw    schedule 22.06.2016

Тези концепции са свързани с концепции от функционалното програмиране до ruby, следователно ви позволява да използвате модели и техники, които обикновено се срещат в езици, където функциите са първокласни граждани.

person flq    schedule 24.08.2010
comment
Всъщност не, блоковете в Ruby идват от Smalltalk и в двата езика те са обекти, а не функции, въпреки че и в двата изглеждат и действат точно като анонимни функции. напр. Дори синтаксисът е много подобен. Например в smalltalk блокът ще бъде a := [:x|x+1] - person OscarRyz; 25.08.2010
comment
@OscarRyz: Донякъде избирам гниди тук, но блоковете в Ruby наистина са функции и не са обекти. Ето защо хората казват, че са затваряния - затварянията са вид функция. Не можете например да напишете totally_an_object = {|n| n * 7} — това е синтактична грешка. Трябва да извикате proc с блока, за да получите действителен обект, представляващ тази функция. Блоковете бяха истински обекти в Smalltalk, но версията на Ruby е малко по-различна - person Chuck; 25.08.2010

Блоковете и процедурите ви позволяват да извличате малки части от код без пълните разходи и сложността на методите.

Дори map сам по себе си е доста мощен и jQuery е изграден около същата концепция:

  ['spite','rest','purpose'].map {|s| s << 'ful' }

И можете да добавите логика, ако е необходимо

  ['sprite','restful','luck'].map {|s| s << (s.end_with?('uck') ? 'i' : '') << 'ly' }

Има умен оператор '&', който означава "преобразувайте символа в процедура и го извикайте", така че можете да конвертирате низове на обекти:

  ['spite',12,:purpose].map(&:to_s)

И използвайки затваряния и комбинирайки писане на прости затваряния, можем да намерим къде се появяват съседни числа. Това е доста тромав Ruby, но по-сбит от повечето езици:

  last = -2 # this variable is accessed and changed within the closure
  objs.sort.map do |o|
    if (last + 1) != o
       last = o
       nil # not one more than previous so return nil
    else
       o
    end
  end.compact  # compact removes nils

Когато започнете да го пренаписвате на друг език, разбирате колко удобно е това. Той ви насърчава да структурирате кода си в по-малки операции, така че да е по-използваем и тестван.

person ndp    schedule 25.08.2010