Быстрое знакомство с Ruby

Андрей Васильев

2020

Язык Ruby

Скриптовый (интепретируемый) язык

Приложение на языке Ruby всегда выполняется с помощью интерпретатора, как во время разработки, так и во время использования конечными пользователями

Объектно-ориентированный язык

  • Объект — это базовый элемент приложения, имеющий состояние и методы, использующие данное состояние
  • Структура объектов описывается с помощью классов
  • Для создания объектов (экземпляров классов) используется метод класса new, конструктор

Динамически типизированный

Переменные могут содержать в себе ссылки на объекты любых типов

Запуск приложений на Ruby

Для выполнения приложения на языке Ruby потребуется:

  • Интерпретатор языка Ruby
  • Набор библиотек, gems, от которых зависит код приложения
  • Код самого приложения

Запуск приложения сводится к вызову интерпретатора Ruby с указанием файла, начинающего исполнение приложения

Актуальные интерпретаторы Ruby

Переменная PATH

Во всех операционных системах есть переменная окружения PATH, содержащая набор каталогов, в которых происходит поиск исполняемых файлов

Если каталога с исполняемым файлом нет в PATH, то для его запуска надо указать абсолютный путь

Руководство по настройке PATH

IRB — интерактивный интерпретатор Ruby

Вместе с обычным интерпретатором языка ruby, предназначенным для выполнения приложений, поставляется также интерактивный интерпретатор irb

Данное приложение работает по следующему принципу:

  • Пользователь вводит строку на языке Ruby и нажимает Enter
  • Приложение считывает данную строку, разбирает и выполняет
  • Выполнение выражения изменяет состояние IRB
  • Результат выполнения выражения выводится пользователю
  • Приложение ожидает ввод от пользователя

IRB позволяет

  • Быстро проверять свои идеи
  • Узнавать детали работы методов

Пример из приложения

Приложение «Музыкальный автомат» должно обрабатывать данные о песнях. Опишем структуру объектов типа «песня» с помощью класса Song:

class Song
  ...
end

Создадим две переменные first_song и second_song, запишем в них ссылки на создаваемые объекты

first_song = Song.new('Калинка малинка')
second_song = Song.new('Смуглянка')

Объекты в Ruby

  • У каждого объекта есть уникальный идентификатор, даже если нет данных
  • Объекты содержат переменные экземпляра, описывающие его состояние
  • Объекты содержат методы, которые могут изменять переменные экземпляра
  • Методы могут быть публичными, приватными и защищёнными
    • Публичные методы могут быть вызваны любыми объектами
    • Приватные методы могут быть вызваны только этим объектом
    • Защищённые методы могут быть вызваны в рамках дерева наследования
  • Вызов метода удобно моделировать как отправку сообщения между объектами
Модель вызова метода

Сообщение включает название метода и набор аргументов для него

Обработка вызова метода

  1. При получении сообщения объект начинает поиск метода по имени
  2. Когда метод найден, ему передаются все аргументы

Если метод не найден или количество аргументов не совпало, то будет выброшено исключение

Более детально логику поиска методов обсудим на следующих занятиях

Примеры вызова методов

"gin joint".length
"Rick".index("c")
42.even?
sam.play(song)
  • Часть кода до точки называется получателем
  • После точки вводится имя метода
  • Аргументы метода передаются в круглых скобках
  • Числа в Ruby являются объектами, они содержат методы
num = -1234
num.abs

Базовые элементы языка

При написании кода на языке Ruby вам всегда будут доступны:

Метод puts модуля Kernel

puts выводит переданные аргументы на стандартный поток вывода

puts 'Hello, world!'
puts 42.even?
puts 2**10

Вывод в Ruby

  • puts выводит аргументы на стандартный поток вывода и добавляет перенос на следующую строку
  • print не добавляет перенос на новую строку
  • printf позволяет выводить информацию согласно формату, как в Си
  • p выводит информацию в формате, пригодном для отладки
  • pp выводит информацию для отладки сложных объектов, пригодной для восприятия разработчиком

Простейший метод

def say_goodnight(name)
  result = "Good night, " + name
  return result
end
# Вызываем метод
puts say_goodnight("John-Boy")
puts say_goodnight("Mary-Ellen")
  • Выражения на разных линиях не надо разделять с помощью точки с запятой
  • Комментарии начинаются с символа #
  • При объявлении переменные должны быть инициализрованы

Определение метода

  • Определение начинается с ключевого слова def
  • Затем следует имя метода, слова в названии которого разделены подчёркиваниями
  • В круглых скобках перечисляются аргументы
  • Тело метода завершается ключевым словом end
  • Результат работы возвращается с помощью return
def say_goodnight(name)
  result = "Good night, " + name
  return result
end

