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