Ruby/Подробнее о методах: различия между версиями

Содержимое удалено Содержимое добавлено
м <source> -> <syntaxhighlight> (phab:T237267)
Строка 7:
Благодаря тому, что указание класса-носителя метода необязательно, на Ruby можно программировать в функциональном стиле, не заботясь о создании класса-«носителя» для каждой группы методов. Метод создаётся с помощью ключевых слов <code>def … end</code>.
 
<sourcesyntaxhighlight lang="ruby">def sum(a, b)
return a + b
end
 
sum(10, 2) #=> 12</sourcesyntaxhighlight>
 
Ruby по умолчанию возвратит из метода результат последнего выполненного выражения, поэтому в конце метода или в условных конструкциях слово <code>return</code> можно опускать. Поскольку методы могут быть переопределены в процессе выполнения программы, можно «на ходу» переписать метод так:
 
<sourcesyntaxhighlight lang="ruby">def sum(a, b)
a + b
end
 
sum(10, 2) #=> 12</sourcesyntaxhighlight>
 
<!-- Про case и if планируется рассказывать в самом конце, а программу is_odd можно переписать как !(number%2).zero?
Строка 25:
Условное выражение и выражение <code>case</code> также возвращают свой результат, поэтому если метод проверяет условия, удобно написать его примерно так:
 
<sourcesyntaxhighlight lang="ruby">
def is_odd(number)
case number % 2
Строка 36:
 
is_odd(4) #=> false
is_odd(3) #=> true</sourcesyntaxhighlight>
 
И <code>return</code>, и промежуточное присвоение можно опустить. Подобные свойства Ruby позволяют делать программы невероятно сжатыми без существенной потери читаемости.
Строка 45:
У методов могут быть необязательные аргументы. Для этого им нужно присвоить значение, которое следует применять «по умолчанию»:
 
<sourcesyntaxhighlight lang="ruby">def sum(a, b = 5)
a + b
end
 
sum(10, 2) #=> 12
sum(10) #=> 15</sourcesyntaxhighlight>
 
==== Методы с восклицательным и вопросительным знаком ====
Строка 60:
Обычно программист, чтобы проверить, пуст ли массив, посмотрит его длину:
 
<sourcesyntaxhighlight lang="ruby">arr = []
if arr.length == 0
puts "empty"
else
puts "not empty"
end</sourcesyntaxhighlight>
 
У массива в Ruby есть метод-предикат <code>.empty?</code>, возвращающий <code>true</code> если массив пуст.
 
<sourcesyntaxhighlight lang="ruby">arr = []
if arr.empty?
puts "empty"
else
puts "not empty"
end</sourcesyntaxhighlight>
 
Если вы реализуете программу, которой будут пользоваться другие, считается хорошим тоном реализовывать методы-предикаты.
Строка 80:
Ещё одна их прелесть — сочетание с модификаторами выражения:
 
<sourcesyntaxhighlight lang="ruby">arr = [1, 2, 3]
p "Array has something" if arr.any?</sourcesyntaxhighlight>
 
Методы с восклицательным знаком на конце меняют объект, к которому привязаны.
 
<sourcesyntaxhighlight lang="ruby">string = " Some string with spaces "
string.strip! #=> "Some string with spaces" — возвращает результат операции…
string #=> "Some string with spaces" …и меняет состояние объекта-адресата</sourcesyntaxhighlight>
 
==== Методы присваивания ====
Строка 95:
Знак равенства в конце названия метода означает, что этот метод присваивает свойству объекта значение:
 
<sourcesyntaxhighlight lang="ruby">class Bottle
def capacity
@capacity
Строка 106:
 
bottle = Bottle.new
bottle.capacity = 10 #=> 10, автоматически преобразуется в вызов метода capacity=</sourcesyntaxhighlight>
 
второй метод
 
<sourcesyntaxhighlight lang="ruby">
class Bottle
attr_accessor :capacity, :contents
Строка 122:
bottle.capacity #=> 0.5
bottle.contents #=> "milk"
</syntaxhighlight>
</source>
.......
 
Строка 129:
Операторы (умножение, деление, возведение в степень и так далее — вплоть до сравнения!) — тоже методы. Например:
 
<sourcesyntaxhighlight lang="ruby">class Broom
def+(another)
12 + another
Строка 136:
 
