Миграция от други езици

Когато за първи път се сблъскате с код, писан на Ruby, е вероятно да ви напомни на езици за програмиране, които вече ползвате. Това е направено с цел. Много от синтаксиса е заимстван от Perl, Python и Java (както и от други езици), затова научаването на Ruby би било много по-лесно.

Този страница е разделена на две секции. Първата е обобщение на нещата, които ще видите от езика X в Ruby. Втората секция е посветена на някои от основните принципи на езика.

Какво да очакваме

Важни неща в езика

Това са някои насоки за нещата, с които ще се запознаете при изучаването на Ruby.

Итерация

Две от нещата, с които може би не сте се сблъсквали са “блоковете” и итераторите. Вместо да използваме цикъл с индекс (както в C, C++ или във версиите на Java преди 1.5), итерацията на списък (в Perl for (@a) {...} или в Python for i in aList: ...) може да се осъществи с:

some_list.each do |this_item|
  # операции за всеки елемент this_item.
end

За повече информация относно each (и неговите приятели collect, find, inject, sort и т.н.) можете да използвате командата ri Enumerableri Enumerable#име_на_метод).

Всичко има стойност

Няма разлика между expression и statement. Всичко има стойност, дори и тя да бъде 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 не съвпадат. Това означава, че те реферират към два отделни обекта в паметта. Това е така, тъй като при създаването на нов низ се заделя памет отделно.

Ако се чудите дали да ползвате низ или символ, помислете кое е по-важно: идентификацията на обекта (например ключ на хеш) или неговото съдържание (“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, от Ruby 2.0 методите могат да използват именувани аргументи:

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 всяко нещо, различно от nil и false, е true. В C, Python и други езици 0, "" и други се считат за false. Нека разгледаме пример с код на Python:

# in Python
if 0:
  print("0 is true")
else:
  print("0 is false")

Изхода от кода е 0 is false. Еквивалента, написан на Ruby:

# in Ruby
if 0
  puts "0 is true"
else
  puts "0 is false"
end

На екрана се изписва 0 is true.

Модификаторите за достъп важат до края на декларацията

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

Може би очаквате another_method да бъде public, но това не е така. Модификатора private важи до употребата на друг модификатор. По подразбиране всички методи са public:

class MyClass
  # Now a_method is public
  def a_method; true; end

  private

  # another_method is private
  def another_method; false; end
end

public, private и protected всъщност са методи и за това могат да приемат параметри. Ако подадем символ на този метод, променяме видимостта на метода с име на символа.

Достъп до метод

В Java, public означава, че методът е достъпен за всички. protected означава, че инстанциите на класа и всички наследяващи го класове, както и класове в същият package могат да го достъпят. private означава, че единствено инстанциите на класа имат достъп до метода.

В Ruby нещата стоят по малко по-различен начин. public работи по същият начин. private се използва за модификатор за достъп, който прави методите достъпни, когато се викат без explicit receiver. В този случай единствено self може да е получател на извикването на private метод.

Към protected трябва да се подхожда с внимание. protected метод може да бъде извикан от клас или инстанция на наследен клас, но също така и с друга инстанции като неговият receiver.

Пример, взет от Ruby Language FAQ:

class Test
  # public by default
  def identifier
    99
  end

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

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

# now make `identifier' protected; it still works
# because protected allows `other' as receiver

class Test
  protected :identifier
end

t1 == t2  # => true

# now make `identifier' private

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 # number of seconds in an hour
  end
  alias hour hours
end

# 14 hours from 00:00 January 1st
# (aka when you finally wake up ;)
Time.mktime(2006, 01, 01) + 14.hours # => Sun Jan 01 14:00:00

Методи със странни имена

В Ruby е разрешено да използваме “?” и “!” като последен символ в името на метод. По конвенция методи, които завършват с “?” (например Array#empty? връща true ако receiver празен) връща булев израз. Потенциално “опасни” методи (тоест методи, променящи 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 objects are not affected
other_car = Car.new
other_car.inspect # => Cheap car

Липсващи методи

Ruby не се отказва, ако не може да намери метод, отговарящ на дадено съобщение към обект. Когато това стане, се извиква метода method_missing. По подразбиране method_missing активира изключение, но поради динамиката на Ruby можем да го предефинираме, както правят много библиотеки:

# id is the name of the method called, the * syntax collects
# all the arguments in an array named 'arguments'
def method_missing(id, *arguments)
  puts "Method #{id} was called, but not found. It has " +
       "these arguments: #{arguments.join(", ")}"
end

__ :a, :b, 10
# => Method __ was called, but not found. It has these
# arguments: a, b, 10

При изпълнението на горният код се извеждат детайли относно извикването на метода, но Вие можете свободно да контролирате следствието от този метод.

Предаване на метод

Извикването на метод представлява изпращане на съобщение към друг обект:

# This
1 + 2
# Is the same as this ...
1.+(2)
# Which is the same as this:
1.send "+", 2

Блоковете са обекти, които просто не го знаят

Блоковете (всъщност closures) се използват интензивно в стандартната библиотека. При извикването на блок можем да ползваме yield или да го направим Proc чрез добавянето на специален аргумент:

def block(&the_block)
  # Inside here, the_block is the block passed to the method
  the_block # return the block
end
adder = block { |a, b| a + b }
# adder is now a Proc object
adder.class # => Proc

Можем да създаваме блокове извън методи чрез Proc.new или чрез метода lambda. Впрочем, методите също са обекти:

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

Операторите са синтактична захар

Повечето оператори са “синтактична захар” за извиквания на методи. Може например да се предифинира Fixnum#+:

class Fixnum
  # You can, but please don't do this
  def +(other)
    self - other
  end
end

Не е нужен operator+ от C++ например.

Може дори и да се направи достъп, подобен на този при масивите, ако се предефинират [] и []=. + и - могат също да бъдат предефинирани съответно чрез +@ и -@.

Операторите, които следват, не са методи и поради това не могат да бъдат предефинирани:

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

В допълнение +=, *= и т.н са съкращения за var = var + other_var, var = var * other_var и не могат да бъдат предефинирани.

Научете още

Ако сте готови за още знания, посетете документацията.