Синглтон-методы в Ruby.
Синглтон-метод — это метод, который определен только для одного объекта.
Код я буду писать не по-рубистски, со скобками и return-ами — чтобы всем было понятно.
Когда я впервые прочитал об этой штуке в 2016 году, я прям офигел. У меня тогда за плечами был только опыт кодинга на C# и Pascal. Поэтому нет ничего странного в том, что я остановился посреди дороги и сказал «а чё, так можно было?».
Смотри сам:
Строку с вызовом я закомментировал. Если уберёшь коммент, то будет ошибка:
index.rb:13:in `<main>': undefined method `twice' for "abc":String (NoMethodError)
Всё потому что синглтон-метод есть только у объекта, который лежит в переменной a. А у других строк его нет, потому что класс String не изменился — его я не трогал (хотя и могу 😏).
Такой прикол: для любого объекта в Ruby можно сделать уникальные методы, не меняя при этом класс.
Не обязательно создавать именно новый метод — можно «на лету» переопределить уже существующий. Вот так ты можешь пошутить над другом (только не на проде плиз):
Это и правда похоже на привычные синглтоны. Синглтон — это когда у класса может быть только один объект. А синглтон-метод — это когда метод есть только у одного объекта.
Как работает.
Методы в Ruby всегда должны быть внутри класса. Поэтому когда ты создаёшь синглтон-метод, для объекта создаётся неявный класс. Этот новый класс как бы «встраивается» между объектом и его реальным классом:
Объяснение я сильно упростил, на самом деле там всё посложнее и поинтереснее.
А вот с терминологией здесь беда. Эти неявные классы как только не называли: «айгенклассы», «обособленные классы», «виртуальные классы», «метаклассы»… И до сих пор называют по-разному. Как-то так получилось, что эта важная часть Ruby была без общепринятого названия до версии 1.9.2. Тогда в язык ввели метод singleton_class
и теперь эти классы называют «классы-синглтоны». Хотя до этого в Ruby уже было понятие синглтона и поди теперь разберись, кто есть кто. Название логичное, но «айгенклассы» мне нравилось больше.
Окей, теперь давай посмотрим на синглтон-класс:
Метод class
вернул нам String
и нет тут никакого синглтона! Я ведь говорил, что синглтон-классы неявные? Просто так до них не достучаться. Поэтому и понадобился тот самый singleton_class
:
До появления singleton_class
единственный способ получить синглтон-класс был таким:
Синглтон-класс для объекта создаётся лениво, по требованию. То есть когда мы добавляем синглтон-метод или вызываем singleton_class
.
Открываем синглтон-класс.
Я показал только один из способов создания синглтон-методов — через def
c указанием объекта. Есть и второй — через «открытие» синглтон-класса. Выглядит реально странно:
Не считая вот этого вот class << "abc"
, это удобный способ определить сразу несколько синглтон-методов. Синтаксису тоже можно найти объяснение: в Ruby есть оператор <<
, который добавляет элемент в конец массива. То есть у него смысл в том, чтобы что-то куда-то добавлять. Да и сложно, наверное, для такой фичи придумать более адекватный синтаксис.
Зачем всё это надо.
Фича прикольная, но зачем это на уровне языка? Просто на синглтон-методах в Ruby держится половина ООП.
В каком-нибудь другом языке я бы получил ошибку типа «Нельзя вызвать статический метод word
на экземпляре класса». А Ruby просто говорит, что метод не найден. Потому что в Ruby экземплярные и статические методы лежат в разных местах:
- экземплярные — внутри класса,
- статические — в классе-синглтоне этого класса (ведь класс — это тоже объект).
Поэтому и выглядит объявление методов класса точь-в-точь как объявление синглтон-методов. Статические методы в Ruby — это синглтон-методы класса.
Я даже могу проверить это — использую метод singleton_methods
, который возвращает список синглтон-методов объекта:
В Ruby ты можешь сделать два метода с одинаковым именем — один экземплярный, а другой статический. И проблем не возникнет, ведь они лежат в разных местах. В C# была бы ошибка компиляции, а в Python остался бы только один из них (тот, что объявлен позднее).
Ненастоящее ООП.
В первой книге о Ruby, что я прочитал, в самом начале шла цитата Юкихиро Мацумото. А за ней — рассказ о том, что Ruby — настоящий ООП язык 💪. В нём всё — полноценный объект и у всего можно вызывать методы.
Кстати, рекомендую эту книжку, «Язык программирования Ruby» Дэвида Флэнагана — она топ.
Мне нравится, что создатели языков стремятся к единообразию. А то в JavaScript вот есть объекты, а есть "number"
, "string"
, "null"
, "undefined"
… И до сих пор в документации TypeScript говорится: «Пожалуйста, не пишите number c большой буквы! Спасибо!». Хорошо хоть что в Ruby не так. Да ведь?
ООП в Ruby и правда классное, мне нравится. Но вот на синглтон-методах оно ломается. Их просто нельзя определить для значений типов Fixnum
и Symbol
. Ну, потому что это примитивные значения, а не ссылки на объекты. И эту разницу ну никак не заметить. Если не попробовать получить синглтон-класс у числа 24, конечно.
А что там у других.
Идея синглтон-методов есть не только в Ruby. Вот есть язык Dylan, в котором есть синглтон-методы. Правда, они сильно отличаются от тех, что в Ruby:
Тут они создаются не для конкретного объекта, а для значения — если аргумент равен строке "cup"
, то метод выполняется. Это как сопоставление с образцом (точно как в Haskell). Dylan, кстати, на 3 года старше Ruby.
При помощи синглтон-методов можно эмулировать прототипное ООП и сделать JavaScript из Ruby. Вообще, JavaScript такой идеей, как добавление метода к объекту не удивишь — там это повсюду и без всяких синглтон-классов.
В динамических языках синглтон-методы вполне можно сделать руками. Для Perl 5 есть библиотека, которая приносит в него частичку Ruby:
В статических языках гораздо больше ограничений. Но в Java анонимные классы тоже сойдут за синглтон-классы:
Конец?
Искать параллели в других языках можно долго, и это интересное занятие. Но ведь даже о синглтонах в Ruby я рассказал так мало и осталась куча нераскрытых вопросов. Я просто напишу их здесь. Если будет интересно, можешь открыть интерпретатор Ruby и попробовать. Погуглить — тоже вариант.
- Что будет, если попробовать получить синглтон-класс у числа 24?
- Как на самом деле синглтон-классы встраиваются в иерархию классов?
- Где хранятся переменные класса?
- Существуют ли синглтон-переменные и синглтон-константы?
Если найдешь что-то, чем захочешь поделиться, смело пиши мне на почту.