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

    Многие любят руби за его красоту и гибкость. Но некоторые не догадываются о темной стороне языка 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 до этого.
    Руби прекрасный язык, дающий огромные возможности для творчества, но у него есть свои темные стороны, которые нужно знать :)
    Поделиться публикацией

    Похожие публикации

    Комментарии 13
      0
      Не этим ли навеяна «тёмная сторона»
      blog.extracheese.org/2010/02/python-vs-ruby-a-battle-to-the-death.html
      0
      “I found lambda!” — Matz
      Спасибо вам, уважаемый — держите плюс в карму:)
      Жаль, что не затронута тема нового в Ruby 1.9.1 синтексиса ламбды…
      Было:
      c = lambda {|a, b| [b, a] }
      c.call(1, 2)
      Стало:
      c = ->(a, b) { [b, a] }
      c.(1,2)
        0
        Благодарю :) Если честно, на руби 1.9 еще не переходил даже, после некоторых проблем с кодировками, решил повременить :)
        Но на сколько мне известно, в новых рубях нету всей этой запутанности с лямбдами и проками.
        0
        кстати, yugui.jp/articles/846 очередная мозговыносная статья для рубиста :)) Чем больше познаю язык, тем он красивее :))))
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Несколько странно выглядит сравнение методов и proc-объектов. Дело в том, что методы в руби не являются обектами. Их конечно можно преобразовать к proc — но это уже другая история.
            Статья несомненно интересная, большое спасибо.

            P.S. proc-объекты в ruby1.9.X стали умнее, взять хотя бы те же умолчательные значения для передаваемых параметров :)
              0
              Дело в том, что методы в руби не являются обектами. Их конечно можно преобразовать к proc — но это уже другая история.

              Должно быть вы путаете с блоками. Методы вполне себе могут быть объектами класса Method, а как раз блоки только преобразовывать в proc.
                0
                Я говорил, о методах, но как выходит ошибался. Действительно есть класс Method, но метод new у него отсутствует.

                ruby-1.9.2-p0 > Method.new
                NoMethodError: undefined method `new' for Method:Class
                	from (irb):15
                	from /home/saks/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
                </ore>
                
                Что немного смущает...
                  0
                  >> class MyClass
                  >>   def initialize(value)
                  >>     @x = value
                  >>   end
                  >>   def my_method
                  >>     @x
                  >>   end
                  >> end
                  => nil
                  
                  >> obj = MyClass.new(1)
                  => #<MyClass:0x00000001cdb210 @x=1>
                  >> m = obj.method :my_method
                  => #<Method: MyClass#my_method>
                  >> m.class
                  => Method
                  >> m.call
                  => 1
                  >> 
                  

                  Method.new и не нужен.
                    0
                    При таком объявлении — не нужен, но было бы логично и ожидаемо, если бы методы можно было создавать
                    при помощи метода new, а потом например привязывать к объектам, классам (как методы класса) или вызывать в контексте какого-либо объекта, как например в javascript с помощью call и apply это делается.

                    А так получается, что есть ключевое слово def (вроде это именно ключевое слово, не оператор), и есть методы define_method / define_singleton_method… Ну можно через eval ещё создать динамически метод, но это не вариант.

                    И получается, что методы то объекты, но вот создаются — не как все, а как-то по особенному, что не соответствует принципу наименьшего удивления. Хотя ruby пытаются делать как можно более логичным.
              0
              блоки и Процедуры и лямбды — это разные вещи. Блок не является объектом, это просто код заключенный в do...end или {}, а процедуры и лямды являются объектами, которые состоят из принимаемых при создании нового экземпляра класса Proc блоков.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое