Статьи

Как справиться с блоками

Когда я переключался на Ruby из фона PHP, одной из самых серых областей для меня были блоки. В Ruby повсюду есть блоки, и их плохое понимание остановит ваше обучение на этапе «новичка». Когда вы только начинаете работать с Ruby, вы неизбежно будете использовать блоки исключительно для итерации.

Привет блоки

3.times {|n| puts "Printed #{n} times" }

Приведенный выше код должен быть «привет миром» блоков, и из этой единственной строки мы узнаем много нового о Ruby. У нас есть число ( Fixnumtimes В случае, если вы пропустили важный бит, «метод, который принимает блок». На данный момент, просто слепо принять блок — это просто кусок кода, который мы можем передать, и который может быть определен только после вызова метода, он может принимать параметры и имеет 2 стиля синтаксиса. У нас есть блоки, определенные в фигурных скобках, как указано выше, или определенные в doend Обычной практикой является использование фигурных скобок, если содержимое блока состоит из одной строки (хотя Авди Гримм дает хороший аргумент для их смешивания в зависимости от того, что делает блок ). В любом случае, приведенный выше код будет так же правильно написан так:

 3.times do |n|
  puts "Printed #{n} times"
end

В PHP мы совсем не привыкли к блокам. На самом деле, можно сказать, что для чего-то настолько тривиального, было бы гораздо лучше просто использовать наш надежный цикл (для каждого). Но слишком много раз я сталкивался со следующим.

 for($i = 0; $i < count($values); $i++)

Теперь в этой единственной строке кода есть множество проблем (сейчас она используется рядом с вами). Для начала count() Но даже это не самая большая моя неприятность. Использование переменной $i$i Я сам виню C, $i

Если рассматривать прошлую область видимости, семантику или даже реализовывать это как цикл foreach, у нас все еще есть то, что выглядит как внешняя циклическая конструкция, вмешивающаяся в наши данные. Я писал такие вещи изо дня в день. Но в наши дни, проведя некоторое время в Ruby в реальном времени, он просто не может использовать циклические конструкции, такие как for Помещая бизнес в блок, он становится чище, и мы получаем закрытие бесплатно (локальные переменные блоков находятся только в области действия блока).

Закрытие PHP

В PHP 5.3 были введены замыкания. Это сделало операцию, которую мы ожидаем со times

 class MyInt {
  protected $value = 0;

  public function __construct($value) {
    $this->value = $value;
  }

  public function times($pBlock) {
    for($i = 0; $i < $this->value; $i++) {
      $pBlock($i);
    }
  }
}

$echo_statement = function($value) {
  echo $value;
};

$val = new MyInt(3);
$val->times($echo_statement);

Таким образом, мы в основном создали новый класс, который будет действовать как Ruby Fixnum$i Это довольно мощная штука, но посмотрите на циклы кода, через которые мы должны перейти, чтобы получить что-то такое же лаконичное, как блок Ruby.

Пролить бобы на методы

Если бы мы создали нашу собственную реализацию times

 class Fixnum
  def times
    for i in (1..self)
      yield i
    end
  end
end

5.times { |n| puts "Im Hi #{n}" }

Выше мы снова открыли класс Fixnumtimes Поэтому мы написали цикл for, который перебирает диапазон от 1 до чего угодно. Самая интересная строка в приведенном выше: yield i Метод yield

Подожди минутку. Где мы проходили мимо указанного блока? Ну, каждый метод принимает блок (независимо от того, определили вы его или нет). Поэтому нам даже не нужно объявлять блок как параметр для нашего метода times Он знает, что блок может быть там. Но мы решили позволить ему работать с использованием yield

Так что же произойдет, если мы забудем передать блок? Ну, ответ будет LocalJumpError : no block given Однако мы можем дать этому методу немного больше гибкости, используя block_given? ,

 class Fixnum
  def times
    if block_given?
      for i in (1..self)
        yield i
      end
    else
      Enumerable::Enumerator.new self
    end
  end
end

5.times { |n| puts "Im Hi #{n}" }
Im Hi 0
Im Hi 1
Im Hi 2
Im Hi 3
Im Hi 4
=> 1..5

5.times
=> #<Enumerable::Enumerator:0xb737acb4>

Здесь мы добавили немного криволинейного шара, возвращая новый объект Enumerable::Enumerator Это было главным образом, чтобы подражать реальной реализации times Он вернет объект Enumerator, что позволит нам сделать следующее:

 enumerator = 5.times
=> #<Enumerable::Enumerator:0xb737acb4>
enumerator.max
=> 4
enumerator.min
=> 0
enumerator.minmax
=> [0, 4]

Блок Сфера

Блоки являются замыканиями, они берут кусок кода и его локальную среду (переменные и т. Д.) И сохраняют его для выполнения позже. Поэтому лучше поговорить о влиянии блоков на окружающую среду. В соответствии с тем, что мы, естественно, ожидаем, переменные, определенные внутри блока, выбрасываются после выполнения блока. Но есть некоторые непредсказуемые поведения в зависимости от того, какую версию Ruby мы используем при определении блоков:

 n = 10
y = 0
[1,2,3].each do |n|
  x = n
  y = y + n
end

y.inspect
# => "6"
n.inspect
# => "3" -- In 1.9.X this will be 10
defined? x
# => nil

Как и ожидалось, переменная x Однако мы можем видеть n Я не знаю о вас, но я чувствую, что это поведение неестественно. Я слышал случаи, когда это действительно «особенность», но мне еще не приходилось сталкиваться с такими ситуациями. Наконец, y

Я упоминаю это поведение с параметрами блока ( n изменяется) для тех из вас, кто играет с Ruby 1.8.x. В Ruby 1.9.x параметр n остается неизменным для блока. Я предпочитаю такое поведение и предлагаю вам также работать с последней и лучшей версией Ruby.

Завершение

  • Блоки — это кусок кода, который позже откладывается для исполнения (продолжайте, просто скажите «закрытие»)
  • Все методы в Ruby принимают блок по умолчанию, и мы можем вызвать блок, используя yield
  • Блоки работают в собственной закрытой среде
  • Переменные, локальные для блока, выбрасываются после выполнения
  • Переменные, определенные до определения блока, заключены в область видимости блоков.

Можно с уверенностью сказать, что мы добились хороших результатов на пути изучения блоков, в следующий раз мы рассмотрим магию lamdaProc