whisk = Broom.new
whisk + 10 #=> 22</sourcesyntaxhighlight>
 
Это применяется, например, во встроенном в Ruby объекте <code>Time</code>. При прибавлении к нему целого числа он возвращает новый объект <code>Time</code> с добавленным количеством секунд:
 
<sourcesyntaxhighlight lang="ruby">t = Time.now #=> Sun Jun 11 20:29:51
t + 60 #=> Sun Jun 11 20:30:51 — на минуту позже</sourcesyntaxhighlight>
 
То же самое характерно для имеющегося в стандартной библиотеке класса <code>Date</code>, но, в отличие от <code>Time</code>, он считает дни вместо секунд.
 
<sourcesyntaxhighlight lang="ruby">require 'date'
d = Date.today #=> Sun Jun 11
d + 1 #=> Mon Jun 12 — на день позже</sourcesyntaxhighlight>
 
==== «Поглощение» аргументов метода ====
Строка 153:
Можно «свернуть» аргументы с помощью звёздочки — тогда метод получит массив в качестве аргумента:
 
<sourcesyntaxhighlight lang="ruby">def sum(*members)
members[0] + members[1]
end
 
sum(10, 2) #=> 12</sourcesyntaxhighlight>
 
Поскольку теперь наш метод принимает неограниченное количество элементов, мы можем пользоваться ими как массивом и в теле функции:
 
<sourcesyntaxhighlight lang="ruby">def sum(*members)
initial = 0
members.collect{ | item | initial += item }
Строка 168:
 
sum(10, 2) #=> 12
sum(10, 2, 12, 34) #=> 58</sourcesyntaxhighlight>
 
Можно разделить аргументы на обязательные и необязательные, просто пометив последний аргумент «звёздочкой». Если методу будут переданы только обязательные аргументы, в переменной «со звёздочкой» в теле метода будет пустой массив.
Строка 174:
Звёздочкой полезно пользоваться и когда нужно передать методу аргументы, но не хочется указывать их по отдельности. Следуя тому же примеру:
 
<sourcesyntaxhighlight lang="ruby">array_to_sum = [10, 2, 12, 34]
sum(*array_to_sum) #=> 58</sourcesyntaxhighlight>
 
=== Подробнее о замыканиях ===
Строка 198:
При передаче замыкания методу, замыкание следует за ''скобками аргументов''.
 
<sourcesyntaxhighlight lang="ruby">puts (1..3).map(){ "Вау!" } # выводит Вау! три раза</sourcesyntaxhighlight>
 
Поскольку при отсутствии аргументов скобки необязательны, простейшая запись такова:
 
<sourcesyntaxhighlight lang="ruby">puts (1..3).map{ "Вау!" } # выводит Вау! три раза</sourcesyntaxhighlight>
 
Важно помнить, что замыкание использует методы и переменные, указанные при его создании, то есть замыкание ''захватывает контекст'', но переменные, определённые в замыкании, остаются для него локальными!
 
<sourcesyntaxhighlight lang="ruby">puts (1..3).map{ word = 'Вау!'; word } # выводит Вау! три раза, поскольку замыкание знает
# переменную word, и она определена в нём
 
puts word # вызывает сообщение об ошибке —
# вне замыкания об этой переменной ничего не известно</sourcesyntaxhighlight>
 
Необходимо заметить, что если переменная была определена ранее, то она может использоваться внутри замыкания:
<sourcesyntaxhighlight lang="ruby">word=""
puts (1..3).map{ word = 'Вау!'; word }
puts word # выведет Вау!
</syntaxhighlight>
</source>
или
<sourcesyntaxhighlight lang="ruby">
i=0
(1..3).map do |x|
Строка 224:
end
puts i # выведет 3
</syntaxhighlight>
</source>
 
Как уже упоминалось, если замыкание многострочное, целесообразней пользоваться формой с <code>do … end</code>:
 
<sourcesyntaxhighlight lang="ruby">
(1..3).map do
random_number = rand()
puts "Вау — случайный номер!\n" + random_number.to_s
end</sourcesyntaxhighlight>
 
==== Замыкания принимают аргументы ====
Строка 238:
Другое замечательное свойство замыканий — они, как и функции, могут принимать аргументы. В таком случае метод, которому передано замыкание, сам «решает», что это замыкание получит в качестве аргумента. Например, уже продемонстрированный метод <code>.map</code> ещё и передаёт замыканию аргумент, который можно захватить следующим образом:
 
