RDRubyTutorial: Метапрограммный Ruby level 2

мая 9, 2012  |  Published in Ruby, Основы  |  6 Comments

Предварительно рекомендую прочесть Метапрограммный Ruby.

К моему удивлению не все Ruby программисты знают внутреннего устройства Ruby. Не в том, смысле, что Ruby’ст должен изучать исходники своего любимого языка программирования, но в том, что многие моменты не понятны. Например не понятно, чем класс отличается от объекта или что такое singleton’ы в Ruby. Эта статья призвана совершить некоторую демистификацию Ruby. В простой манере и без влезания в код самого Ruby я расскажу почему что-то работает именно так, как работает. Статья ориентирована не на новичков, а на программистов более-менее среднего уровня, то есть достаточно хорошо знакомых с Ruby.

Статья будет иметь формат повествования, она будет лишена структуры, читайте ее просто как программистскую сказку.

Классы — это объекты, но только на половину. Классы являются экземплярами одного из базовых классов Class, однако они не являются «классическими» объектами.

$ ruby -v
ruby 1.9.3p191 (2012-04-19 revision 35398) [i686-linux]
$ irb

class A
end

A.class #=> Class

В Ruby объекты не могут иметь собственных методов. Объект — это просто набор данных. Например объект класса Human содержит данные об имени, росте, весе, цвете глаз и прочих параметрах человека. В этом смысле объект в Ruby не сильно отличается от ассоциативного массива (а в JavaScript — это и вовсе одно и то же). Поскольку объект не может хранить собственные методы, то его методы хранятся в классе. То, что класс хранит методы своих экземпляров — это основное отличие класса от обыкновенного объекта. Даже не то, что класс может создавать экземпляры, а то, что он хранит методы экземпляров, ведь мы можем используя техники метапрограммирования расширить обыкновенный объект до того, что он будет подобным классу в своем поведении, но методы своих экземпляров он хранить не сможет.

class A
  def hello
    puts 'hello'
  end
end

a = A.new

a.hello
# hello

Когда мы для a вызываем метод hello, то, на самом деле a передает сообщение об его вызове по цепочке наследования, сначала к своему singleton классу, затем к классу A и т.д. пока метод не будет найден. У самого же a нет методов!

Вот он singleton класс:

a.singleton_class
#=> #<Class:#<A:0x82bf770>>

А вот еще один:

A.singleton_class
#=> #<Class:A>

Любой объект в том числе и класс (который объект только на половину) имеет закрепленный за собой singletion класс. Singleton класс встраивается в цепочку наследования. Здесь он наследуется от класса A:

a.singleton_class
#=> #<Class:#<A:0x82bf770>>

А здесь он наследуется от класса Class:

A.singleton_class
#=> #<Class:A>

Таким образом Singleton класс — это незаметная ссылка между экземпляром класса и классом. Когда мы определяем объектам специальные методы, например так:

def a.bye
  puts 'bye'
end

a.bye
# bye

… или так:

def A.how_are_you
  puts 'How are you?'
end

A.how_are_you
# How are you?

… или так:

class A
  def self.what_is_your_name
    puts "What is your name?"
  end
end

A.what_is_your_name
# What is your name?

… или даже так:

class A
  class << self
    def how_old_are_you
      puts 'How old are you?'
    end
  end
end

A.how_old_are_you
# How old are you?

… то мы не явно имеем дело с Singleton классами. Класс способен хранить методы своих экземпляров, но не свои собственные. Каждый класс имеет Singleton класс, который хранит уникальные методы класса. Стандартные методы класс, будучи экземпляром класса Class, хранит в классе Class и других базовых классах, например классе Object от которого он наследуется:

A.superclass #=> Object

Все уникальные методы объектов, которые вы определяете хранятся в Singleton классах, в универсальные методы хранятся в их классах.

a.singleton_methods #=> [:bye]
A.singleton_methods #=> [:how_are_you, :what_is_your_name, :how_old_are_you]
A.methods.count #=> 100
(A.methods - A.singleton_methods).count #=> 97

