Метод Ruby унарной тильды ('~')
Я работал в REPL и нашел очень интересное поведение: метод тильды.
Кажется, синтаксис Ruby имеет встроенный литеральный унарный оператор, ~
, просто сидящий.
Это означает, что ~Object.new
отправляет сообщение ~
экземпляру Object
:
class Object
def ~
puts 'what are you doing, ruby?'
end
end
~Object.new #=> what are you doing, ruby?
Это кажется действительно крутым, но загадочным. Действительно ли Мац пытается дать нам собственный настраиваемый унарный оператор?
Единственная ссылка, которую я могу найти в rubydocs, находится в примечаниях о приоритете оператора, где она была ранжирована как оператор с наивысшим приоритетом номер один !
и unary +
Это имеет смысл для унарных операторов. (Для интересных ошибок относительно следующих двух уровней приоритета **
затем unary -
, проверьте этот вопрос.) Кроме того, не упоминается об этой утилите.
Две заметные ссылки на этот оператор можно найти путем поиска среди вопросов ~=,
,! ~ , and
~>, это и это. Они оба отмечают свою полезность, странность и безвестность, не вдаваясь в ее историю.
После того, как я собирался списать ~
как классный способ предоставить пользовательское поведение унарного оператора для ваших объектов, я нашел место, где его фактически использовали в ruby - fixnum (integers).
~2
возвращает -3
. ~-1
возвращает 1
. Таким образом, он отрицает целое число и вычитает один... по какой-то причине?
Может ли кто-нибудь просветить меня как цель уникального и неожиданного поведения тильды в рубине?
Ответы
Ответ 1
Использование pry для проверки метода:
show-method 1.~
From: numeric.c (C Method):
Owner: Fixnum
Visibility: public
Number of lines: 5
static VALUE
fix_rev(VALUE num)
{
return ~num | FIXNUM_FLAG;
}
Хотя это непроницаемая для меня, это побудило меня искать C UNARY ~
оператора. Один существует: он побитовый оператор NOT, который переворачивает биты двоичного целого (~1010
=> 0101
). По какой-то причине это переводит на один меньше, чем отрицание десятичного целого в Ruby.
Что еще более важно, поскольку ruby является объектно-ориентированным языком, правильным способом кодирования поведения ~0b1010
является определение метода (пусть его называют ~
), который выполняет побитовое отрицание на двоичном целочисленном объекте. Чтобы понять это, рубиновый парсер (это все предположение здесь) должен интерпретировать ~obj
для любого объекта как obj.~
, Поэтому вы получаете унарный оператор для всех объектов.
Это просто догадка, кто-нибудь с более авторитетным или разъясняющим ответом, пожалуйста, просветите меня!
--РЕДАКТИРОВАТЬ--
Как @7stud указует, Regexp
класс использует его, а, по существу, соответствие регулярного выражения против $_
, последней строки, полученной gets
в текущей области.
Как указывает @Daiku, побитовое отрицание Fixnum
также документировано.
Я думаю, что мое объяснение парсера решает больший вопрос о том, почему Ruby позволяет ~
как глобальный унарный оператор, который вызывает Object#~
.
Ответ 2
Для fixnum
это одно дополнение, которое в двоичном fixnum
переворачивает все единицы и нули в противоположное значение. Здесь документ: http://www.ruby-doc.org/core-2.0/Fixnum.html#method-i-7E. Чтобы понять, почему он дает значения, которые он делает в ваших примерах, вам нужно понять, как отрицательные числа представлены в двоичном формате. Почему Ruby предоставляет это, я не знаю. Два дополнения, как правило, используются в современных компьютерах. Преимущество состоит в том, что одни и те же правила для основных математических операций работают как для положительных, так и для отрицательных чисел.
Ответ 3
~
- это двоичный оператор с одним дополнением в Ruby. Одним из дополнений является просто перевертывание битов числа, что теперь число арифметически отрицательно.
Например, 2 в 32-битном двоичном формате Fixnum 0000 0000 0000 0010, поэтому ~ 2 будет равно 1111 1111 1111 1101 в одном дополнении.
Однако, как вы заметили, и эта статья обсуждается более подробно, версия Ruby одного дополнения, похоже, реализована по-разному, поскольку она не только делает целое отрицательное число, но и вычитает из него 1. Я понятия не имею, почему это так, но похоже, что это так.
Ответ 4
Он упоминается в нескольких местах в кирку 1.8, например, в классе String. Однако в рубине 1.8.7 он не работает в классе String, как рекламируется. Он работает для класса Regexp:
print "Enter something: "
input = gets
pattern = 'hello'
puts ~ /#{pattern}/
--output:--
Enter something: 01hello
2
Предполагается, что он будет работать аналогично для класса String.
Ответ 5
- ~ (Бигнум)
- ~ (Сложный)
- ~ (Fixnum)
- ~ (Регулярное выражение)
- ~ (IPAddr)
- ~ (Целое число)
- ~ (Регулярное выражение)
Каждый из них задокументирован в документации.
Этот список взят из документации по Ruby 2.6
Поведение этого метода "в целом" в основном такое, как вы хотите, как вы описали, определив метод с именем ~
в классе Object. Поведения в основных классах, которые определяют его сопровождающие реализации, кажутся достаточно хорошо задокументированными, поэтому у них не должно быть неожиданного поведения для этих объектов.