<sourcesyntaxhighlight lang="ruby">puts (1..3).map do |i|
i
end</sourcesyntaxhighlight>
{{Внимание|Данный листинг не будет работать на Ruby версии 1.9.2}}
 
Строка 251:
Ключевое слово <code>yield</code> в методе открывает раздвижные двери, впускающие аргумент[ы] в замыкание.
 
<sourcesyntaxhighlight lang="ruby">def twice
yield "и раз"
yield "и два"
Строка 257:
 
twice { |words| puts "!!! " + words } #=> !!! и раз
#=> !!! и два</sourcesyntaxhighlight>
 
При этом строка будет передаваться замыканию в переменную <code>words</code> при каждом выполнении.
Строка 263:
Если замыкание обязательно, следует пометить его как последний аргумент метода и в начале аргумента добавить [[w:Aмперсанд|амперсанд]]:
 
<sourcesyntaxhighlight lang="ruby">def twice(&closure)
yield "и раз"
yield "и два"
end
 
twice #=> Ошибка LocalJumpError - отсутствует замыкание</sourcesyntaxhighlight>
 
Последнее утверждение не совсем верно. Даже совсем не верно. Указания переменной замыкания недостаточно для контроля наличия входного замыкания. Дело в том, что в случае, если замыкание не вызывается, то и ошибки не будет:
 
<sourcesyntaxhighlight lang="ruby">def func(a, &closure)
return a if a
yield "и раз"
Строка 279:
 
func true #=> true
func false #=> LocalJumpError: no block given</sourcesyntaxhighlight>
 
Более того, вызов функции <code>twice</code> без указания замыкания также приведёт к ошибке. Таким образом, гораздо лучше вместо введения обязательного параметра задавать замыкание по-умолчанию:
 
<sourcesyntaxhighlight lang="ruby">def func(a, &closure)
return a if a
closure ||= lambda{ |words| puts "!!! " + words }
Строка 295:
 
func(false){ |words| puts "??? " + words } #=> ??? и раз
#=> ??? и два</sourcesyntaxhighlight>
 
Здесь <code>lambda</code> — пустая функция, а <code>closure.call</code> — явный способ вызова замыкания на выполнение.
Строка 301:
Замыкание можно также передать другому методу, просто указав его как последний аргумент с амперсандом:
 
<sourcesyntaxhighlight lang="ruby">def writing_to(file, &closure)
File.open(file, 'w', &closure)
end</sourcesyntaxhighlight>
 
Наконец, на десерт, напишем свой inject.
<sourcesyntaxhighlight lang="ruby">
class Array
def inject2 ( buf )
Строка 318:
p [1,2,3].inject2(10){|b,e| b + e} #=> 16
p [1,2,3].inject(10){|b,e| b + e} #=> 16
</syntaxhighlight>
</source>
 
==== Некоторые применения замыканий ====
Строка 326:
Типичное применение замыкания — когда после выполнений некой операции нужно «вынести мусор»: закрыть открытый ресурс или отсоединиться от сети. Предположим, что мы пишем метод для интернет-системы. При этом мы хотим выполнить несколько операций. Но чтобы их выполнить, нужно подключить пользователя к Сети. После того, как операции завершились, надо его так же незаметно отключить.
 
<sourcesyntaxhighlight lang="ruby">connected{ download_email }</sourcesyntaxhighlight>
 
В данном случае мы пишем только замыкание с <code>download_email</code>, все заботы по открытию (а главное — закрытию) соединения возьмёт на себя метод <code>connected</code>:
 
<sourcesyntaxhighlight lang="ruby">def connected
connect_to_internet
result = yield
disconnect
result
end</sourcesyntaxhighlight>
 
В данном случае мы сохраняем то, что вернуло замыкание, в метод, закрываем соединение и возвращаем результат замыкания как свой собственный.
Строка 343:
Если воспользоваться встроенной проверкой исключений, то метод принимает такой вид:
 
<sourcesyntaxhighlight lang="ruby">def connected
connect_to_internet
begin
Строка 351:
end
result
end</sourcesyntaxhighlight>
 
