Синтаксис языка программирования Ruby в целом похож на синтаксис Perl и Python . Определения классов и методов обозначаются ключевыми словами, тогда как блоки кода могут быть определены как ключевыми словами, так и фигурными скобками. В отличие от Perl, переменные не обязательно имеют префикс sigil . При использовании sigil изменяет семантику области действия переменной. Для практических целей нет различия между выражениями и операторами . [1] [2] Разрывы строк значимы и воспринимаются как конец оператора; точка с запятой может быть эквивалентно использована. В отличие от Python, отступы не имеют значения.
Одно из отличий от Python и Perl заключается в том, что Ruby сохраняет все свои переменные экземпляра полностью закрытыми для класса и раскрывает их только через методы доступа ( attr_writer
, attr_reader
, и т. д.). В отличие от методов «getter» и «setter» других языков, таких как C++ или Java , методы доступа в Ruby могут быть созданы с помощью одной строки кода с помощью метапрограммирования ; однако методы доступа также могут быть созданы традиционным способом C++ и Java. Поскольку вызов этих методов не требует использования скобок, легко изменить переменную экземпляра на полноценную функцию без изменения единой строки вызывающего кода или необходимости выполнять какой-либо рефакторинг, достигая функциональности, аналогичной членам свойств C# и VB.NET .
Дескрипторы свойств Python похожи, но в процессе разработки приходится идти на компромисс. Если начать в Python с использования публично открытой переменной экземпляра, а затем изменить реализацию, чтобы использовать закрытую переменную экземпляра, открытую через дескриптор свойства, может потребоваться скорректировать внутренний код класса для использования закрытой переменной, а не открытого свойства. Дизайн Ruby заставляет все переменные экземпляра быть закрытыми, но также предоставляет простой способ объявления методов set
и get
методов. Это соответствует идее, что в Ruby никогда не осуществляется прямой доступ к внутренним членам класса извне класса; вместо этого классу передается сообщение и получается ответ.
Следующие примеры можно запустить в оболочке Ruby, например Interactive Ruby Shell , или сохранить в файле и запустить из командной строки, введя .ruby <filename>
Классический пример Hello world :
ставит «Привет, мир!»
Немного базового кода Ruby:
# Все, включая литерал, является объектом, поэтому это работает: - 199 . abs # => 199 'ice is nice' . length # => 11 'ruby is cool.' . index ( 'u' ) # => 1 "Хороший день, не правда ли?" . downcase . split ( '' ) . uniq . sort . join # => " '?acdeinsty"
Вход:
print 'Пожалуйста, введите имя >' name = gets . chomp puts "Привет #{ name } ."
Конверсии:
puts 'Дайте мне число' число = получает . chomp puts число . to_i выходное_число = число . to_i + 1 puts выходное_число . to_s + ' большее число.'
В Ruby существует множество способов определения строк.
Следующие задания эквивалентны:
a = " \n Это строка в двойных кавычках \n " a = %Q{ \n Это строка в двойных кавычках \n } a = %{ \n Это строка в двойных кавычках \n } a = %/\nЭто строка в двойных кавычках\n/ a = <<- BLOCK Это строка BLOCK в двойных кавычках
Строки поддерживают интерполяцию переменных :
вар = 3 . 14159 "пи равно #{ var } " => "пи равно 3,14159"
Следующие присваивания эквивалентны и создают необработанные строки :
a = 'Это строка в одинарных кавычках' a = %q{Это строка в одинарных кавычках}
Построение и использование массива :
а = [ 3 , 'привет' , 14 . 5 , 1 , 2 , [ 6 , 15 ]] a [ 2 ] # => 14,5 a . [] ( 2 ) # => 14,5 a . обратный # => [[6, 15], 2, 1, 14,5, 'привет', 3] a . flatten . uniq # => [3, 'привет', 14,5, 1, 2, 6, 15]
Создание и использование ассоциативного массива (в Ruby называемого хешем ):
hash = Hash . new # эквивалентно hash = {} hash = { water : 'wet' , fire : 'hot' } # делает предыдущую строку избыточной, поскольку сейчас мы # назначаем hash новому, отдельному объекту hash puts hash [ :fire ] # печатает "hot" hash . each_pair do | key , value | # или: hash.each do |key, value| puts " #{ key } is #{ value } " end # возвращает {:water=>"wet", :fire=>"hot"} и печатает: # вода мокрая # огонь горячий hash . delete :water # удаляет пару :water => 'wet' и возвращает "wet" hash . delete_if { | key , value | value == 'hot' } # удаляет пару :fire => 'hot' и возвращает {}
Если утверждение:
# Сгенерировать случайное число и вывести, четное оно или нечетное. if rand ( 100 ) . even? puts "It's even" else puts "It's odd" end
Два синтаксиса для создания блока кода:
{ puts 'Hello, World!' } # обратите внимание на фигурные скобки # или: do puts 'Hello, World!' end
Блок кода может быть передан методу как необязательный аргумент блока. Многие встроенные методы имеют такие аргументы:
File . open ( 'file.txt' , 'w' ) do | file | # 'w' обозначает "режим записи" file . puts 'Написал какой-то текст.' end # файл автоматически закрывается здесь Файл . readlines ( 'file.txt' ) . each do | line | puts line end # => Написал какой-то текст.
Передача параметров блоку, который будет замыканием :
# В переменной экземпляра объекта (обозначенной как '@') запомните блок. def remember ( & a_block ) @block = a_block end # Вызвать предыдущий метод, передав ему блок, который принимает имя. remember { | name | puts "Hello, #{ name } !" } # Вызвать замыкание (обратите внимание, что это происходит не для замыкания каких-либо свободных переменных): @block . call ( 'Jon' ) # => "Привет, Джон!"
Создание анонимной функции :
proc { | arg | puts arg } Proc . new { | arg | puts arg } lambda { | arg | puts arg } -> ( arg ) { puts arg } # введено в Ruby 1.9
Возврат замыканий из метода:
def create_set_and_get ( initial_value = 0 ) # обратите внимание, что значение по умолчанию равно 0 closure_value = initial_value [ Proc . new { | x | closure_value = x }, Proc . new { closure_value } ] end setter , getter = create_set_and_get # возвращает два значения setter.call ( 21 ) getter.call # = > 21 # Переменные параметров также могут использоваться в качестве привязки для замыкания, # поэтому предыдущее можно переписать так:def create_set_and_get ( closure_value = 0 ) [ proc { | x | closure_value = x } , proc { closure_value } ] конец
Передача потока управления программой блоку, который был предоставлен во время вызова:
def use_hello yield "привет" конец # Вызвать предыдущий метод, передав ему блок. use_hello { | string | puts string } # => 'hello'
Итерация по перечислениям и массивам с использованием блоков:
array = [ 1 , 'привет' , 3 . 14 ] array . each { | item | puts item } # выводит: # 1 # 'привет' # 3.14 array . each_index { | index | puts " #{ index } : #{ array [ index ] } " } # выводит: # 0: 1 # 1: 'привет' # 2: 3.14 # В следующем примере используется диапазон (a..b) ( 3..6 ) . each { | num | puts num } # выводит: # 3 # 4 # 5 # 6 # В следующем примере используется диапазон (a...b) ( 3 ... 6 ) . each { | num | puts num } # выводит: # 3 # 4 # 5
Метод, такой как inject
может принимать как параметр, так и блок. inject
Метод выполняет итерацию по каждому члену списка, выполняя некоторую функцию над ним, сохраняя при этом агрегат. Это аналогично функции foldl
в языках функционального программирования . Например:
[ 1 , 3 , 5 ] .inject ( 10 ) { | сумма , элемент | сумма + элемент } # => 19
На первом проходе блок получает 10 (аргумент для внедрения) как sum
, и 1 (первый элемент массива) как element
. Это возвращает 11, который затем становится sum
на следующем проходе. Он добавляется к 3, чтобы получить 14, который затем добавляется к 5 на третьем проходе, чтобы в итоге вернуть 19.
Использование перечисления и блока для возведения в квадрат чисел от 1 до 10 (используя диапазон ):
( 1 .. 10 ) . собрать { | x | x * x } # => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Или вызвать метод для каждого элемента ( map
является синонимом collect
):
( 1 .. 5 ) . карта ( & :to_f ) # => [1.0, 2.0, 3.0, 4.0, 5.0]
Следующий код определяет класс с именем Person
. В дополнение к initialize
, обычному конструктору для создания новых объектов, он имеет два метода: один для переопределения <=>
оператора сравнения (чтобы Array#sort
можно было сортировать по возрасту), а другой для переопределения to_s
метода (чтобы Kernel#puts
можно было форматировать его вывод). Вот attr_reader
пример метапрограммирования в Ruby: attr_accessor
определяет методы getter и setter переменных экземпляра, но attr_reader
только методы getter. Последний вычисляемый оператор в методе — это его возвращаемое значение, что позволяет опустить явный return
оператор.
class Person attr_reader :name , :age def initialize ( name , age ) @name , @age = name , age end def <=> ( person ) # оператор сравнения для сортировки @age <=> person . age end def to_s " #{ @name } ( #{ @age } )" end end группа = [ Человек . новый ( "Боб" , 33 ), Человек . новый ( "Крис" , 16 ), Человек . новый ( "Эш" , 23 ) ] ставит группу . сортировку . обратный
Приведенный выше код выводит три имени в обратном порядке возраста:
Боб (33)Ясень (23)Крис (16)
Person
является константой и является ссылкой на Class
объект.
В Ruby классы никогда не закрываются: методы всегда можно добавить к существующему классу. Это применимо ко всем классам, включая стандартные встроенные классы. Все, что нужно сделать, это открыть определение класса для существующего класса, и указанное новое содержимое будет добавлено к существующему содержимому. Простой пример добавления нового метода к классу стандартной библиотеки Time
:
# повторно открыть класс времени Ruby class Time def yesterday self - 86400 end end сегодня = Время . сейчас # => 2013-09-03 16:09:37 +0300 вчера = сегодня . вчера # => 2013-09-02 16:09:37 +0300
Добавление методов к ранее определенным классам часто называют monkey-patching . Если это делается безрассудно, то это может привести как к конфликтам поведения с последующими неожиданными результатами, так и к проблемам масштабируемости кода.
Начиная с Ruby 2.0 стало возможным использовать уточнения для уменьшения потенциально негативных последствий «мани-патчинга» путем ограничения области действия патча определенными областями кодовой базы.
# повторно открыть модуль класса времени Ruby RelativeTimeExtensions refine Time do def half_a_day_ago self - 43200 end end end модуль MyModule класс MyClass # Разрешить использование уточнения с помощью RelativeTimeExtensions def window Время . сейчас . полдня_назад конец конец конец
Исключение возникает при raise
вызове:
поднимать
К исключению можно добавить необязательное сообщение:
поднять "Это сообщение"
Исключения также могут быть указаны программистом:
raise ArgumentError , "Недопустимые аргументы!"
В качестве альтернативы можно передать методу экземпляр исключения raise
:
raise ArgumentError . new ( "Недопустимые аргументы!" )
Последняя конструкция полезна при создании экземпляра пользовательского класса исключений, содержащего конструктор, который принимает более одного аргумента:
class ParseError < Exception def initialize ( input , line , pos ) super "Не удалось проанализировать ' #{ input } ' в строке #{ line } , позиция #{ pos } " end end вызвать ParseError . новый ( "Foo" , 3 , 9 )
Исключения обрабатываются предложением rescue
. Такое предложение может перехватывать исключения, которые наследуются от StandardError
. Другие ключевые слова управления потоком, которые можно использовать при обработке исключений, это else
и ensure
:
begin # сделать что-то rescue # обработать исключение else # сделать это, если исключение не было вызвано ensure # сделать это независимо от того, было ли вызвано исключение end
Распространенной ошибкой является попытка перехватить все исключения с помощью простого спасательного предложения. Чтобы перехватить все исключения, нужно написать:
begin # сделайте что-нибудь rescue Exception # Здесь код обработки исключений. # Не пишите только "rescue"; это перехватывает только StandardError, подкласс Exception. end
Или перехватывайте определенные исключения:
begin # сделайте что-нибудь, спасите RuntimeError # обработайте только RuntimeError и его подклассы end
Также можно указать, что объект исключения должен быть доступен для предложения обработчика:
begin # сделайте что-нибудь спасательное RuntimeError => e # обработка, возможно, связанная с e, например "puts e.to_s" end
В качестве альтернативы последнее исключение сохраняется в магической глобальной переменной $!
.
Также можно выделить несколько исключений:
begin # сделать что-нибудь спасательное RuntimeError , Timeout :: Error => e # обработка, возможно, с участием e end
Код Ruby может программно изменять во время выполнения аспекты своей собственной структуры, которые были бы фиксированы в более жестких языках, таких как определения классов и методов. Этот вид метапрограммирования может использоваться для написания более краткого кода и эффективного расширения языка.
Например, следующий код Ruby генерирует новые методы для встроенного String
класса на основе списка цветов. Методы оборачивают содержимое строки в HTML-тег, стилизованный под соответствующий цвет.
ЦВЕТА = { черный : "000" , красный : "f00" , зеленый : "0f0" , желтый : "ff0" , синий : "00f" , пурпурный : "f0f" , голубой : "0ff" , белый : "fff" } класс String ЦВЕТА . каждый do | цвет , код | define_method "in_ #{ цвет } " do "<span style= \" цвет: # #{ код } \" > #{ сам } </span>" конец конец конец
Сгенерированные методы затем можно использовать следующим образом:
"Привет, мир!" . in_blue => "<span style= \" color: #00f \" >Привет, мир!"</span>"
Чтобы реализовать эквивалент во многих других языках, программисту пришлось бы писать каждый метод ( in_black
, in_red
, in_green
, и т. д.) отдельно.
Вот некоторые другие возможные варианты использования метапрограммирования Ruby:
В синтаксисе Ruby оператор — это всего лишь частный случай выражения, которое не может использоваться в качестве аргумента (например, множественное присваивание).
оператор [...] не может быть частью выражения, если он не заключен в скобки.