Pull to refresh

Темная сторона Ruby, Proc и lambda

Ruby
Многие любят руби за его красоту и гибкость. Но некоторые не догадываются о темной стороне языка Ruby. Сегодня расмотрим одну из темных лошадок руби, многие им пользуются, но не многие знают его секрет. Итак, тема дня блоки!

Блоки в руби используются практически везде, зачастую, они заменяют методы. Но, есть существенные отличия между методом и блоком.

Первое отличие:
Блоки видят переменные объявленные в области действия блока, а методы нет.

Пример с использованием блоков:
hello = "Hello World"
l = lambda {puts hello}
l.call #=> Hello World => nil

Пример с использование метода:
def say_hello
puts hello
end
hello = "Hello World"
say_hello #=> NameError: undefined local variable or method `hello'

Как видим, метод не видит переменную пока вы не передадите её как аргумент

Второе отличие:
Чувствительность методов и блоков к принимаемым аргументам. Методы очень чувствительны к аргументам, можно передать только то количество аргументов которые описаны в методе. Иначе вы получите ошибку вместо результата.
С блоками проблема проще и сложнее в одно и тоже время, в руби существует два вида блоков, Proc.new {} и lambda {}, каждый из них реагирует по разному на аргументы.

p = Proc.new { |i, k| [i, k] }
l = lambda { |i, k| [i, k] }

p.call("hello", "world") # => ["hello","world"]
p.call("hello") # => ["hello, nil]
p.call # => [nil, nil]
p.call("hello", "world", "bye") # => ["hello", "world"]

l.call("hello", "world") # => ["hello", "world"]
l.call("hello") # ArgumentError: wrong number of arguments (1 for 2)
l.call # ArgumentError: wrong number of arguments (0 for 2)
l.call("hello", "world", "bye") # ArgumentError: wrong number of arguments (3 for 2)


Вродебы все понятно, пусть немного и раздражает такое различие(казалось-бы один и тот-же блок), привыкнуть можно, но это только цветочки! :)
В предыдущем примере мы рассмотрели блоки у которых может быть 2 и более аргументов. По логике ничего не должно измениться если будет 1 или вообще не будет аргументов. Но так как жизнь сложная и нечестная штука, так и у руби есть свой взгляд на логику.

p = Proc.new { |i| i }
l = lambda { |i| i }

p.call("hello") # => "hello"
p.call("hello", "world") # => ["hello", "world"] # warning: multiple values for a block parameter (2 for 1)
p.call # => nil # warning: multiple values for a block parameter (0 for 1)

l.call("hello") # => "hello"
l.call("hello", "world") # => ["hello", "world"] # warning: multiple values for a block parameter (2 for 1)
l.call # => nil # warning: multiple values for a block parameter (0 for 1)


Как вам такой необычный поворот событий? Из предыдущего примера новичок осознал что существует 2 разных блока, и оба они по разному реагируют на аргументы, но после этого примера все перевернулось, теперь вместо ошибки новичок получил предупреждение. У любого человека который только начал изучать эту часть языка мозг загрузиться и зависнет. Но Matz и тут не дает расслабиться нашему мозгу. Идем дальше:

p = Proc.new { puts "нет аргументов" }
l = lambda { puts "нет аргументов" }

p.call # => nil
p.call("hello") # => nil

l.call # => nil
l.call("hello") # => nil


Заключительный штрих в разрыве всех шаблонов и правил логики. Тренинги по НЛП учат как разрывать шаблон чтобы получить доступ к подсознанию человека, это один из таких примеров. При отсутствии аргументов оба вида блоков реагируют одинаково.

Можно немножко подытожить. В руби существует 2 вида работы с аргументами — строгий и неопределенный(strict&loose). Методы всегда работают по принципу строгих аргументов, Proc блоки работают по принципу неопределенных аргументов, а вот lambda блоки работают в строгом режиме если аргументов больше 2 и в неопределенном режиме если аргументов нет или есть один единственный аргумент.

Существует еще одно отличие между блоками lambda и Proc. Это отличие кроется в return.
lambda блоки просто возвращают результат из блока, пример:

def max_element
l = lambda { return [1,2,3,4] }
array = l.call
return array.max
end
max_element # => 4


А Proc работает немного иначе, вместо того чтобы просто вернуть результат из блока, Proc возвращает результат в своей объявленной зоне, пример:

def max_element
p = Proc.new { return [1,2,3,4] }
array = p.call
return array.max # здесь был Proc
end
max_element # => [1,2,3,4]


Как видим до return array.max дело так и не дошло, так как блок p вызвал return до этого.
Руби прекрасный язык, дающий огромные возможности для творчества, но у него есть свои темные стороны, которые нужно знать :)
Tags:Ruby
Hubs: Ruby
Total votes 1: ↑1 and ↓0+1
Views37K

Popular right now

Top of the last 24 hours