Тогда, даже если метод вызовет ошибку, соединение всё равно будет закрыто.
Строка 363:
Студент МЭТТ Ширяев Денис на одном из зачётов предложил использовать метод <code>.size</code> в качестве итератора. Он использовал его для подсчёта количества элементов массива, удовлетворяющих условию. По сути, он предложил укоротить связку <code>.find_all{ … }.size</code>. Вот как будет выглядеть программа подсчёта количества чётных элементов массива:
 
<sourcesyntaxhighlight lang="ruby">array = [1, 2, 3, 4, 5, 6]
array.size{ |i| (i % 2).zero? } #=> 3</sourcesyntaxhighlight>
 
Чтобы заставить работать данную программу, необходимо перед использованием метода <code>.size</code> переопределить его, написав следующий код, который будет реализовывать эту функциональность:
 
<sourcesyntaxhighlight lang="ruby">class Array
def size(&closure)
closure ? inject(0){ |count, elem| (yield elem) ? count + 1 : count } : length
end
end</sourcesyntaxhighlight>
 
Метод реализован только для массивов, но возможно его добавление к хешам или строкам.
Строка 380:
Студенты часто возмущаются: почему, чтобы получить случайное число от 3 до 6 нужно писать нечто невнятное вида:
 
<sourcesyntaxhighlight lang="ruby">3 + rand(4)</sourcesyntaxhighlight>
 
Откуда чего берётся? Почему нельзя написать проще? Например вот так:
 
<sourcesyntaxhighlight lang="ruby">(3..6).rand</sourcesyntaxhighlight>
 
Действительно, почему? Давайте добавим такую функциональность к классу Range:
 
<sourcesyntaxhighlight lang="ruby">class Range
def rand
first + Kernel.rand(last - first + (exclude_end? ? 0 : 1))
end
end</sourcesyntaxhighlight>
 
Для проверки можно выполнить следующий код:
 
<sourcesyntaxhighlight lang="ruby">p Array.new(100){ (3..6).rand }.uniq.sort #=> [3, 4, 5, 6]</sourcesyntaxhighlight>
 
Что и требовалось реализовать. Кстати, данная реализация имеет один изъян: для строковых диапазонов метод <code>Range#rand</code> будет выдавать ошибку. Решается проблема достаточно просто. Надо реализовать <code>Array#rand</code> (получение случайного элемента массива), а внутри <code>Range#rand</code> вызывать связку <code>.to_a.rand</code>. Теперь тоже самое, но на Ruby:
 
<sourcesyntaxhighlight lang="ruby">class Array
def rand
self[Kernel.rand(size)]
Строка 410:
to_a.rand
end
end</sourcesyntaxhighlight>
 
Или еще проще (без изменения класса Array):
 
<sourcesyntaxhighlight lang="ruby">class Range
def rand
to_a.sample
end
end</sourcesyntaxhighlight>
 
Для проверки выполним следующий код:
 
<sourcesyntaxhighlight lang="ruby">p Array.new(100){ ("a".."c").rand }.uniq.sort #=> ["a", "b", "c"]</sourcesyntaxhighlight>
 
Странно, но, видимо, всё работает!
Строка 432:
Важно помнить, что в Ruby все типы являются объектами, даже сами классы. Каждый класс до конца выполнения программы остаётся открытым, а это значит, что в любой тип можно добавить собственные методы (или изменить поведение существующих). Каждый класс можно определять постепенно, в нескольких частях программы:
 
<sourcesyntaxhighlight lang="ruby">class Broom
def sweep
end
Строка 444:
end
 
Broom.instance_methods #=> […, "sweep", …, "wash_lavatory_pan", …]</sourcesyntaxhighlight>
 
Метод <code>.instance_methods</code> возвращает массив, который содержит имена методов, которые можно вызвать.
Строка 452:
Простейший пример — добавление метода классу <code>String</code>, выводящий только согласные буквы из строки:
 
<sourcesyntaxhighlight lang="ruby">class String
def consonants
cons = []
Строка 460:
end
 
"Crazy brown fox jumps over a lazy dog".consonants #=> "Crzbwnfxjmpsvrldg"</sourcesyntaxhighlight>
 
Операция расширения класса (добавление нового метода к существующему) по сути не отличается от создания нового класса.
Строка 466:
У объектов в Ruby есть методы класса и методы экземпляра. В нашем примере <code>consonants</code> — это именно метод экземпляра. При создании нового класса или изменении существующего создать метод класса можно, начав его имя с имени класса или с <code>self</code> и точки:
 
