Объясните синтаксис итератора в Ruby on Rails - PullRequest
4 голосов
/ 20 июля 2010

Я начал изучать Ruby on Rails и обнаружил, что синтаксис запутался, поэтому мне пришлось прочитать о некотором синтаксисе Ruby. Я узнал синтаксис от http://www.cs.auckland.ac.nz/references/ruby/doc_bundle/Manual/man-1.4/syntax.html:

method_call do [`|' expr...`|'] expr...end

Они называют это Итератором. Я понимаю, что итератор проходит через цикл, но я не понимаю, как именно я должен это прочитать или что происходит в этом синтаксисе. Я все время вижу это в скриншотах RoR, и слова имеют смысл, но на самом деле я понятия не имею, что происходит. Может ли кто-нибудь объяснить мне это?

редактировать: пример

respond_to do |format|
    format.json
    format.xml { render :xml => @posts }
end

Ответы [ 7 ]

4 голосов
/ 20 июля 2010

Методы могут принимать конструкцию под названием «Блоки».Это анонимные методы, которые передаются в метод.

Другой синтаксис для этого:

method_call { |var| do_something(var) }

По сути, вы говорите, что для каждого элемента в итерации назовите его «var»и сделай что-нибудь с этим предметом.Метод просто вызывает ваш блок, который вы передали, так как он «уступает» ему элементы.

Помогает ли это?

edit: В вашем примере вы - онииспользуя шаблон итератора забавным образом ... возможно, передавая только один format объект в ваш блок, чтобы вы могли затем сказать ему, какие форматы обрабатывать, и что делать, когда вы его видите.

Другими словами, они используют шаблон для создания своего рода DSL, который позволяет вам настраивать то, на что вы отвечаете.

2 голосов
/ 20 июля 2010
method_call do [`|' expr...`|'] expr...end

Не ограничивается итерационными функциями.

В ruby ​​любой метод может принять блок в качестве аргумента. Затем блок может быть вызван методом. В случае итератора метод выглядит примерно так:

def iter
  for i in [:x,:y,:z]
    yield i
  end
end

Если вы вызываете iter с блоком, он зацикливается на [:x, :y, :z] и возвращает каждый из них блоку, который затем может делать все, что вы захотите. например распечатать их:

iter { |z| puts z }

Вы также можете использовать это, чтобы скрыть шаги инициализации и очистки, такие как открытие и закрытие файлов. например File.open. File.open, если бы он был реализован в чистом ruby ​​(он в C для производительности) сделал бы что-то вроде этого.

def File.open filename, opts
  f = File.new filename, opts
  yield f
  f.close
end

Вот почему вы можете использовать

File.open 'foobar', 'w' do |f|
  f.write 'awesome'
end

respond_to аналогично. Это работает примерно так :( посмотрите реальную реализацию здесь )

   def respond_to
     responder = Responder.new(self)
     block.call(responder)
     responder.respond
   end

Создает объект респондента, который имеет методы типа html, которые принимают блок и передают его вам. Это оказывается очень удобным, потому что позволяет делать такие вещи, как:

def action
  @foo = Foo.new params[:foo]
  respond_to do |format|
    if @foo.save
      format.html { redirect_to foo_path @foo }
      format.xml { render :xml => @foo.to_xml }
    else
      flash[:error] = "Foo could not be saved!"
      format.html { render :new }
      format.xml { render :xml => {:errors => @foo.errors }.to_xml}
    end
  end
end

Посмотрите, как я могу изменить поведение в зависимости от сохранения внутри блока? Делать это было бы намного более раздражающим без него.

2 голосов
/ 20 июля 2010

В случае итераторов, думайте о них как об интерфейсе в Java: вы можете сделать цикл for в Ruby, но все объекты, которые вы можете захотеть перебрать (должны), реализуют метод 'each', который принимает блок (т.е. закрытие, анонимная функция).

Блоки используются повсеместно в Ruby. Представьте, что у вас есть этот массив:

[1, 2, 3, 4, 5, 6].each do |i| puts i.to_s end

Здесь вы создаете массив, а затем вызываете метод 'each' для него. Вы передаете блок ему. Вы можете выделить это следующим образом:

arr = [1, 2, 3, 4, 5, 6]
string_printer = lambda do |i| puts i.to_s end
arr.each(&string_printer)

Интерфейс такого типа реализован в других вещах: коллекция Hash позволяет перебирать пары ключ-значение:

{:name => "Tom", :gender => :male}.each do |key, value| puts key end

do..end можно заменить фигурными скобками, например:

[1, 2, 3, 4, 5, 6].each {|i| puts i.to_s }

Этот тип итерации стал возможен благодаря функциональному программированию, которое использует Ruby: если вы создаете класс, которому нужно что-то итерировать, вы также можете реализовать каждый метод. Рассмотрим:

class AddressBook
  attr_accessor :addresses
  def each(&block)
    @addresses.each {|i| yield i }
  end
end

Все виды классов реализуют интересную функциональность с помощью этого шаблона блока: посмотрите, например, метод String each_line и each_byte.

1 голос
/ 20 июля 2010

То, что вы видите, это блок кода, синтаксис немного неловкий, когда вы впервые его видите.

Так что, в принципе, с итераторами у вас есть «вещь», которая может повторяться, и этополучает блок, чтобы знать, что делать.

Например, класс Range имеет метод с именем "each", который получает блок кода для выполнения на каждом элементе в диапазоне.

Допустим, вы хотите напечатать его:

range = 1..10 #range literal 
range.each {|i|
    puts i
}

Код: {|i| puts i} - это блок, который говорит, что делать, когда этот диапазон проходит по каждому из его элементов.Альтернативный синтаксис тот, который вы опубликовали:

 range.each do |i|
      puts i 
 end

Эти блоки используются с итераторами, но они не ограничиваются "итерационным" кодом, вы можете использовать их в других сценариях, например:

class Person 
  def initialize( with_name ) 
    @name = with_name
  end
  # executes a block 
  def greet 
      yield @name #passes private attribute name to the block 
  end 
end 

p = Person.new "Oscar" 
p.greet { |n|
     puts "Name length = #{n.length}"
     puts "Hello, #{n}"
}

Отпечатки:

Name length = 5
Hello, Oscar

Таким образом, вместо использования метода greet с фиксированным поведением, блок позволяет разработчику указать, что делать, что очень полезно для итераторов, нокак вы только что видели не единственное место.В вашем случае этот блок позволяет вам указать, что делать в методе respond_to.

1 голос
/ 20 июля 2010
<function> do |<temp variable>|
    <code to operate on temp variable>
end

Это создает временную анонимную функцию, которая принимает элемент во временную переменную, а затем позволяет вещам работать с этим элементом. Анонимная функция передается исходному <function>, указанному для работы с элементами, полученными с помощью этой функции.

0 голосов
/ 20 июля 2010

Я думаю, вы могли бы назвать это итератором, потому что часто блочная функция вызывается более одного раза. Как в:

5.times do |i|
  puts "#{i} "
end

«За кулисами» сделаны следующие шаги:

  • Вызывается метод times экземпляра объекта 5, передавая код puts "#{i} " в Proc экземпляре объекта.
  • Внутри метода times этот код вызывается внутри цикла, передавая текущий индекс в качестве параметра. Вот как times может выглядеть ( это на C, на самом деле ):

<code>class Fixnum
  def times_2(&block) # Specifying &block as a parameter is optional
    return self unless block_given?
    i = 0
    while(i < self) do
      yield i # Here the proc instance "block" is called
      i += 1
    end
    return self
  end
end

Обратите внимание, что область действия (т.е. локальные переменные и т. Д.) Копируется в функцию блока:

x = ' '
5.times do { |i| puts "#{i}" + x }
0 голосов
/ 20 июля 2010

Документация, которую вы читаете, древняя - практически доисторическая. Если бы веб-страницы могли собирать пыль, у этого был бы толстый слой.

Попробуйте справочный материал на веб-сайте ruby-lang . Кроме того, книга Programming Ruby (кирка) является важным справочником.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...