Вызов метода

  • Передавайте методу столько обязательных аргументов, сколько он ожидает
  • При вызове метода можно не указывать круглые скобки:
puts say_goodnight("John-Boy")
puts(say_goodnight("John-Boy"))

Обычно скобки опускаются только в самых простых случаях:

  • Если метод не имеет аргументов, не указывайте скобки
  • Если метод имеет аргументы, указывайте скобки за исключением стандартных методов вывода информации (puts, print и p)

Также следует использовать соглашения конкретных библиотек

Строки

  • Для определения строк обычно используются литералы
  • Литералы описываются с помощью одинарных и двойных кавычек
  • Строки в одинарных кавычках не обрабатываются
  • Обработка строк в двойных кавычках
    • Замена специальных символов (\n, \t)
    • Заполнение результатов выражений #{ ... }
  • В отличие от Java строки можно изменять

Вместо конкатенации строк используйте шаблонные строки

def say_goodnight(name)
  result = "Good night, #{name.capitalize}"
  return result
end

Возвращаемое значение

  • Для возвращения значения из метода в произвольный момент используется ключевое слово return
  • Результат вычисления последнего выражения в методе является его значением
def say_goodnight(name)
  "Good night, #{name.capitalize}"
end

return обычно используется когда результат становится известен заранее:

  • При возникновении ошибочного состояния
  • Когда действие не надо выполнять для данных аргументов
  • Когда аргументы не прошли проверку

Наименования элементов

Первый символ элемента приложения описывает его назначение

  • Имена локальных переменных, параметров методов и имена методов должны начинаться со строчной буквы или подчёркивания
  • Имена глобальных переменных начинаются со знака $
  • Имена переменных экземпляра, начинаются со знака @
  • Имена переменных класса, начинаются с @@
  • Имена классов, модулей и констант начинаются с заглавной буквы
  • Имена методов могут заканчиваться символами ?, !, =
  • Слова в именах переменных разделяются подчёркиваниями
  • Слова в названиях классов начинаются с заглавной буквы

Массивы и ассоциативные массивы

  • Массивы являются индексируемыми коллекциями
    • Обычные массивы индексируются числами
    • Ассоциативные массивы — любыми объектами
  • Коллекции всегда являются динамическими
  • В коллекции могут находится любые объекты

Массивы

a = [1, 'cat' , 3.14]
puts "Первый элемент #{a[0]}"
# Установим новое значение 3 элементу массива
a[2] = nil
puts "Массив сейчас #{a.inspect} "

Создание массивов

Объект nil

  • Во многих языках программирования есть понятие «отсутствующего значения», например null в Java
  • nil — объект, обозначающий отсутствие значения

Массивы из строк

a = ['ant' , 'bee' , 'cat' , 'dog' , 'elk']
a[0] # => "ant"
a[3] # => "dog"
# Создание такого же массива из строки
a = %w{ant bee cat dog elk}
a[0] # => "ant"
a[3] # => "dog"

Ассоциативные массивы

  • Для создания литералов ассоциативных массивов используются фигурные скобки
  • Элементами описания являются пары ключ-значение, разделённые «=>»: ключ => значение
  • Ключи должны быть уникальными для массива
  • Пары разделяются друг от друга запятыми
inst_section = {
  'cello' => 'string',
  'clarinet' => 'woodwind',
  'drum' => 'percussion'
}
print inst_section['cello']

Значение по умолчанию

  • Если обратиться к несуществующему элементу, тогда будет возвращено значение nil
  • Иногда желательно, чтобы массив возвращал осмысленное значение по умолчанию
# Создаём массив со значением по умолчанию
histogram = Hash.new(0)
histogram['ruby'] # => 0
histogram['ruby'] = histogram['ruby'] + 1
histogram['ruby'] # => 1

Проблема уникальных значений

Часто при написании приложения нам необходимо описать уникальные значения и обращаться к ним с помощью слов

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

NORTH = 1
"north"

Проблемами использования констант являются:

  • Мы не можем выполнить обратное преобразование надёжно
  • Пользователь может использовать значение вместо константы
  • Константы необходимо заранее объявить и подключить объявление во всех местах использования

Проблемой использования строк является то, что под каждую из них необходимо выделить отдельную память

Символы в Ruby

В Ruby есть специальный тип данных Symbol, который позволяет описать уникальное именованное значение

  • В отличие от констант их не надо определять заранее и они всегда будут уникальны
  • В отличие от констант они не имеют связанного с ними значения
  • В отличие от строк они не занимают оперативную память при использовании
  • В отличие от строк они быстрее сравниваются друг с другом
  • В отличие от строк и констант их стоит рассматривать как код, а не как данные

Символы создаются с помощью синтаксиса :north или :"north"

walk(:north)
def walk(direction)
  if direction == :north
    ...
