В вашем написании код приходится читать дважды, в разных направлениях
В случае небольшого скрипта это действительно может показаться странным. Хотя, например, в JS это не особо кого-то напрягает. Условно говоря:
var callback = function() { ... }
someFunction(callback);
Смысл моего метода как раз-таки в том, чтобы облегчить поверхностное чтение кода, без углубления в детали, если сравнивать с вариантом, когда все вспомогательные функции вынесены в методы. Когда вы начинаете погружаться в детали, вам в любом случае придется прыгать туда-сюда. Зачастую, с помощью поиска.
Ваш же вариант действительно выглядит корректно, однако, я его даже не рассматриваю (о, кажется, я Вас процитировал), потому что темой является именно замена однострочных методов на лямбды (наверняка ведь в Вашей практике встречались такие однострочники).
users.each { |user| ... } можно же писать
Можно, но, имхо, не самая удачная практика, ибо вы создаете две переменные с практически идентичными названиями. Одна опечатка и найти проблему может быть сложно. Лично я стараюсь либо во множественном числе писать user_list, либо, если это блок, в единственном писать по первой букве: users.each { |u| ... }.
По-моему, это лучше чем rubles_per_unit, т.к. не привязано к валюте
А я вот считаю, что именно по этой самой причине rubles_per_unit лучше, т.к. несет больше информации о содержимом (мы ведь разбираем конкретный xml с валютами по отношению к рублю).
Ну так я ведь в посте и написал, что эксперимент удален от реальности:)
Смысл его в том, чтобы показать, что использование лямбд заметно проигрывает по скорости использованию методов (хотя это, вроде, и так очевидно, но, судя по Вашим комментариям выше, не совсем).
И да, я понимаю, что это плохой эксперимент. Но смысл метода в первую очередь в попытке улучшить читаемость кода. И при этом я делаю акцент на том, что еще и потеря в производительности будет.
Я бы с радостью провел эксперимент поточнее, если бы знал, как это лучше сделать. Я не особо интересуюсь вопросами быстродействия. просто стараюсь, чтобы все было в рамках разумного. Однако у меня не хватает компетентности (признаю), чтобы грамотно оценивать скорость работы подобных вещей.
А мой эксперимент более чем красноречиво говорит о том, что использование лямбды медленнее, чем использование метода. Не понимаю, к чему Вы придираетесь. Да, мой способ оценивания, мягко говоря, кустарный, но зато наглядный. Думаю, очевидно, что операция возведения в квадрат выполняется за фиксированное время.
И да. Мне всегда казалось, что лямбда — это объект-функция. Синтаксис -> (x) { x * x } является сахаром по отношению к lambda { |x| x * x }, который инициализирует объект класса Proc с некоторыми особенностями.
В JS же синтаксис function f(x) { return x * x } является сахаром по отношению к var f = function() { return x * x } (источник — Крокфорд), что по сути является присвоением лямбды локальной переменной.
Каждый раз, когда мы вызываем этот метод, мы создаем новый объект-лямбду. Т.е. он инициализируется, под него выделяется память, а потом он уничтожается.
Так понятнее, на что именно тратится время?
Сама операция возведения в квадрат была выбрана спонтанно. С тем же успехом можно было бы взять функцию идентичности.
В JS Вы столкнетесь с тем же самым. Попробуйте провести эксперимент для разных интерпретаторов, какие-то наверняка оптимизируют этот момент, но потеря скорости очевидна.
В руби, кстати, proc { |x| x} является сахаром по отношению к Proc.new { |x| x }, а лямбда — это как раз-таки объект Proc
и был уверен, что там предлагают от этого отказаться. Уж не знаю, с чего я взял, что это считают говнокодом, но производительность действительно падает.
Пример синтетический, поэтому дискретизация выглядит несколько избыточно.
Однако относительно Вашего куска кода:
rate при наличии rates — не самое удачное имя для переменной
Не совсем ясно, что этот rate обозначает, так что стоило назвать переменную хотя бы rubles_per_unit, как в посте
Разнообразные куски кода, которые выделяются доп. отступом, усложняют понимание кода. В данном случае, это не заметно, если код будет большим и сложным, то эти куски кода еще и смешивают уровни абстракции (что не есть хорошо, если почитать Роберта Мартина, да Вы и сами это прекрасно понимаете, наверное)
Неужели Ваши 4 строки выглядят понятнее, чем эти три?
Кстати, сам метод rubles_per_unit специфичен для объектов из xml ЦБ, при этом имя у него весьма абстрактное, так что использование лямбды в данном случае только подчеркивает его специфичность.
Например, чтобы использовать их в функциях высшего порядка, как в примере:
rates.map(&rate_hash_element).to_h
Ситуации разные, иногда это даже ухудшает читаемость. Иногда же наоборот, повышает.
Ruby немного почерпнул из Perl. И поэтому TMTOWTDI. А из всех вариантов решения задачи нужно выбрать тот, который лучше всего вписывается в код. Я не предлагаю использовать эти лямбды повсеместно. Просто предлагаю взять это на заметку, как один из вариантов. Другое дело, если идея слишком уж неудачная (на это есть опрос).
Вот моя идея заключается в том, чтобы этот паровозик убрать и оставить только значимую логику выделенной.
В данном примере это не особо заметно, но представьте, что у вас N-ое количество приватных методов, и за каждым из них следует вереница из вспомогательных специфичных исключительно для него однострочников. Это заставляет глаза разбегаться, и сам по себе код класса начинает воспринимается сложнее и выглядит очень монструозным
Есть рабочий пример, но по некоторым причинам я решил его не использовать. Более того, я не уверен, что использование этого в продакшне хорошая затея, именно поэтому и написал данный пост, чтобы посмотреть, что думают люди. В первую очередь волнует вопрос производительности, т.к. мои немногочисленные коллеги, вроде, положительно отнеслись к идее.
Относительно Вашего примера — тогда уж не rubles_per_unit, а value_per_unit;)
Просто хотелось продемонстрировать хоть сколько-нибудь ясно, что я предлагаю.
Пока что это не особо получилось, очевидно.
А вообще, неужели у Вас нет в практике случаев, когда вы пишите какой-нибудь приватный метод, а за ним паровозиком идут связанные именно с ним методы (вспомогательные функции)? Вот моя идея заключается в том, чтобы этот паровозик убрать и оставить только значимую логику выделенной.
Сами посудите, у Вас функция rubles_per_unit до безобразия проста, а азнимает аж 4 строки и бросается в глаза наравне с rate_hash, хотя вне нее, по сути, бесполезна (на самом деле, полезна, но будем считать, что она максимально специфична).
В Ruby оператор switch (который case) работает по-особенному.
На объектах есть перегружаемый оператор ===, который в книге Мацумото называется case-equality operator, созданный специально для него.
Собственно:
case(object)
when cond1 then 1
when cond2 then 2
end
эквивалентно:
if cond1 === object
1
elsif cond2 === object
2
end
Эта часть языка мне не нравится, поскольку может ввести в заблуждение (и вводит!).
Поэтому лично я предпочитаю использовать его форму без параметра (где не используется ===) ради простых веток:
case
when then something1
when cond2 then something2
when cond3 then something3
end
vs
if cond1
something1
elsif cond2
something2
elsif cond3
something3
end
Я в посте привел простой пример кода на JS. Подобный шаблон я несколько раз встречал в проектах и это считается говнокодом по описанным в посте причинам (пруфы можно найти).
Вся моя идея заключается в том, чтобы принести в жертву немного скорости для того, чтобы код стал более читаемым.
Если вы бегло посмотрите на первый пример, то Вам сразу же в глаза бросятся три метода (можно даже не читать их названия, сама суть, что их три, и два из них по сути бесполезны для читающего), а во втором случае — всего-лишь один.
В случае JavaScript подобное вкладывание, как мне кажется, используется для того, чтобы инкапсулировать функции. В моем случае смысл исключительно во внешнем виде текста (кода).
В случае небольшого скрипта это действительно может показаться странным. Хотя, например, в JS это не особо кого-то напрягает. Условно говоря:
Смысл моего метода как раз-таки в том, чтобы облегчить поверхностное чтение кода, без углубления в детали, если сравнивать с вариантом, когда все вспомогательные функции вынесены в методы. Когда вы начинаете погружаться в детали, вам в любом случае придется прыгать туда-сюда. Зачастую, с помощью поиска.
Ваш же вариант действительно выглядит корректно, однако, я его даже не рассматриваю (о, кажется, я Вас процитировал), потому что темой является именно замена однострочных методов на лямбды (наверняка ведь в Вашей практике встречались такие однострочники).
Можно, но, имхо, не самая удачная практика, ибо вы создаете две переменные с практически идентичными названиями. Одна опечатка и найти проблему может быть сложно. Лично я стараюсь либо во множественном числе писать
user_list, либо, если это блок, в единственном писать по первой букве:users.each { |u| ... }.А я вот считаю, что именно по этой самой причине
rubles_per_unitлучше, т.к. несет больше информации о содержимом (мы ведь разбираем конкретный xml с валютами по отношению к рублю).Ну так я ведь в посте и написал, что эксперимент удален от реальности:)
Смысл его в том, чтобы показать, что использование лямбд заметно проигрывает по скорости использованию методов (хотя это, вроде, и так очевидно, но, судя по Вашим комментариям выше, не совсем).
И да, я понимаю, что это плохой эксперимент. Но смысл метода в первую очередь в попытке улучшить читаемость кода. И при этом я делаю акцент на том, что еще и потеря в производительности будет.
Я бы с радостью провел эксперимент поточнее, если бы знал, как это лучше сделать. Я не особо интересуюсь вопросами быстродействия. просто стараюсь, чтобы все было в рамках разумного. Однако у меня не хватает компетентности (признаю), чтобы грамотно оценивать скорость работы подобных вещей.
ок, к черту руби.
Собственно, я сам провел эксперимент для функции идентичности.
в 6 раз медленнее, чем аналогичный код с методом.
А мой эксперимент более чем красноречиво говорит о том, что использование лямбды медленнее, чем использование метода. Не понимаю, к чему Вы придираетесь. Да, мой способ оценивания, мягко говоря, кустарный, но зато наглядный. Думаю, очевидно, что операция возведения в квадрат выполняется за фиксированное время.
И да. Мне всегда казалось, что лямбда — это объект-функция. Синтаксис
-> (x) { x * x }является сахаром по отношению кlambda { |x| x * x }, который инициализирует объект класса Proc с некоторыми особенностями.В JS же синтаксис
function f(x) { return x * x }является сахаром по отношению кvar f = function() { return x * x }(источник — Крокфорд), что по сути является присвоением лямбды локальной переменной.Все очень просто.
Каждый раз, когда мы вызываем этот метод, мы создаем новый объект-лямбду. Т.е. он инициализируется, под него выделяется память, а потом он уничтожается.
Так понятнее, на что именно тратится время?
Сама операция возведения в квадрат была выбрана спонтанно. С тем же успехом можно было бы взять функцию идентичности.
В JS Вы столкнетесь с тем же самым. Попробуйте провести эксперимент для разных интерпретаторов, какие-то наверняка оптимизируют этот момент, но потеря скорости очевидна.
В руби, кстати,
proc { |x| x} является сахаром по отношению кProc.new { |x| x }, а лямбда — это как раз-таки объект Procя знаю о замыканиях, уверяю Вас. В сниппете замыкание не используется (де факто), есть просто вкладывание.
Однако, я действительно оказался несколько голословен, ибо держал в голове вот это:
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
но не уверен, что эо можно назвать авторитетным источником.
Пример синтетический, поэтому дискретизация выглядит несколько избыточно.
Однако относительно Вашего куска кода:
rateпри наличииrates— не самое удачное имя для переменнойrateобозначает, так что стоило назвать переменную хотя быrubles_per_unit, как в постеНеужели Ваши 4 строки выглядят понятнее, чем эти три?
Кстати, сам метод rubles_per_unit специфичен для объектов из xml ЦБ, при этом имя у него весьма абстрактное, так что использование лямбды в данном случае только подчеркивает его специфичность.
Например, чтобы использовать их в функциях высшего порядка, как в примере:
Ситуации разные, иногда это даже ухудшает читаемость. Иногда же наоборот, повышает.
Ruby немного почерпнул из Perl. И поэтому TMTOWTDI. А из всех вариантов решения задачи нужно выбрать тот, который лучше всего вписывается в код. Я не предлагаю использовать эти лямбды повсеместно. Просто предлагаю взять это на заметку, как один из вариантов. Другое дело, если идея слишком уж неудачная (на это есть опрос).
А можете привести пару кусков кода для других языков?
Я, вроде, и понимаю, что можно использовать много где, но приходит на ум только CoffeeScript.
В данном примере это не особо заметно, но представьте, что у вас N-ое количество приватных методов, и за каждым из них следует вереница из вспомогательных специфичных исключительно для него однострочников. Это заставляет глаза разбегаться, и сам по себе код класса начинает воспринимается сложнее и выглядит очень монструозным
Есть рабочий пример, но по некоторым причинам я решил его не использовать. Более того, я не уверен, что использование этого в продакшне хорошая затея, именно поэтому и написал данный пост, чтобы посмотреть, что думают люди. В первую очередь волнует вопрос производительности, т.к. мои немногочисленные коллеги, вроде, положительно отнеслись к идее.
Относительно Вашего примера — тогда уж не rubles_per_unit, а value_per_unit;)
Просто хотелось продемонстрировать хоть сколько-нибудь ясно, что я предлагаю.
Пока что это не особо получилось, очевидно.
А вообще, неужели у Вас нет в практике случаев, когда вы пишите какой-нибудь приватный метод, а за ним паровозиком идут связанные именно с ним методы (вспомогательные функции)? Вот моя идея заключается в том, чтобы этот паровозик убрать и оставить только значимую логику выделенной.
Сами посудите, у Вас функция rubles_per_unit до безобразия проста, а азнимает аж 4 строки и бросается в глаза наравне с rate_hash, хотя вне нее, по сути, бесполезна (на самом деле, полезна, но будем считать, что она максимально специфична).
Добавил в пост тот же пример с валютами, но классом (просто обернул). Так, вроде, более ясно, о чем я.
Добавил в пост тот же пример с валютами, но классом (тупо обернул). Так, вроде, понятнее, о чем я.
смысл не в инкапсуляции, а в сокращении строк кода и вызуальном выделении основных методов (основных != публичных)
Более того, мне кажется, что использовать лямбды имеет смысл для вспомогательных функций приватных методов (просто пример это не иллюстрирует).
Вы тестируете однострочные вспомогательные функции?
Простите за некропостинг, но все же.
В Ruby оператор switch (который case) работает по-особенному.
На объектах есть перегружаемый оператор ===, который в книге Мацумото называется case-equality operator, созданный специально для него.
Собственно:
эквивалентно:
Эта часть языка мне не нравится, поскольку может ввести в заблуждение (и вводит!).
Поэтому лично я предпочитаю использовать его форму без параметра (где не используется ===) ради простых веток:
vs
Если честно, не совсем Вас понял.
Я в посте привел простой пример кода на JS. Подобный шаблон я несколько раз встречал в проектах и это считается говнокодом по описанным в посте причинам (пруфы можно найти).
Вся моя идея заключается в том, чтобы принести в жертву немного скорости для того, чтобы код стал более читаемым.
Если вы бегло посмотрите на первый пример, то Вам сразу же в глаза бросятся три метода (можно даже не читать их названия, сама суть, что их три, и два из них по сути бесполезны для читающего), а во втором случае — всего-лишь один.
В случае JavaScript подобное вкладывание, как мне кажется, используется для того, чтобы инкапсулировать функции. В моем случае смысл исключительно во внешнем виде текста (кода).