<sourcesyntaxhighlight lang="ruby">class String
def self.consonants_from(string)
cons = []
Строка 474:
end
 
String.consonants_from("Crazy fox jumps over a lazy dog") #=> "Crzbwnfxjmpsvldg"</sourcesyntaxhighlight>
 
Одним из специфических свойств Ruby является то, что классы сами по себе — экземпляры класса <code>Class</code>, и с ними можно работать как с обычными объектами. Специальный синтаксис для доступа к методам класса в Ruby не нужен. Классы можно хранить в переменных, передавать методам и так далее.
Строка 482:
Проиллюстрируем это простым примером. Как мы знаем, у класса <code>File</code> есть метод <code>open</code>. Создадим метод у класса <code>File</code>, дающий нам доступ к временному файлу, создаваемому в момент выполнения кода. Это такой же метод, но открывающий только файлы из директории <code>/tmp</code>:
 
<sourcesyntaxhighlight lang="ruby">
class File
def self.temporary(&closure)
Строка 497:
end
 
File.temporary { |f| f << "Some info" } #=> #<File:/Tests/(irb)_1151198720.tmp (closed)></sourcesyntaxhighlight>
 
{{info|Для управления временными файлами в Ruby существует класс <code>Tempfile</code>. Помимо других достоинств он гарантирует, что созданные временные файлы по завершении программы
Строка 506:
Добавим к классу <code>File</code> метод <code>myself</code>:
 
<sourcesyntaxhighlight lang="ruby">class File
class << self
def myself
Строка 512:
end
end
end</sourcesyntaxhighlight>
 
Если нужно добавить метод только к конкретному экземпляру, нужно выйти на его айгенкласс:
 
<sourcesyntaxhighlight lang="ruby">
string = "Crazy brown fox jumps over a lazy dog"
other_string = "Three black witches"
Строка 527:
 
string.vowels #=> "ayoue"
other_string.vowels #=> NoMethodError: undefined method `vowels' for …</sourcesyntaxhighlight>
 
Возможность добавлять и изменять устройство уже существующих классов — одно из основных свойств Ruby, обеспечивающих великую гибкость языка. Часто бывает, что метод возвращает не тот результат, который нам нужен — тогда при его изменении все программы, обращающиеся к данному методу будут получать изменённый результат.
Строка 535:
Как ни странно, изредка программисту приходится взять на себя позицию разрушителя — удалить существующий метод или константу. Метод <code>undef</code> позволяет сделать это:
 
<sourcesyntaxhighlight lang="ruby">class Broom
def sweep
"Метём!"
Строка 557:
 
broom.sweep #=> "Метём!"
birch_broom.sweep #=> Ошибка NoMethodError — такого метода нет, хоть он и был унаследован</sourcesyntaxhighlight>
 
Уничтожение класса несколько сложнее, но тоже возможно:
 
<sourcesyntaxhighlight lang="ruby">Object.send(:remove_const, :Broom)</sourcesyntaxhighlight>
 
После этого <code>Broom</code> будет существовать только для объекта-экземпляра:
 
<sourcesyntaxhighlight lang="ruby">class Broom
end
 
Строка 573:
 
Broom #=> Ошибка NameError: неизвестная константа Broom
whisk.class #=> Broom, всё ещё существует для экземпляра</sourcesyntaxhighlight>
 
Это свойство Ruby крайне полезно, если нужно создать класс, наследующий от другого, но при этом имеющий другого родителя. Например:
 
<sourcesyntaxhighlight lang="ruby"># В чужой программе:
class Connection < Socket
# много-много методов…
Строка 592:
end
 
# В итоге чужая программа будет использовать созданный нами Connection</sourcesyntaxhighlight>
 
Полная замена чужих классов довольно опасна, но бывают ситуации, когда эта методика спасает.
Строка 609:
 
Писать класс не так уж и сложно. Простейший класс будет выглядеть так:
<sourcesyntaxhighlight lang="ruby">
class NewClass
def initialize(a,b,c)
Строка 630:
#=> b = 20
#=> c = 30
</syntaxhighlight>
</source>
 
=== Наследовать или перемешать? ===