Использование lambda в качестве локальных функций

    Наверняка вы сталкивались с ситуацией, когда есть достаточно жирный метод, и вам приходится вынести часть его кода в отдельный метод и ваш класс/модуль переполняется методами, которые относятся к одному единственному методу и нигде более не используется. Ужасный каламбур, правда?


    Если вы просто хотите ознакомиться с реализацией класса, то эти самые вспомогательные методы очень сильно мозолят глаза, приходится прыгать по коду туда-сюда. Да, конечно, можно разнести их по отдельным модулям, но я считаю, что зачастую это слишком избыточно (я, например, не хочу создавать модуль, который, по сути, определяет только один метод, декомпозированный на n частей). Особенно неприятно, когда эти вспомогательные функции состоят из одной строки (например, метод, который выдергивает определенный элемент из распарсенного JSON).


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


    Несколько синтетический пример


    Задача


    Сгенерировать Hash с курсом разных валют по отношению к рублю. Примерно такой:


    { 'USD' => 30.0,
      'EUR' => 50.0,
      ... }

    Решение


    На сайте Центробанка есть такая страница: http://www.cbr.ru/scripts/XML_daily.asp


    Собственно, все можно сделать вот так:


    require 'open-uri'
    require 'active_support/core_ext/hash' # for Hash#from_xml
    
    def rate_hash
      uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp')
      xml_with_currencies = uri.read
      rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute']
    
      rates.map(&method(:rate_hash_element)).to_h
    end
    
    def rate_hash_element(rate)
      [rate['CharCode'], rubles_per_unit(rate)]
    end
    
    def rubles_per_unit(rate)
      rate['Value'].to_f / rate['Nominal'].to_f
    end
    

    Либо классом:


    require 'open-uri'
    require 'active_support/core_ext/hash' # for Hash#from_xml
    
    class CentralBankExchangeRate
      def rubles_per(char_code)
        rate_hash_from_cbr[char_code] || fail('I dunno :C')
      end
    
      #
      # other public methods for other currencies
      #
    
      private
    
      # Gets daily rates from Central Bank of Russia
      def rate_hash_from_cbr
        uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp')
        xml_with_currencies = uri.read
        rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute']
    
        rates.map(&method(:rate_hash_element)).to_h
      end
    
      # helper method for #rate_hash_from_cbr
      def rate_hash_element(rate)
        [rate['CharCode'], rubles_per_unit[rate]]
      end
    
      # helper method for #rate_hash_element
      def rubles_per_unit(rate)
        rate['Value'].to_f / rate['Nominal'].to_f
      end
    
      #
      # other private methods
      #
    end

    Не будем рассуждать о том, какие библиотеки стоило использовать, будем считать, что у нас есть рельсы и поэтому воспользуемся Hash#from_xml оттуда.


    Собственно, нашу задачу решает метод #rate_hash, в то время как оставшиеся два метода являются вспомогательными для него. Согласитесь, что их присутствие очень сильно отвлекает.


    Обратите внимание на переменную xml_with_currencies: ее значение используется всего-лишь один раз, а это значит, что ее наличие совсем необязательно и можно было написать Hash.from_xml(uri.read)['ValCurs']['Valute'], однако, как мне кажется, ее использование чуть-чуть улучшает читаемость кода. Собственно, появление вспомогательных методов — это тот же самый прием, но для кусков кода.


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


    Решение с lambda


    require 'open-uri'
    require 'active_support/core_ext/hash' # for Hash#from_xml
    
    def rate_hash
      uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp')
      xml_with_currencies = uri.read
      rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute']
    
      rubles_per_unit = -> (r) { r['Value'].to_f / r['Nominal'].to_f }
      rate_hash_element = -> (r) { [r['CharCode'], rubles_per_unit[r]] }
    
      rates.map(&rate_hash_element).to_h
    end

    Либо классом:


    
    require 'open-uri'
    require 'active_support/core_ext/hash' # for Hash#from_xml
    
    class CentralBankExchangeRate
      def rubles_per(char_code)
        rate_hash_from_cbr[char_code] || fail('I dunno :C')
      end
    
      #
      # other public methods for other currencies
      #
    
      private
    
      # Gets daily rates from Central Bank of Russia
      def rate_hash_from_cbr
        uri = URI.parse('http://www.cbr.ru/scripts/XML_daily.asp')
        xml_with_currencies = uri.read
        rates = Hash.from_xml(xml_with_currencies)['ValCurs']['Valute']
    
        rubles_per_unit = ->(r) { r['Value'].to_f / r['Nominal'].to_f }
        rate_hash_element = ->(r) { [r['CharCode'], rubles_per_unit[r]] }
    
        rates.map(&rate_hash_element).to_h
      end
    
      #
      # other private methods
      #
    end

    Теперь визуально сразу видно, что у нас есть один метод, пригодный для использования. А если мы захотим погрузиться в его реализацию, то проблем с чтением также возникнуть не должно, поскольку объявления лямбд достаточно броские и понятные (спасибо синтаксическому сахару).


    Но ведь так нельзя!


    Насколько я знаю, в JavaScript справедливо является плохой практикой вкладывание функций друг в друга:


    function foo() {
      return bar();
    
      function bar() {
        return 'bar';
      }
    }

    Справедливо, потому что каждый раз при вызове foo() мы создаем функцию bar, а затем уничтожаем ее. Более того, параллельное выполнение нескольких foo() создаст 3 одинаковых функции, что еще и тратит память. upd. здесь наоборот пишут "feel free to use them"., так что я не прав.


    Но насколько критичен вопрос потребления лишних долей секунды для нашего метода? Лично я не вижу смысла ради выигрыша в полсекунды отказываться от разнообразных удобных конструкций. Например:


    some_list.each(&:method)

    Медлительнее, чем


    some_list.each { |e| e.method }

    Потому что в первом случае используется неявное приведение к Proc.


    К тому же, Ruby все-таки работает на сервере, а не клиенте, так что скорости там намного выше (хотя тут тоже можно поспорить, ведь сервер обслуживает множество людей, и потеря даже доли секунды в глобальном масштабе увеличивается до минут/часов/дней)


    И все же, что со скоростью?


    Давайте проведем отдаленный от реальности эксперимент.


    using_lambda.rb:


    N = 10_000_000
    
    def method(x)
      sqr = ->(x) { x * x }
      sqr[x]
    end
    
    t = Time.now
    N.times { |i| method(i) }
    puts "Lambda: #{Time.now - t}"
    

    using_method.rb:


    N = 10_000_000
    
    def method(x)
      sqr(x)
    end
    
    def sqr(x)
      x * x
    end
    
    t = Time.now
    N.times { |i| method(i) }
    puts "Method: #{Time.now - t}"

    Запуск:


    ~/ruby-test $ alias test-speed='ruby using_lambda.rb; ruby using_method.rb'
    ~/ruby-test $ rvm use 2.1.2; test-speed; rvm use 2.2.1; test-speed; rvm use 2.3.0; test-speed
    Using /Users/nondv/.rvm/gems/ruby-2.1.2
    Lambda: 11.564349
    Method: 1.523036
    Using /Users/nondv/.rvm/gems/ruby-2.2.1
    Lambda: 9.270079
    Method: 1.523763
    Using /Users/nondv/.rvm/gems/ruby-2.3.0
    Lambda: 9.254366
    Method: 1.333142

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


    Заключение


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


    Если, например, скорость не является настолько критичной, что нужно бороться за каждые милисекунды, а сам метод не вызывается по миллиону раз в секунду, можно ли пожертвовать скоростью в данном случае? А может, вы вообще считаете, что это не улучшает читаемость?


    К сожалению, приведенный пример не иллюстрирует наглядно смысл такого странного использования лямбд. Смысл появляется, когда есть класс с достаточно большим количеством приватных методов, большая часть которых используется в других приватных методах, причем, только единожды. Это по задумке должно облегчить понимание реализации работы отдельных методов класса, т. к. нет кучи def и end, а есть достаточно простые однострочные функции (-> (x) { ... })


    Спасибо, за уделенное время!


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


    1. Я не предлагаю заменять все приватные методы на лямбды. Я предлагаю заменять только очень простые однострочники, которые нигде более не используются, кроме как в нужном методе (причем сам метод, скорее всего, будет приватным).
    2. Более того, даже для простых однострочников нужно исходить из ситуации и использовать этот "прием" только если читаемость кода действительно улучшится и при этом проседание по скорости не будет сколько-нибудь значительным.
    3. Основной профит использования лямбд — сокращение кол-ва строк кода и визуальное выделение наиболее значимых частей кода (текстовый редактор одинаково подсвечивает главные и вспомогательные методы, а тут мы воспользуемся лямбдой).
    4. Выносить в лямбды желательно чистые функции

    UPD2.
    Кстати, в первом примере два вспомогательных метода можно объединить в один:


    
    def rate_hash_element(rate)
      rubles_per_unit = rate['Value'].to_f / rate['Nominal'].to_f
      [rate['CharCode'], rubles_per_unit]
    end
    

    UPD3. от 10.08.2016


    Оказывается, в ruby-style-guide упоминается этот прием. (Ссылка)[https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#no-nested-methods]

    Only registered users can participate in poll. Log in, please.

    Как вам идея?

    • 27.0%Нравится, возьму на заметку17
    • 15.9%Не нравится, но ничего ужасного не вижу10
    • 25.4%Выглядит уродливо даже без учета падения скорости16
    • 15.9%Потери скорости слишком велики10
    • 15.9%Затрудняюсь. Интересен результат10
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 51

      0
      Функции не нужно создавать на каждом вызове, из стоит кэшировать. Для особенно критичных к скорости мест может иметь смысл использование eval для генерации специальной функции с заинлайленными значениями. После прогрева такая функция будет по скорости не уступать обычной (это из V8, в руби не уверен в существовании прогрева).
        0

        Если честно, не совсем Вас понял.


        Я в посте привел простой пример кода на JS. Подобный шаблон я несколько раз встречал в проектах и это считается говнокодом по описанным в посте причинам (пруфы можно найти).


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


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

          +1
          Я думаю, что закешировать действительно можно, например привязав к классу:

          @@rubles_per_unit||= ->(r) { r['Value'].to_f / r['Nominal'].to_f }
          
            0

            Не подумал об этом. Возьму на заметку, спасибо!

        –1
        Тестировать как эти лямбды?
          +2

          Вы тестируете однострочные вспомогательные функции?


          def some_method
            # ...
            some_private_method
          end
          
          private
          
          def some_private_method
            some_private_method_2
            ...
          end
          ...
            0

            Добавил в пост тот же пример с валютами, но классом (тупо обернул). Так, вроде, понятнее, о чем я.

            0
            Можно вынести этот функционал в отдельный модуль и при помощи ключевого слова private указать где публичные методы, а где приватные.
              0

              смысл не в инкапсуляции, а в сокращении строк кода и вызуальном выделении основных методов (основных != публичных)


              Более того, мне кажется, что использовать лямбды имеет смысл для вспомогательных функций приватных методов (просто пример это не иллюстрирует).

                0

                Добавил в пост тот же пример с валютами, но классом (просто обернул). Так, вроде, более ясно, о чем я.

                0
                … давайте приведу несколько синтетический пример
                … Давайте проведем отдаленный от реальности эксперимент

                А какой смысл в таких примерах, неужели у вас нет нормального рабочего примера, не оторванного от реальности? Может, если примера нет, то и проблемы нет?)

                По-моему, тут есть неправильное разбиение на функции. В rubles_per_unit() надо передавать готовые значения. Тогда это будет не вспомогательная функция, а вполне рабочая, которую можно вызывать из других мест. А для map вполне естественно указывать лямбды, потому что без map это будет просто тело цикла.

                Пример (Ruby не знаю, поэтому на PHP, уж извините):
                class CentralBankExchangeRate
                {
                    public function function rate_hash()
                    {
                        $uri = URI::parse('http://www.cbr.ru/scripts/XML_daily.asp');
                        $xml_with_currencies = $uri->read();
                        $rates = Hash::from_xml($xml_with_currencies)['ValCurs']['Valute'];
                
                        $rate_hash = [];
                        foreach ($rates as $rate) {
                            $rate_hash[$rate['CharCode']] = $this->rubles_per_unit($rate['Value'], $rate['Nominal']);
                        }
                    }
                
                    public function rubles_per_unit($value, $nominal)
                    {
                        return ($value / $nominal);
                    }
                }
                
                  0

                  Есть рабочий пример, но по некоторым причинам я решил его не использовать. Более того, я не уверен, что использование этого в продакшне хорошая затея, именно поэтому и написал данный пост, чтобы посмотреть, что думают люди. В первую очередь волнует вопрос производительности, т.к. мои немногочисленные коллеги, вроде, положительно отнеслись к идее.


                  Относительно Вашего примера — тогда уж не rubles_per_unit, а value_per_unit;)
                  Просто хотелось продемонстрировать хоть сколько-нибудь ясно, что я предлагаю.
                  Пока что это не особо получилось, очевидно.


                  А вообще, неужели у Вас нет в практике случаев, когда вы пишите какой-нибудь приватный метод, а за ним паровозиком идут связанные именно с ним методы (вспомогательные функции)? Вот моя идея заключается в том, чтобы этот паровозик убрать и оставить только значимую логику выделенной.
                  Сами посудите, у Вас функция rubles_per_unit до безобразия проста, а азнимает аж 4 строки и бросается в глаза наравне с rate_hash, хотя вне нее, по сути, бесполезна (на самом деле, полезна, но будем считать, что она максимально специфична).

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


                    В данном примере это не особо заметно, но представьте, что у вас N-ое количество приватных методов, и за каждым из них следует вереница из вспомогательных специфичных исключительно для него однострочников. Это заставляет глаза разбегаться, и сам по себе код класса начинает воспринимается сложнее и выглядит очень монструозным
                      0
                      Согласен, мне тоже это не очень нравится. Но зачем использовать именно лямбды, если можно просто написать линейный код, и дать понятное название переменной с результатом вычисления?
                        0

                        Например, чтобы использовать их в функциях высшего порядка, как в примере:


                        rates.map(&rate_hash_element).to_h

                        Ситуации разные, иногда это даже ухудшает читаемость. Иногда же наоборот, повышает.
                        Ruby немного почерпнул из Perl. И поэтому TMTOWTDI. А из всех вариантов решения задачи нужно выбрать тот, который лучше всего вписывается в код. Я не предлагаю использовать эти лямбды повсеместно. Просто предлагаю взять это на заметку, как один из вариантов. Другое дело, если идея слишком уж неудачная (на это есть опрос).

                      0

                      Кстати, сам метод rubles_per_unit специфичен для объектов из xml ЦБ, при этом имя у него весьма абстрактное, так что использование лямбды в данном случае только подчеркивает его специфичность.

                    +1
                    Я вас полностью поддерживаю. Мне тоже нравится такой способ локального выноса куска функционала, и не только в руби.
                      0

                      А можете привести пару кусков кода для других языков?
                      Я, вроде, и понимаю, что можно использовать много где, но приходит на ум только CoffeeScript.

                        +1
                        На swift очень удобный синтаксис для работы с функциями. В данном случае лично для меня такой вынос делает код более красивым.
                        let fn: Result<FeedParsed> -> FeedParsed? = { result in
                            switch result {
                            case .Success(let result):
                                return result
                            case .Fail(let error):
                                print(error)
                                return nil
                            }
                        }
                                
                        self.completion(self.parserResults.flatMap(fn))
                        
                      0

                      Почему простой вариант не рассматривается даже? Все инкапсулированно, и лишние сущности не надо создавать.


                        rates.map do |x|
                          rate = x['Value'].to_f / x['Nominal'].to_f
                          [x['CharCode'], rate] 
                        end.to_h
                        0

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


                        Однако относительно Вашего куска кода:


                        • rate при наличии rates — не самое удачное имя для переменной
                        • Не совсем ясно, что этот rate обозначает, так что стоило назвать переменную хотя бы rubles_per_unit, как в посте
                        • Разнообразные куски кода, которые выделяются доп. отступом, усложняют понимание кода. В данном случае, это не заметно, если код будет большим и сложным, то эти куски кода еще и смешивают уровни абстракции (что не есть хорошо, если почитать Роберта Мартина, да Вы и сами это прекрасно понимаете, наверное)

                        Неужели Ваши 4 строки выглядят понятнее, чем эти три?


                        rubles_per_unit = ->(r) { r['Value'].to_f / r['Nominal'].to_f }
                        rate_hash_element = ->(r) { [r['CharCode'], rubles_per_unit[r]] }
                        
                        rates.map(&rate_hash_element).to_h
                          0

                          В вашем написании код приходится читать дважды, в разных направлениях. Сначала сверху вниз, потому что это императивный язык, потом снизу вверх, чтобы вникнуть в логику.


                          rate от exchange rate, так вроде переводится обменный курс. Можно 2 слова писать, если в команде возникают недопонимания. По-моему, это лучше чем rubles_per_unit, т.к. не привязано к валюте. Ничего страшного в связи с rates не вижу, users.each { |user| ... } можно же писать.

                            0
                            В вашем написании код приходится читать дважды, в разных направлениях


                            В случае небольшого скрипта это действительно может показаться странным. Хотя, например, в JS это не особо кого-то напрягает. Условно говоря:
                            var callback = function() { ... }
                            someFunction(callback);


                            Смысл моего метода как раз-таки в том, чтобы облегчить поверхностное чтение кода, без углубления в детали, если сравнивать с вариантом, когда все вспомогательные функции вынесены в методы. Когда вы начинаете погружаться в детали, вам в любом случае придется прыгать туда-сюда. Зачастую, с помощью поиска.
                            Ваш же вариант действительно выглядит корректно, однако, я его даже не рассматриваю (о, кажется, я Вас процитировал), потому что темой является именно замена однострочных методов на лямбды (наверняка ведь в Вашей практике встречались такие однострочники).
                            users.each { |user| ... } можно же писать


                            Можно, но, имхо, не самая удачная практика, ибо вы создаете две переменные с практически идентичными названиями. Одна опечатка и найти проблему может быть сложно. Лично я стараюсь либо во множественном числе писать user_list, либо, если это блок, в единственном писать по первой букве: users.each { |u| ... }.
                            По-моему, это лучше чем rubles_per_unit, т.к. не привязано к валюте


                            А я вот считаю, что именно по этой самой причине rubles_per_unit лучше, т.к. несет больше информации о содержимом (мы ведь разбираем конкретный xml с валютами по отношению к рублю).
                              +1
                              «var callback = function() {… }
                              someFunction(callback);»

                              Такой подход не особо рекомендуется разными best practices, всё же рекомендуют определять функции, благо в JS они имеют область видимости как у переменных, дополнительным плюсом является нормальное название их в утилитах для разработчиков + поиск по имени функции в IDE.
                                0

                                хм… действительно.
                                Мне почему-то казалось, что:


                                (function() {
                                  someFunction(callback);
                                
                                  function someFunction(callback) { callback(); }
                                  function callback() { console.log('callback'); }
                                })();

                                не выполнится.


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

                        0
                        А кто сказал, что «вкладывание функций друг в друга» в JS, как вы изволили выразиться, является плохой практикой, и кем это признано? Именно это «вкладывание» позволяет организовать замыкание, например, и много других вкусных вещей. А уж функциональный код на JS можно (и нужно) писать безо всяких лямбд. И с памятью и с производительностью при этом все в порядке, уверяю вас.
                          0

                          я знаю о замыканиях, уверяю Вас. В сниппете замыкание не используется (де факто), есть просто вкладывание.


                          Однако, я действительно оказался несколько голословен, ибо держал в голове вот это:
                          https://google.github.io/styleguide/javascriptguide.xml#Nested_functions


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


                          P.S. Погуглив на эту тему, нашел множество обсуждений на stackoverflow и вот эту статью:
                          http://code.tutsplus.com/tutorials/stop-nesting-functions-but-not-all-of-them--net-22315
                          но не уверен, что эо можно назвать авторитетным источником.

                            –1
                            Да с чего она падать-то будет, не могу постичь. Функция в JS — это такой же объект, как и все остальные, и даже наследуется от базового Object. С точки зрения компилятора, это то же самое, что объявить класс внутри класса (обычная практика в Java, например). А вот возможность возвратить из функции функцию, не вычисляя ее,- это уже ленивые вычисления, т.е. то, что повышает производительность.
                            И кстати, если говорить о лямбдах, то они вообще не про то, о чем вы написали. Лямбда — это всего лишь способ передать функции функцию в качестве аргумента, не более того. Зачем же их использовать в столь интенсивных вычислениях, как возведение в квадрат ряда натуральных чисел?
                            Теперь собственно о результатах вашего эксперимента. О чем он говорит? Что лямбда возводит натуральные числа в квадрат в 7 раз медленнее, чем обычный метод? С какой это стати? Вы считаете, что обычный метод так сильно оптимизирует возведение в квадрат? Если да, то я хочу знать этот наиболее оптимальный алгоритм вычисления функции y=x^2. Или у руби есть две разные математические библиотеки, одна для лямбд, а другая для обычных функций? И в это я готов поверить, если буду иметь результаты дебаггинга интерпретатора. Но лучше всего проводить подобные эксперименты, используя давно проверенные методы мат. статистики. По крайней мере, оценить количество фактического материала (данных), необходимое для получения более-менее достоверного результата.
                              0

                              Все очень просто.


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


                              Так понятнее, на что именно тратится время?


                              Сама операция возведения в квадрат была выбрана спонтанно. С тем же успехом можно было бы взять функцию идентичности.
                              В JS Вы столкнетесь с тем же самым. Попробуйте провести эксперимент для разных интерпретаторов, какие-то наверняка оптимизируют этот момент, но потеря скорости очевидна.


                              В руби, кстати, proc { |x| x} является сахаром по отношению к Proc.new { |x| x }, а лямбда — это как раз-таки объект Proc

                                0

                                Собственно, я сам провел эксперимент для функции идентичности.


                                N = 10_000_000
                                
                                def method(x)
                                  id = ->(x) { x }
                                  id[x]
                                end
                                
                                t = Time.now
                                N.times { |i| method(i) }
                                puts "Lambda: #{Time.now - t}"

                                в 6 раз медленнее, чем аналогичный код с методом.

                                0

                                И да. Мне всегда казалось, что лямбда — это объект-функция. Синтаксис -> (x) { x * x } является сахаром по отношению к lambda { |x| x * x }, который инициализирует объект класса Proc с некоторыми особенностями.


                                В JS же синтаксис function f(x) { return x * x } является сахаром по отношению к var f = function() { return x * x } (источник — Крокфорд), что по сути является присвоением лямбды локальной переменной.

                                  –1
                                  Хорошо, создаем объект-лямбду. Создание объекта — дорогостоящая операция во всех языках программирования, на всех платформах, все это знают. И если руби создает объект (не очень большой) столь медленно, то к черту руби. Это единственный вывод, который можно сделать по результатам вашего эксперимента. Но даже это является спорным. Чтобы утверждать это, нужен еще один эксперимент — по выделению и освобождению памяти для объектов руби различного размера и на разных платформах. Может быть, на какой-нибудь *BSD алгоритмы выделения памяти столь совершенны, что это ваше 7g-ускорение сойдет на нет тихо и незаметно.
                                    0

                                    ок, к черту руби.

                                  0

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

                                    0
                                    Да, проблема именно в способе оценивания. Вы проводите многофакторный эксперимент, или, говоря математическим языком, находитесь в многомерном пространстве признаков, а выдаете на-гора какой-то частный случай и требуете считать это результатом. Это все равно, что теорему Пифагора a^2+b^2=c^2 распространить на все натуральные степени и сказать: готово, ребята, великая теорема Ферма доказана.
                                      0

                                      Ну так я ведь в посте и написал, что эксперимент удален от реальности:)


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


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

                                        0
                                        Да, эксперимент нуждается в уточнении. Даже если отвлечься от выделения памяти и от руби. Например, некое подобие лямбды можно организовать и в С++, используя функторы. И что, эти лямбды будут работать в 7 раз медленнее, чем обычные методы классов? Я оооооочень сомневаюсь.Наоборот, я не удивлюсь, если они будут работать быстрее.
                                          0
                                          Вы что хотите доказать? Что С++ быстрее Ruby и кушает меньше памяти? Это факт.
                                          За синтаксический сахар надо платить производительностью и потреблением памяти, но в то же время мы получаем невероятное ускорение разработки и меньшее в разы количество кода, что благоприятно сказывается на количестве багов в коде.
                                          И среди интерпретируемых языков Ruby совсем не медленный.
                                          Да и задач где Ruby реально будет недоставать производительности на порядки меньше, чем тех где надо сделать побыстрее и в срок и эти узкие места можно написать на том же С++ и слинковать в Ruby.

                                          Проблема с производительностью у автора поста в том, что он лямбды перекомпилирует на каждом обращении к функции rate_hash и автору надо научиться пользоваться гемом benchmark, на моём компьютере с core i7 разница всего в 4 раза. А если лямбды объявить раньше, то разница на 33%. И автор как раз и пытается выяснить оправдано ли улучшение читаемости кода к ухудшению производительности. Мой опыт показывает что да, оправдано (правда способ автора мне не нравится, вот как выше printercu написал так мне понятнее).
                                            –1
                                            C++ я привел, чтобы показать, что идея эксперимента изначально некорректна, а именно некорректна постановка задачи. Сравнивать круг задач, для которых пригоден С++, а для которых руби — это, простите, какой-то моветон. Руби хорош на своих рельсах (что тоже спорно, для реализации MVC паттерна лучше взять какой-нибудь Backbone.js и горя не знать), да еще в каком-нибудь метасплойте, для обучения скрипт-кидди удаленно заливать шеллы на ни в чем не повинные серверы.
                                            О лямбдах. Да, на какой-то конкретной платформе руби-лямбды работают медленнее, чем руби-методы, и что с того? Вам это что-нибудь дает? Мне лично нет. К вопросу о читаемости кода: давайте сделаем то же самое на Java8, там тоже теперь есть лямбды. Но там вам не нужно будет мучительно выбирать между читаемостью и производительностью, вы получите и то, и другое, и почти задаром, так что вышеприведенный эксперимент вообще потеряет смысл. Проблема руби в его уродливом синтаксисе и системе классов, которые вообще не классы, а @#$% знает что. Отсюда и подобные эксперименты. Работай автор на той же Java8, я уверен, ему бы и в голову не пришло этим заниматься.
                                              +1
                                              «C++ я привел, чтобы показать, что идея эксперимента изначально некорректна, а именно некорректна постановка задачи.»
                                              Автор пытается рассуждать на тему: «что важнее читабельность кода или производительность?», в чём некорректность от использования в примерах Ruby?

                                              «Сравнивать круг задач, для которых пригоден С++, а для которых руби — это, простите, какой-то моветон»
                                              Они служат для разного класса задач и люди которые врываются их сравнивать в первую очередь должны задуматься, а нужны ли их сравнения. Могу вас удивить, но, в основном, люди которые пишут на Ruby, знают не только Ruby и они имеют представление о плюсах и минусах этого языка.

                                              «своих рельсах (что тоже спорно, для реализации MVC паттерна лучше взять какой-нибудь Backbone.js и горя не знать»
                                              Боюсь вам надо почитать про разницу между фронтендом и бэкендом.

                                              «Да, на какой-то конкретной платформе руби-лямбды работают медленнее, чем руби-методы, и что с того? Вам это что-нибудь дает? Мне лично нет.»
                                              Если вам это неинтересно, проходите мимо, автор правильно проставил тэги и есть люди которым это действительно интересно.

                                              «К вопросу о читаемости кода: давайте сделаем то же самое на Java8, там тоже теперь есть лямбды.»
                                              Ну давайте, напишите аналог этого кода экрана так на два… =) И запускать потом через пень колоду надо будет…
                                              Боюсь что С++, Java и другим языкам подобного плана нет смысла соревноваться в читаемости и компактности кода с Руби, у них другие преимущества.

                                              «Проблема руби в его уродливом синтаксисе и системе классов, которые вообще не классы, а @#$% знает что. Отсюда и подобные эксперименты»
                                              При чём тут это вообще не понятно, подобные эксперименты появляются от того что людям непонятно: что лучше 100500 функций в 1-2 строки, или императивный подход где код лежит в одном месте. И подобные мысли могут посещать программистов на любых языках программирования общего пользования.

                                              «Работай автор на той же Java8, я уверен, ему бы и в голову не пришло этим заниматься.»
                                              Как показывает мой жизненный опыт, самые любители померятся 3,14письками (производительностью) — это те кто программируют на Go, C/C++ и Java (и может быть это вполне логично, потому что на них решают задачи где как раз производительность крайне критична).
                                                0
                                                Как писали на заре ФИДО, слишкам многа букав (ниасилил). Вы, уважаемый, судя по всему, никогда не писали код С++ уровня предприятия, иначе бы вам пришлось постичь ВЕСЬ С++, а не только его какое-то подмножество (как показывает практика, неофиты обычно останавливаются на смарт-пойнтерах и забрасывают это дело, переходя на питон, руби и тому подобное). И тогда при взгляде на такие языки, как руби, у вас бы появлялись позывы к рвоте, вот прям как у меня сейчас. По поводу фронтенда и бекенда я отвечу в диалектическом ключе. Примем утверждение, что фронтенд — это перевернутый с ног на голову бекенд, и наоборот (мы не располагаем определениями этих понятий, а следовательно, по Канту, мы можем мыслить эти объекты лишь синтетически Тогда мы имеем полное право производить с ними подобные действия). Тогда, если вас (т.е. меня) тошнит от руби на бекенде (а также везде, где я вижу этот недокод с его недоклассами, недотипами и недометодами), я использую backbone на фронтенде и получаю тот же результат, и даже красивее, ибо js действительно красивый язык. Только не начинайте холивар еще и по этому поводу плз. Потому что если вы напишете, что руби тоже по-своему красивый язык, мне придется отослать вас еще и к учению Лейбница о монадах. (Это не те монады, которые используются в Haskell, но что-то общее есть.). А вообще весело у вас тут на хабре, мне нравится. Тема выеденного яйца не стоит, а такая буря в стакане. Класс!
                                      0
                                      Ваш эксперимент говорит не о том, что «использование лямбды медленнее, чем использование метода», а о том, что десять миллионов раз объект создать дороже, чем один раз.

                                      Определите константу, присвойте ей вашу лямбду и внезапно выясните, что лямбда быстрее.
                                        0

                                        Вы говорите о причине, а я о следствии (вы меня не просветили, комментарием выше я этот момент уточнил уже)


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

                                          0
                                          Чего-чего я нарушу?

                                          У меня в каждом пухлом классе есть вложенный приватный класс, который и определяет все эти константы. Расположен в самом конце файла, глаза никому не мозолит.

                                          Просветил ли я вас, нет ли, — не имеет никакого значения. Ваш вопрос на самом деле звучит так: «имеет ли смысл выполнить операцию сто раз вместо одного, если это [на мой взгляд] улучшает читаемость». На этот вопрос ответ всегда «нет», и лямбды тут вообще ни при чем. Если же вы хотите предметно поговорить про лямбды — то ответ как раз обратный: да, лямбдами вместо методов пользоваться можно и нужно: это [незначительно] ускоряет код и, главное, делает его гораздо более читаемым.

                                +2
                                Не совсем понятно, зачем одну строчку кода, которая используется один раз, выносить в лямбда. Если это независимая операция, значит место ей в методе. Если она работает только в контексте другого метода, почему не просто строчка кода, которая возвращает результат в переменную? Я делал как вы, лямбда, нужные только в пределах метода, но они использовались по несколько раз.
                                  0

                                  О, странно, Ваш комментарий появился как-то поздновато.


                                  Повторю то, что написал в ЛС:


                                  Я придумал очень неудачный пример. А так да, кейс использования лямбд именно такой же, как и у Вас. Так что мне приятно слышать, что кто-то этим уже и так пользуется.


                                  Спасибо!

                                    0
                                    Е, нет, стоп.
                                    и вам приходится вынести часть его кода в отдельный метод и ваш класс/модуль переполняется методами, которые относятся к одному единственному методу и нигде более не используется

                                    и
                                    Особенно неприятно, когда эти вспомогательные функции состоят из одной строки

                                    это не примеры, а заглавная часть статьи, теория, так сказать. Именно к «одной строки», и «к единственному методу» у меня был вопрос. Может лямбда выглядит и более опрятно, но одна строка, которая используется один раз, причем она не делает независимую операцию (getAuthorId может состоять с одной строки, но операция определенно самостоятельная, впрочем, как и публичная), не имеет на мой взгляд смысла. А многократное использование — это совсем другое дело :)
                                  0
                                  Создание лямбды с последующей передачей в map не является идиоматическим подходом в Ruby. «Решение с lambda» привычнее переписать так, и не только потому, что лямбда может работать медленнее, чем блок:

                                  rates.map{ |r| [r['CharCode'], r['Value'].to_f / r['Nominal'].to_f] }.to_h
                                    0

                                    Выше уже демонстрировалось подобное решение (кстати, написано оно лучше, чем у Вас) и я ответил по этому поводу.
                                    Вот эта ветка: https://habrahabr.ru/post/303594/#comment_9663324

                                      0
                                      На вкус и цвет… Перед тем, как отправить, я прочитал это решение. И всё же, в данном конкретном случае предпочитаю написать более короткий «однострочник».
                                        0

                                        "лучше" — потому что информативнее. Про внешний вид я не спорю, тут уж кому как.

                                  Only users with full accounts can post comments. Log in, please.