- Сообщений: 247
- Спасибо получено: 537
Операторы в руби
11 года 8 мес. назад - 11 года 8 мес. назад #73056
от Iren_Rin
Iren_Rin создал тему: Операторы в руби
Сегодня я вам расскажу о том, чего на самом деле нет - о операторах в руби. Вы можете спросить: а как же +, -, * и т.п. Так вот, их нет. И ложки тоже нет кстати.
Есть только [strike]матрица[/strike] объекты. Методы - тоже объекты, а все эти операторы - на самом деле вызов методов.
Но не все так просто, как на первый взгляд
Поэтому в руби есть приоритет операторов, некоторые выполняются раньше, некоторые позже. Но я специально не буду вам о них рассказывать - математические операторы исполняются точно так, как мы привыкли в школе, - деление и умножение раньше чем сложение и т.п. Остальные приоритеты - совсем не очевидны, поэтому лучше их группировать при помощи скобок
В общем знайте, когда вы видите что то похожее на оператор - на самом деле это вызов метода и синтаксический сахар, которым так славен руби. Да они по-особому обрабатываются интерпретатором, но они остаются самыми настоящими методами, со всеми вытекающими.
Но что нам это дает? Возможность [strike]останавливать пули[/strike] определить \ переопределить операторы и получить отличный код.
Допустим нам нужно написать класс урона, ну вы знаете - "Алистер нанес скелету 20 единиц святого урона"
Уже хорошо, теперь нужно добавить атрибуты - для простоты оставим единицы и природу.
Почему я использовал attr_reader а не attr_accessor - я долго мучался, как же мне стоит писать, но в итоге решил - если предполагается установка атрибута из вне - использую attr_accessor, если нет - attr_reader и работаю с инстанс переменными (если мне нужно атрибут установить).
Теперь мы можем написать:
А сколько нанесли Алистер и Мориган вместе? Нужно добавить сложение!
Сложение не должно изменять текущий объект, поэтому создаем новый. Points в новом объекте будут равны сумме points в исходных объектах, nature - тут уже на выбор разработчика. На мой взгляд, если, у урона природа одна, то и nature для такого объекта должен возвращать собственно строку природы. Если же несколько - то от массива не уйдешь. Обратите внимание, как я использовал flatten, это мне позволяет не думать, сколько там природ у слагаемого. Метод в таком виде - пример утиной типизации - если в двух словах то нам не важно, какой объект передается методу, мы предполагаем что этот объект скорее всего принадлежит классу Damage "Если кто то крякает как утка и выглядит как утка - значит это утка" (c) - отсюда название такого подхода. Плюс тут в том, что кода меньше, ну а минус - иногда получаются такие веселые баги, что век не поймаешь. Поэтому я почти всегда стараюсь хоть как то валидировать то, что ко мне пришло в метод.
Это пример защитного программирования. Идея в том, что лучше сразу сообщить о ошибке, ведь чем раньше вы обнаружите ошибку - тем дешевле ее исправить.
Хорошо, а что если на цели дебафф, увеличивающий урон вдвое? А если бафф, уменьшающий урон?
Теперь мы можем сделать так:
Ну так кто круче, Алистер или Морриган? Кто нанес больше урона? Нам нужны методы сравнения >, <, <=, == и т.д. Для начала нужно решить, будет ли влиять природа на сравнение урона? Вспоминая свои годы жизни в WoW, я думаю что нет. Будем сравнивать только points. Мы можем сами определить каждый из этих методов сами, а можем пойти легким путем. Comparable - это миксин из стандартной библиотеки, включив его мы получаем сразу все методы сравнения в классе. Но мы должно определить последний на сегодня оператор <=> (Лодочка). Этот метод должен возвращать -1 когда объект меньше, чем предоставленный, 0 когда объекты равны, 1, когда больше. (Мы же пойдем совсем легким путем и просто вызовем <=> у атрибута points, ведь это число).
В итоге у нас получился полный класс:
И вот как мы его используем:
В общем всем теперь ясно, что Алистер круче, а Морриган желаю быть добрее.
Так же, определив методы равенства в нашем классе, мы получаем доступ к другим фишкам, которые на этих методах основываются. На ум приходят только массивы, но уверен что есть еще.
И для тех кто дочитал до сюда - бонус! Два самых мутных оператора в руби [] и []=, ведь аргументы для этих методов передаются по особому:
Это все, что я хотел сказать, спасибо за внимание!
Есть только [strike]матрица[/strike] объекты. Методы - тоже объекты, а все эти операторы - на самом деле вызов методов.
Code:
1 + 1 #=> 2
#То же самое, что и
1.+(1) #=> 2
1 * 2 #=> 2
#То же самое, что и
1.*(2) #=> 2
#и так далее
Code:
1 + 2 * 3 + 4
#это выражение нельзя представить в таком виде
1.+(2).*(3).+(4)
#правильно будет так
1.+(2.*(3)).+(4)
Code:
expression_one && expression_two || expression_three && expression_for
#не стоит пологаться, что кто то помнит что сильнее - && или ||. Используйте скобки!
В общем знайте, когда вы видите что то похожее на оператор - на самом деле это вызов метода и синтаксический сахар, которым так славен руби. Да они по-особому обрабатываются интерпретатором, но они остаются самыми настоящими методами, со всеми вытекающими.
Но что нам это дает? Возможность [strike]останавливать пули[/strike] определить \ переопределить операторы и получить отличный код.
Допустим нам нужно написать класс урона, ну вы знаете - "Алистер нанес скелету 20 единиц святого урона"
Code:
class Damage
end
Code:
class Damage
attr_reader :points, :nature
def initialize(points, nature)
@points, @nature = points, nature
end
end
Теперь мы можем написать:
Code:
alister_damage = Damage.new 20, 'holly'
morrigan_damage = Damage.new 40, 'fire'
Code:
#Дальше буду писать как будто я в теле класса.
def +(other)
Damage.new points + other.points, [nature, other.nature].flatten
end
Code:
def +(other)
check other, Damage
Damage.new points + other.points, [nature, other.nature].flatten
end
private
def check(value, klass)
unless value.is_a? klass
raise ArgumentError "expect #{value} to be a #{klass.name}"
end
end
Code:
party_damage = alister_damage + morrigan_damage
party_damage.points #=> 60
party_damage.nature #=> ['holly', 'fire']
Хорошо, а что если на цели дебафф, увеличивающий урон вдвое? А если бафф, уменьшающий урон?
Code:
def *(number)
check number, Numeric
Damage.new points * number, nature
end
def /(number)
check number, Numeric
Damage.new points / number, nature
end
Теперь мы можем сделать так:
Code:
party_damage = party_damage * 2
# или в сокращенной форме
party_damage *= 2
Ну так кто круче, Алистер или Морриган? Кто нанес больше урона? Нам нужны методы сравнения >, <, <=, == и т.д. Для начала нужно решить, будет ли влиять природа на сравнение урона? Вспоминая свои годы жизни в WoW, я думаю что нет. Будем сравнивать только points. Мы можем сами определить каждый из этих методов сами, а можем пойти легким путем. Comparable - это миксин из стандартной библиотеки, включив его мы получаем сразу все методы сравнения в классе. Но мы должно определить последний на сегодня оператор <=> (Лодочка). Этот метод должен возвращать -1 когда объект меньше, чем предоставленный, 0 когда объекты равны, 1, когда больше. (Мы же пойдем совсем легким путем и просто вызовем <=> у атрибута points, ведь это число).
Code:
include Comparable
def <=>(other)
check other, Damage
points <=> other.points
end
В итоге у нас получился полный класс:
ВНИМАНИЕ: Спойлер!
Code:
class Damage
include Comparable
attr_reader :points, :nature
def initialize(points, nature)
@points, @nature = points, nature
end
def +(other)
check other, Damage
Damage.new points + other.points, [nature, other.nature].flatten
end
def *(number)
check number, Numeric
Damage.new points * number, nature
end
def /(number)
check number, Numeric
Damage.new points / number, nature
end
def <=>(other)
check other, Damage
points <=> other.points
end
private
def check(value, klass)
unless value.is_a? klass
raise ArgumentError "expect #{value} to be a #{klass.name}"
end
end
end
И вот как мы его используем:
Code:
alister_damage = Damage.new 20, 'holly'
morrigan_damage = Damage.new 50, 'fire'
alister.kindness #=> 2 Алистер жутко добрый
alister_damage *= alister.kindness
alister_damage.points #=> 40
morrigan.malice #=> 2 та еще злюка эта Мориган
morrigan_damage /= morrigan.malice
morrigan_damage.points #=> 25
morrigan_damage >= alister_damage #=> false
alister_damage > morrigan_damage #=> true
alister_damage == morrigan_damage #=> false
party_damage = morrigan_damage + alister_damage
party_damage.points #=> 65
party_damage.nature #=> ['holly', 'fire']
Так же, определив методы равенства в нашем классе, мы получаем доступ к другим фишкам, которые на этих методах основываются. На ум приходят только массивы, но уверен что есть еще.
Code:
arr = [Damage.new(100, 'acid'), Damage.new(50, 'cold'), Damage.new(120, 'fire')]
arr.max.nature #=> 'fire'
arr.min.points #=> 50
arr.sort.map { |dmg| "#{dmg.nature} - #{dmg.points}" } #=> ['cold - 50', 'acid - 100', 'fire - 120']
И для тех кто дочитал до сюда - бонус! Два самых мутных оператора в руби [] и []=, ведь аргументы для этих методов передаются по особому:
ВНИМАНИЕ: Спойлер!
Code:
def [](arg1, arg2, arg3) #геттер, все что во время вызова окажется внутри квадратных скобок - будет аргументами для этого метода.
end
#вызвать его нужно так obj[arg1, arg2, arg3]
def []=(arg1, arg2, arg3) #сеттер, поступает с аргументами так же, как и метод сверху, за тем исключением, что последний аргумент передается после знака =
end
#вызывать его нужно так obj[args1, arg2] = arg3
#и чтобы совсем вас запутать, скажу, что если после знака = больше аргументов, чем один, они сгрузятся в массив
self[1, 2] = 3, 4, 5
#в нашем случае arg3 будет равен [3, 4, 5]
Это все, что я хотел сказать, спасибо за внимание!
Последнее редактирование: 11 года 8 мес. назад пользователем Iren_Rin.
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
11 года 8 мес. назад #73062
от Amphilohiy
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Amphilohiy ответил в теме Операторы в руби
Сначала говоришь, что операторов нет... А потом утверждаешь, что у оператор есть приоритет...
В целом согласен, что у Руби такой синтаксис, что в итоге перегрузка операторов выглядит как просто объявление метода в принципе, но отличия все же есть.
Как ты заметил - приоритет, обычные методы так не делают... вроде бы...
Нельзя определить метод plus и потом швыряться им как
Да, ты сказал, что синтаксический сахар, дело обычное, но все же есть набор методов, для которых позволен этот сахар. И есть они ни что иное как операторы.
Но чую, что ты имел ввиду, что они просто являются методами, и я тут ни за что на тебя напал
Пример, конечно, показательный, но немного пугает. Можно сложить гигантский урон не очень хорошего элемента, а затем впихнуть единичку, на которую у противника слабость... Но лучше чем обычные комплексные числа
А чтобы в итоге не выглядело, что я просто жалуюсь, то я дам ссыль на сайт с приоритетами, для ленящихся гуглить
Приоритеты
.
А вообще хотел бы придраться немного к контенту. Использование оператора <=> в методе max указано вскользь в коде. Можно было бы явнее описать использования таких операторов по умолчанию (если не задана другая функция) например при сортировке массива. Ну и обратить внимание на то, что можно подобные функции писать самому, опираясь на эти операторы, и они все равно будут довольно гибкими. /cheers
В целом согласен, что у Руби такой синтаксис, что в итоге перегрузка операторов выглядит как просто объявление метода в принципе, но отличия все же есть.
Как ты заметил - приоритет, обычные методы так не делают... вроде бы...
Нельзя определить метод plus и потом швыряться им как
Code:
foo plus bar
Но чую, что ты имел ввиду, что они просто являются методами, и я тут ни за что на тебя напал
Пример, конечно, показательный, но немного пугает. Можно сложить гигантский урон не очень хорошего элемента, а затем впихнуть единичку, на которую у противника слабость... Но лучше чем обычные комплексные числа
А чтобы в итоге не выглядело, что я просто жалуюсь, то я дам ссыль на сайт с приоритетами, для ленящихся гуглить
А вообще хотел бы придраться немного к контенту. Использование оператора <=> в методе max указано вскользь в коде. Можно было бы явнее описать использования таких операторов по умолчанию (если не задана другая функция) например при сортировке массива. Ну и обратить внимание на то, что можно подобные функции писать самому, опираясь на эти операторы, и они все равно будут довольно гибкими. /cheers
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Спасибо сказали: Iren_Rin
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
11 года 8 мес. назад #73064
от Iren_Rin
Iren_Rin ответил в теме Операторы в руби
1) Ну хотя бы с тем что Алистер круче Морриган ты согласен, это радует.
2) Я приследовал две цели - показать что операторы в руби это методы и то как их можно переопределить и использовать. Не вина метода + что интерпретатор может распознавать его вызов без точки
. Но это действительно самые настоящие методы, и это не перезагрузка операторов так выглядит, а самое настоящее определение самого настоящего метода. Ты можешь его получить при помощи method, увидеть в public_methods, вызвать при помощи __send__ и public_send, эти методы наследуются, подмешиваются и т.п. и т.д.
Я допишу это в начальный пост.
3) Перечитал примеры и понял что их недостаточно в части про <=>, добавлю в начальный пост (я просто хотел показать как подключив один миксин и определив один метод вы можете получить целый вкусный кусок функциональности за бесплатно).
4) Как ты мог заметить, мультипликаторы для урона Алистера и урона Морриган я применил до того как сложил их. Естественно, в такой реализации все мультипликаторы должны быть применены до складывания урона.
Спасибо за конструктивную критику!
2) Я приследовал две цели - показать что операторы в руби это методы и то как их можно переопределить и использовать. Не вина метода + что интерпретатор может распознавать его вызов без точки
Code:
1.public_send :+, 1 #=> 2
3) Перечитал примеры и понял что их недостаточно в части про <=>, добавлю в начальный пост (я просто хотел показать как подключив один миксин и определив один метод вы можете получить целый вкусный кусок функциональности за бесплатно).
4) Как ты мог заметить, мультипликаторы для урона Алистера и урона Морриган я применил до того как сложил их. Естественно, в такой реализации все мультипликаторы должны быть применены до складывания урона.
Спасибо за конструктивную критику!
Спасибо сказали: Amphilohiy
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
11 года 8 мес. назад - 11 года 8 мес. назад #73066
от Amphilohiy
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Amphilohiy ответил в теме Операторы в руби
Ну, сложение уронов штука сложная, т.к. должна происходить в контексте сопротивлений цели. Тут "операторы" не очень подходят (разве что сделать операторы с разными классами: существо - урон, или существо + урон как лечение. Смущает только то, что придется дублировать существо с иным значением хп, ибо менять значение операнда плохой, очень плохой тон), но не стоит из-за этого менять пример.
Я в основном к тому, что в тех же сях перегрузка оператора выглядит как определение функции, правда можно ли их потом вызвать через точку - не проверял. Но самое главное - я думаю что мы от самого термина "оператор" не откажемся, ибо имеет много синтаксических сходств с другими ЯП, да и Руби к ним относится по особому. Но посыл понял - сформировать более верное представление (что хоть операторы и особенные с виду, в итоге они просто методы с подходящим именем)
И, извини слоупока, есть же тема операторов [](key) и []=(key, value). Новичкам может быть непрозрачно, особенно с учетом продвинутого сахорка (значение ключа между именем функции). Но не факт, что потребуется, возможно они у нас смышленые
Я в основном к тому, что в тех же сях перегрузка оператора выглядит как определение функции, правда можно ли их потом вызвать через точку - не проверял. Но самое главное - я думаю что мы от самого термина "оператор" не откажемся, ибо имеет много синтаксических сходств с другими ЯП, да и Руби к ним относится по особому. Но посыл понял - сформировать более верное представление (что хоть операторы и особенные с виду, в итоге они просто методы с подходящим именем)
И, извини слоупока, есть же тема операторов [](key) и []=(key, value). Новичкам может быть непрозрачно, особенно с учетом продвинутого сахорка (значение ключа между именем функции). Но не факт, что потребуется, возможно они у нас смышленые
Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Последнее редактирование: 11 года 8 мес. назад пользователем Amphilohiy.
Спасибо сказали: Iren_Rin
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
11 года 8 мес. назад #73067
от Iren_Rin
Iren_Rin ответил в теме Операторы в руби
Добавил описание [] и []= как бонус в самом конце, они просто немного мутноваты
Спасибо сказали: Amphilohiy
Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.
Время создания страницы: 0.095 секунд