Почему название такое — Singleton класс? — Дело в том, что этот класс может иметь только один объект:

a.singleton_class.new
TypeError: can't create instance of singleton class

Потому и название такое, хотя ме больше нравится название Eigenclass, которое, однако встречается значительно реже. С названием eigenclass не возникает путаницы так как существует еще такой паттерн как Singleton («Одиночка»), который позволяет создавать классы, которые не могут иметь более одного экземпляра. Из-за того, что Eigenclass имеет только экземпляр — объект, за которым он закреплен, то и было решено использовать название Singleton, которое правда не описывает его предназначение и вносит путаницу с паттерном проектирования о котором говорилось выше. Название Eigenclass лучше отображает суть ведь в переводе с английского/немецкого оно означает «собственный класс».

Делаем выводы:

  • Объект, если рассматривать его совсем примитивно является просто хэшем, который хранит определенные данные.
  • Класс является объектом, который способен хранить методы своих экземпляров.
  • Singleton класс — это класс, экземпляром которого на самом деле является объект, который может обладать только одним экземпляром, и которых хранит все уникальные методы объекта.
  • Мы не говорим, что a наследуется от A и не смотря на наличие singleton класса — это правильно, так как singleton класс — это просто реализация, но не парадигма и его наличие ни на что не влияет.

Люди часто не понимают, что делает следующий код:

class A
  class << self
    def how_old_are_you
      puts 'How old are you?'
    end
  end
end

Этот код — это в некотором смысле синтаксический сахар. Выражение class нуждается в получении константы — имени класса. self — ссылка на текущий контекст, если мы не указываем приемник вызова метода явно, то предполагается, что им является self — объект в контексте которого осуществляется вызов. Когда мы используем class << self, то это просто специальный синтаксис, через стрелку «<<« в котором мы как бы открываем контекст предка — singleton класса. В class мы не можем передать что-то иное, кроме константы, в этом случае возникнет синтаксическая ошибка:

class A
  class singleton_class
    def rubydev
      puts "RubyDev.ru"
    end
  end
end

SyntaxError: (irb):90: class/module name must be CONSTANT
from /home/vladimir/.rvm/rubies/ruby-1.9.3-head/bin/irb:16:in `<main>’

Singleton классы являются безымянными (анонимными). Им не нужно имя хотя бы потому, что они сами по себе существовать не могу, с ними на прямую никто работать не должен, а если и возникает такая необходимость, то обратиться к ним можно через метод singleton_class объекта, за которым они закреплены.

Синтаксис class << self просто обрабатывается иначе. Интерпретатор понимает его не как необходимость вызова для объекта class метода << и передачи аргумента self, а как нечто иное — переключение в контекст singleton класса. Вот и все!

Tags: , , , ,

Responses

  1. arnisoft says:

    мая 9, 2012 at 19:19 (#)

    Примечательно, что именно сегодня вышла близкая статья на хабре об include и extend, которая также в конечном счете свелась к феномену singleton class.
    Вот так за один день увидели свет две замечательные статьи, прочистившие голову от шелухи по сабжу. Спасибо.

  2. admin says:

    мая 9, 2012 at 20:16 (#)

    Свою статью я написал после участия в комментировании статьи на хабре и там возник вопрос по поводу class << self.

  3. none says:

    мая 17, 2012 at 01:48 (#)

    Т.е. каждый экземпляр класса ссылается на свой единственный eigenclass ?

    Было бы классно, если бы была илюстрация

  4. admin says:

    мая 17, 2012 at 09:14 (#)

    none, да, каждый класс является экземпляром не от класса Class, а от eigenclassa, который наследуется от Class, а точнее от родительского класса объекта.

  5. anon says:

    мая 22, 2012 at 16:41 (#)

    Singleton класс — это класс, экземпляром которого на самом деле является объект, который может обладать только одним экземпляром, и которых хранит все уникальные методы объекта.

    Сложно воспринимаетмя это предлжение текста.

  6. anon says:

    мая 22, 2012 at 16:56 (#)

    *Сложно воспринимается это предложение.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.