end

Символы как ключи хешей

Символы часто используются в качестве ключей хешей

inst_section = {
  :cello => 'string',
  :clarinet => 'woodwind'
}

Поддержка таких хешей добавлена в синтаксис языка

inst_section = {
  cello: 'string',
  clarinet: 'woodwind'
}

Управляющие конструкции

Ruby поддерживает знакомые вам конструкции

  • if для описания условной логики
  • while для описания циклических действий
today = Time.now
if today.saturday?
  puts "Do chores around the house"
elsif today.sunday?
  puts "Relax"
else
  puts "Go to work"
end

Блок описания заканчивается ключевым словом end

Выражения в условиях

Любое выражение может являться условием. Ложными значениями являются только false и nil. Все остальные значения являются верными.

while weight < 100 && num_pallets <= 5
  pallet = next_pallet()
  weight += pallet.weight
  num_pallets += 1
end

Метод gets получает строку со стандартного потока ввода

При достижении конца потока gets возвращает nil

while line = gets
  puts line.downcase
end

Модификаторы выражений

Условные операторы могут выступать в роли модификатора выражения

if radiation > 3000
  puts "Danger, Will Robinson"
end
puts "Danger, Will Robinson" if radiation > 3000

Аналогично для операторов цикла

square = 4
while square < 1000
  square = square*square
end
square = square*square while square < 1000

Однако не стоит таким образом описывать сложные выражения

Нумерованные циклы

В Ruby также существует цикл for, который позволяет выполнить действие для элемента коллекции

for value in [1, 2, 3] do
  puts value
end

Однако для выполнения действий несколько раз удобнее воспользоваться методом times у целых чисел:

3.times do |value|
  puts value
end

Регулярные выражения

  • Регулярное выражение — способ описать шаблон, которому должна соответствовать строка
  • Реализованы как часть языка, а не в виде библиотеки
  • Регулярные выражения — объекты
  • /Perl|Python/ — либо строка целиком Perl, либо целиком Python
  • /ab+c/a, за которым следует несколько b, затем c
  • Выражения можно сравнивать со строками с помощью ~=
  • Метод sub строки позволяет заменять подстроки
line = "Perl and Python are scripting languages"
newline = line.sub(/Perl/, 'Ruby')
newerline = newline.gsub(/Python/, 'Ruby')

Блоки и итераторы

  • Вы можете ассоциировать некоторый блок кода с методом
  • Блоки могут быть использованы для реализации
    • Обратных вызовов callbacks
    • Передачи управления из метода блоку
  • Блоки можно сравнить с анонимным методом

Примеры блоков

{ puts "Hello" } # Для одного выражения
do # Для нескольких выражений
  club.enroll(person)
  person.socialize
end

Ассоциация блока с методом

Для ассоциации блока с методом его необходимо описать сразу после вызова метода

greet { puts "Hi" }
verbose_greet("Dave") { puts "Hi" }

Вызов ассоциированного блока кода

Вызов осуществляется с помощью ключевого слова yeild

def call_block
  puts "Start of the method"
  yield
  puts "End of the method"
end
call_block { puts "In the block" }

Аргументы блоков

  • Блокам могут быть переданы аргументы
  • Аргументы описываются между вертикальными линиями
def who_says_what
  yield ("Dave", "hello")
  yield ("Andy", "goodbye")
end

who_says_what do |person, phrase|
  puts "#{person} says #{phrase}"
end

Итераторы

Итератором называется метод коллекций, принимающий блок

Под коллекцией понимаются как встроенные классы массива и хеша, так и классы, созданные пользователями

Коллекции поддерживают множество итераторов

animals = %w(ant bee cat dog)
animals.each { |animal| puts animal }
3.upto(6) { |number| print number }
('a'..'e').each { |character| print character }

Примеры полезных итераторов

data = [5, 66, 13, 24, 46]
# Все элементы с позицией
data.each_with_index do |number, index|
    puts "#{index}: #{number}"
end
# Все ли элементы удовлетворяют условию: являются нечётными
data.all? { |number| number.odd? }
# Создать новый массив на основе текущего: квадраты чисел
data.map { |number| number**2 }
# Посчитать общую характеристику: сумму чисел
data.reduce { |memo, number| number + memo }
# Выбрать часть элементов массива
data.select { |number| number > 40 }

Обработка аргументов командной строки

Доступ к списку аргументов

Массив ARGV содержит список строк, которые были переданы в виде аргументов командной строки

puts "You gave  #{ARGV.size}  arguments"
p ARGV

Обработка файлов, переданных приложению

Если приложение ориентировано на обработку текстовых файлов, то пути к ним можно передать в качестве аргументов

В приложении к ним можно обратиться как к одному большому файлу через специальный объект ARGF