В Ruby из других языков

Если вы впервые посмотрите на Ruby код, он скорее всего напомнит вам некоторые используемые вами языки. Это не случайно. Большинство синтаксических конструкций покажутся знакомыми пользователям Perl, Python и Java, так что, если вы уже писали на них, изучить Ruby окажется проще простого.

Эта страница состоит из двух частей. В первой содержится попытка сверхкратко описать, что вас ждет при переходе с языка Х на Ruby. Вторая рассказывает об основных особенностях языка, и как эти особенности соотносятся с тем, что вы уже знаете.

Чего ожидать после языка Х на Ruby

Важные замечания по поводу языка и подсказки

Тут собрано несколько подсказок и советов по основным особенностям Ruby, которые вы увидите по мере изучения языка

Итерации и циклы

Две особенности Ruby, отличающиеся от всего ранее увиденного, но к которым надо привыкнуть - это “блоки” и итераторы. Вместо того, чтобы итерироваться по индексу (как в С, С++ и pre-1.5 Java), или по списку (как в Perl for (@a) {...}, или в Python for i in aList: ...), в Ruby зачастую вы увидите

some_list.each do |this_item|
  # Внутри "блока"
  # у нас есть объект this_item.
end

За более подробной информацией о each (и сопутствующих collect, find, inject, sort, и т.д.), обращайтесь к ri Enumerableri Enumerable#имя_функции).

Все имеет значение

В Ruby нет разницы между выражением и оператором. Все имеет значение, даже если это значение - nil. Вот так:

x = 10
y = 11
z = if x < y
      true
    else
      false
    end
z # => true

Символы - это не “легковесные” строки

Многие начинающие натыкаются на проблему понимания, что такое “символ” в Ruby, и для чего он предназначен.

Символы лучше всего сравнить с уникальными идентификаторами. Символ это скорее сам знак, а не то, что он означает. Запустите irb, чтобы почувствовать разницу:

irb(main):001:0> :george.object_id == :george.object_id
=> true
irb(main):002:0> "george".object_id == "george".object_id
=> false
irb(main):003:0>

Метод object_id возвращает уникальный идентификатор объекта. Если два объекта имеют одинаковый object_id, то это один и тот же объект (указатель на один объект в памяти).

Как вы видите, как только вы используете символ, любой другой символ, идентичный по написанию с первым, будет обращаться к тому же объекту в памяти. У всех “символов” с одинаковым набором букв (с одним именем) один и тот же object_id.

Теперь взглянем на строки. Их object_id не совпадает. Это означает, что это два разных объекта в памяти. При создании строки Ruby всегда выделяет память для нее.

Если вы сомневаетесь, что использовать - символ или строку - задумайтесь, что важнее: уникальность объекта (например, для ключа в хеше) или содержание (как в примере выше - “george”)

Все является объектом

“Все - это объект” - не преувеличение. Даже классы и числа, и с ними можно делать то, что и с обычными объектами:

# То же самое, что и
# class MyClass
#   attr_accessor :instance_var
# end
MyClass = Class.new do
  attr_accessor :instance_var
end

Изменяемые константы

Константы на самом деле не константы. Если вы поменяете значение константы, это выдаст предупреждение, но не остановит программу. Однако это не говорит о том, что вы должны переопределять константы.

Соглашение о наименовании

Ruby диктует некоторые правила о наименовании. Константы начинаются с заглавной буквы. Глобальные переменные начинаются со знака $. Переменные экземпляра начинаются с @. Переменные класса начинаются с @@.

Имена методов могут начинаться с заглавных букв, однако это может запутать, например:

Constant = 10
def Constant
  11
end

Значение Constant - 10, а Constant() - 11.

Именованные параметры

Как и в Python, начиная с версии 2.0 Ruby методы принимают именованные параметры.

def deliver(from: "A", to: nil, via: "mail")
  "Sending from #{from} to #{to} via #{via}."
end

deliver(to: "B")
# => "Sending from A to B via mail."
deliver(via: "Pony Express", from: "B", to: "A")
# => "Sending from B to A via Pony Express."

“Истинное” Ruby

В Ruby все кроме nil и false рассматриваются как истина. В С, Python и многих других языках 0 и некоторые другие значения, например пустой список, являются ложью. Взгляните на следующий код Python (пример применим и у другим языкам):

# Python
if 0:
  print("0 - истина")
else:
  print("0 - ложь")

Это выведет на экран “0 - ложь”. Эквивалент на Ruby:

# Ruby
if 0
  puts "0 - истина"
else
  puts "0 - ложь"
end

Выведет на экран “0 - истина”.

Модификаторы доступа действуют до конца контекста

В следующем Ruby коде

class MyClass
  private
  def a_method; true; end
  def another_method; false; end
end

можно подумать, что another_method публичный. Нет, это не так. Модификатор доступа private действует до конца контекста, или до другого модификатора доступа. По умолчанию все методы публичны.

class MyClass
  # a_method публичный
  def a_method; true; end

  private

  # another_method приватный
  def another_method; false; end
end

public, private и protected на самом деле методы, и они могут принимать параметры. Можно передать им имена методов как символы, чтобы поменять область доступа метода.

Вызов методов

В Java public означает, что метод можно вызвать везде. protected методы можно вызвать только инстансами этого класса, инстансами дочернего класса и инстансами класса в этом же пакете. private методы не может вызвать никто кроме инстанса класса.

В Ruby все немного по-другому. public методы на самом деле публичные. private метод может быть вызван только без явного объявления вызывающей стороны. Только self может быть вызывающей стороной приватного метода.

О protected методах надо поговорить подробнее. Protected метод может быть вызван инстансом текущего или дочернего класса, однако может иметь вызывающей стороной другой инстанс. Пример, позаимствованный из Ruby Language FAQ:

class Test
  # публичный метод по умолчанию
  def identifier
    99
  end

  def ==(other)
    identifier == other.identifier
  end
end

t1 = Test.new  # => #<Test:0x34ab50>
t2 = Test.new  # => #<Test:0x342784>
t1 == t2       # => true

# сделаем `identifier' protected методом
# это возможно, потому что можно вызвать метод у объекта other

class Test
  protected :identifier
end

t1 == t2  # => true

# теперь сделаем `identifier' приватным

class Test
  private :identifier
end

t1 == t2
# NoMethodError: private method `identifier' called for #<Test:0x342784>

Открытые классы

Классы в Ruby “открыты”. То есть, вы можете открыть их и добавить или изменить их в любое время. Даже базовые классы, такие как Fixnum или Object, родительский для всех объектов. Ruby on Rails определяет несколько методов на Fixnum, чтобы работать со временем. Смотрите:

class Fixnum
  def hours
    self * 3600 # число секунд в 1 часе
  end
  alias hour hours
end

# 14 часов после 00:00, 1 января
# (когда все только-только просыпаются ;)
Time.mktime(2006, 01, 01) + 14.hours
# => Sun Jan 01 14:00:00

Прикольные имена методов

В Ruby имена методов могут оканчиваться на вопросительный или восклицательный знаки. По соглашению методы, которые отвечают на вопрос, заканчиваются вопросительным знаком (например, Array#empty? возвращает true если массив пустой). Некоторые, “потенциально опасные” методы (которые меняют вызывающую сторонy, self или параметры) заканчиваются восклицательным знаком (например, exit!). Однако не все методы, которые меняют аргументы заканчиваются так, например Array#replace заменяет содержимое массива переданным массивом. Просто нет смысла иметь метод, который бы не менял исходный массив в этом случае.

Singleton методы

Singleton методы - это методы, определенные на единственном инстансе и доступные только на нем.

class Car
  def inspect
    "Cheap car"
  end
end

porsche = Car.new
porsche.inspect # => Cheap car
def porsche.inspect
  "Expensive car"
end

porsche.inspect # => Expensive car

# Другие объекты не изменяют поведение
other_car = Car.new
other_car.inspect # => Cheap car

“Пропавшие” методы

Ruby не сдается, если не находит вызванный метод, а вызывает метод method_missing, передав ему имя “потерянного” метода и аргументы. По умолчанию method_missing вызывает исключение NameError, но вы можете переопределить его по вашим потребностям, что и делает множество библиотек, например:

# id - имя вызванного метода, *arguments - такой синтаксис
# передает все аргументы в функцию как массив 'arguments'
def method_missing(id, *arguments)
  puts "Метод #{id} был вызван, но не найден." +
       "Его аргументы: #{arguments.join(", ")}"
end

__ :a, :b, 10
# => Метод __ был вызван, но не найден.
# Его аргументы: a, b, 10

Пример выше просто выводит на экран детали вызова метода, но вы можете сделать что-то полезное по вашему усмотрению.

Передача сообщений, а не вызов функций

На самом деле вызов метода - это передача (send) ему сообщения. Наглядно:

# Это
1 + 2
# то же самое, что и ...
1.+(2)
# и то же самое, что и:
1.send "+", 2

Блоки - тоже объекты, только они об этом еще не знают

Блоки (на самом деле - замыкания) часто используются в стандартной библиотеке. Чтобы вызвать блок можно либо использовать yield, либо сделать его объектом класса Proc, прибавив специальный аргумент к списку аргументов, например так:

def block(&the_block)
  # Тут the_block это блок, переданный методу
  the_block # вернет блок (как объект)
end
adder = block { |a, b| a + b }
# adder - это объект класса Proc
adder.class # => Proc

Вы можете создавать блоки-объекты также через Proc.new с блоком или вызывая lambda метод.

В принципе, методы - это тоже объекты:

method(:puts).call "puts is an object!"
# => puts is an object!

Операторы - это синтаксический сахар

Большинство операторов в Ruby - это просто синтаксический сахар (с учетом некоторых правил) для вызова методов. Например, можно переопределить + метод для класса Fixnum:

class Fixnum
  # Так можно, но не значит, что нужно ;)
  def +(other)
    self - other
  end
end

Так что вам не потребуется operator+ из С++, и т.д.

А еще можно симулировать обращения к объекту как к массиву с помощью методов [] и []=. Можно определить унарные операторы + и - (например +1 или -2) методами +@ и -@ соответственно. Операторы ниже, однако, не являются синтаксическим сахаром. Они не являются методами и не могут быть переопределены:

=, .., ..., not, &&, and, ||, or, ::

В дополнение к этому, +=, *= и т.д. - это всего лишь сокращения для var = var + other_var, var = var * other_var и т.д. и, соответственно, не могут быть переопределены.

Узнать больше

Если вам хочется узнать о Ruby больше - перейдите к документации.