Скрытые возможности Ruby
Продолжая тему "Скрытые возможности...", давайте расскажем о менее известных, но полезных функциях языка программирования Ruby.
Попытайтесь ограничить это обсуждение ядром Ruby, без каких-либо вещей Ruby on Rails.
Смотрите также:
- Скрытые возможности C#
- Скрытые возможности Java
- Скрытые возможности JavaScript
- Скрытые возможности Ruby on Rails
- Скрытые возможности Python
(Пожалуйста, только одна скрытая функция в ответ.)
Спасибо
46 ответов
Начиная с Ruby 1.9, ProC#=== является псевдонимом вызова ProC#, что означает, что объекты Proc могут использоваться в выражениях case следующим образом:
def multiple_of(factor)
Proc.new{|product| product.modulo(factor).zero?}
end
case number
when multiple_of(3)
puts "Multiple of 3"
when multiple_of(7)
puts "Multiple of 7"
end
У Питера Купера есть хороший список рубиновых трюков. Возможно, мой любимый из них - это возможность перечисления как отдельных предметов, так и коллекций. (То есть обрабатывайте объект, не являющийся коллекцией, как коллекцию, содержащую только этот объект.) Это выглядит так:
[*items].each do |item|
# ...
end
Не знаю, насколько это скрыто, но я нашел это полезным, когда нужно сделать хэш из одномерного массива:
fruit = ["apple","red","banana","yellow"]
=> ["apple", "red", "banana", "yellow"]
Hash[*fruit]
=> {"apple"=>"red", "banana"=>"yellow"}
Один трюк, который мне нравится, это использовать сплат (*
) расширитель объектов, кроме массивов. Вот пример соответствия регулярному выражению:
match, text, number = *"Something 981".match(/([A-z]*) ([0-9]*)/)
Другие примеры включают в себя:
a, b, c = *('A'..'Z')
Job = Struct.new(:name, :occupation)
tom = Job.new("Tom", "Developer")
name, occupation = *tom
Вау, никто не упомянул оператор триггера:
1.upto(100) do |i|
puts i if (i == 3)..(i == 15)
end
Одна из замечательных особенностей ruby заключается в том, что вы можете вызывать методы и запускать код в тех местах, где другие языки будут недовольны, например, в определениях методов или классов.
Например, чтобы создать класс, который имеет неизвестный суперкласс до времени выполнения, т.е. является случайным, вы можете сделать следующее:
class RandomSubclass < [Array, Hash, String, Fixnum, Float, TrueClass].sample
end
RandomSubclass.superclass # could output one of 6 different classes.
Это использует 1,9 Array#sample
метод (только в 1.8.7, см. Array#choice
), и пример довольно надуманный, но вы можете увидеть силу здесь.
Еще один интересный пример - возможность выставлять значения параметров по умолчанию, которые не являются фиксированными (как часто требуют другие языки):
def do_something_at(something, at = Time.now)
# ...
end
Конечно, проблема с первым примером состоит в том, что он оценивается во время определения, а не во время вызова. Итак, после выбора суперкласса он остается этим суперклассом до конца программы.
Однако во втором примере каждый раз, когда вы звоните do_something_at
, at
переменная будет временем, когда метод был вызван (ну, очень, очень близко к нему)
Еще одна крошечная функция - конвертировать Fixnum
в любую базу до 36:
>> 1234567890.to_s(2)
=> "1001001100101100000001011010010"
>> 1234567890.to_s(8)
=> "11145401322"
>> 1234567890.to_s(16)
=> "499602d2"
>> 1234567890.to_s(24)
=> "6b1230i"
>> 1234567890.to_s(36)
=> "kf12oi"
И, как прокомментировал Хью Уолтерс, преобразование в другую сторону так же просто:
>> "kf12oi".to_i(36)
=> 1234567890
Хеши со значениями по умолчанию! Массив в этом случае.
parties = Hash.new {|hash, key| hash[key] = [] }
parties["Summer party"]
# => []
parties["Summer party"] << "Joe"
parties["Other party"] << "Jane"
Очень полезно в метапрограммировании.
Загрузите исходный код Ruby 1.9 и выпустите make golf
тогда вы можете делать такие вещи:
make golf
./goruby -e 'h'
# => Hello, world!
./goruby -e 'p St'
# => StandardError
./goruby -e 'p 1.tf'
# => 1.0
./goruby19 -e 'p Fil.exp(".")'
"/home/manveru/pkgbuilds/ruby-svn/src/trunk"
Прочитайте golf_prelude.c
для более аккуратных вещей, скрывающихся
Еще одно забавное дополнение в функциональности 1.9 Proc - это ProC#curry, который позволяет превратить Proc, принимающий n аргументов, в один, принимающий n-1. Здесь он объединен с советом ProC#===, о котором я упоминал выше:
it_is_day_of_week = lambda{ |day_of_week, date| date.wday == day_of_week }
it_is_saturday = it_is_day_of_week.curry[6]
it_is_sunday = it_is_day_of_week.curry[0]
case Time.now
when it_is_saturday
puts "Saturday!"
when it_is_sunday
puts "Sunday!"
else
puts "Not the weekend"
end
Булевы операторы на не булевых значениях.
&&
а также ||
Оба возвращают значение последнего оцененного выражения.
Вот почему ||=
обновит переменную со значением, возвращаемым выражением справа, если переменная не определена. Это явно не задокументировано, но общеизвестно.
Тем не менее &&=
не так широко известно о.
string &&= string + "suffix"
эквивалентно
if string
string = string + "suffix"
end
Это очень удобно для деструктивных операций, которые не должны продолжаться, если переменная не определена.
Функция Symbol#to_proc, которую предоставляет Rails, действительно классная.
Вместо
Employee.collect { |emp| emp.name }
Ты можешь написать:
Employee.collect(&:name)
И последнее: в ruby вы можете использовать любой символ, который хотите разделить. Возьмите следующий код:
message = "My message"
contrived_example = "<div id=\"contrived\">#{message}</div>"
Если вы не хотите избегать двойных кавычек в строке, вы можете просто использовать другой разделитель:
contrived_example = %{<div id="contrived-example">#{message}</div>}
contrived_example = %[<div id="contrived-example">#{message}</div>]
Помимо того, что вам не нужно избегать разделителей, вы можете использовать эти разделители для более хороших многострочных строк:
sql = %{
SELECT strings
FROM complicated_table
WHERE complicated_condition = '1'
}
Я считаю, что использование команды define_method для динамической генерации методов довольно интересно и не так хорошо известно. Например:
((0..9).each do |n|
define_method "press_#{n}" do
@number = @number.to_i * 10 + n
end
end
Приведенный выше код использует команду define_method для динамического создания методов с "press1" по "press9". Вместо того, чтобы вводить все 10 методов, которые по сути содержат один и тот же код, команда define method используется для генерации этих методов на лету по мере необходимости.
Используйте объект Range в качестве бесконечного ленивого списка:
Inf = 1.0 / 0
(1..Inf).take(5) #=> [1, 2, 3, 4, 5]
Более подробная информация здесь: http://banisterfiend.wordpress.com/2009/10/02/wtf-infinite-ranges-in-ruby/
module_function
Методы модуля, объявленные как module_function, создадут свои копии как частные методы экземпляра в классе, который включает в себя модуль:
module M
def not!
'not!'
end
module_function :not!
end
class C
include M
def fun
not!
end
end
M.not! # => 'not!
C.new.fun # => 'not!'
C.new.not! # => NoMethodError: private method `not!' called for #<C:0x1261a00>
Если вы используете module_function без каких-либо аргументов, то все методы модуля, которые идут после оператора module_function, автоматически сами становятся module_functions.
module M
module_function
def not!
'not!'
end
def yea!
'yea!'
end
end
class C
include M
def fun
not! + ' ' + yea!
end
end
M.not! # => 'not!'
M.yea! # => 'yea!'
C.new.fun # => 'not! yea!'
Предупреждение: этот предмет был признан самым ужасным хакером 2008 года, поэтому используйте его с осторожностью. На самом деле, избегайте этого, как чумы, но это, безусловно, скрытый рубин.
Суператоры добавляют новых операторов в Ruby
Вы когда-нибудь хотели сверхсекретного оператора рукопожатия для какой-то уникальной операции в вашем коде? Как играть в гольф код? Попробуйте операторы, такие как -~+~- или<--- Последний из них используется в примерах для изменения порядка элементов.
Я не имею ничего общего с проектом Superators Project, кроме того, что восхищаюсь им.
Я опаздываю на вечеринку, но:
Вы можете легко взять два массива одинаковой длины и превратить их в хеш, один из которых содержит ключи, а другой - значения:
a = [:x, :y, :z]
b = [123, 456, 789]
Hash[a.zip(b)]
# => { :x => 123, :y => 456, :z => 789 }
(Это работает, потому что Array#zip "архивирует" значения из двух массивов:
a.zip(b) # => [[:x, 123], [:y, 456], [:z, 789]]
И Hash[] может взять именно такой массив. Я видел, как люди это делают:
Hash[*a.zip(b).flatten] # unnecessary!
Что дает тот же результат, но сплат и сплющение совершенно не нужны - возможно, их не было в прошлом?)
Авто-живительные хэши в Ruby
def cnh # silly name "create nested hash"
Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}
end
my_hash = cnh
my_hash[1][2][3] = 4
my_hash # => { 1 => { 2 => { 3 =>4 } } }
Это может быть чертовски удобно.
Уничтожение массива
(a, b), c, d = [ [:a, :b ], :c, [:d1, :d2] ]
Куда:
a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]
Используя эту технику, мы можем использовать простое присваивание, чтобы получить нужные значения из вложенного массива любой глубины.
Class.new()
Создайте новый класс во время выполнения. Аргумент может быть производным классом, а блок является телом класса. Вы также можете посмотреть на const_set/const_get/const_defined?
чтобы ваш новый класс был правильно зарегистрирован, чтобы inspect
печатает имя вместо числа.
Не то, что вам нужно каждый день, но довольно удобно, когда вы делаете.
Большая магия, которую вы видите в Rubyland, связана с метапрограммированием, которое заключается в простом написании кода, который пишет код для вас. Руби attr_accessor
, attr_reader
, а также attr_writer
Все это простое метапрограммирование, в котором они создают два метода в одной строке, следуя стандартному шаблону. Rails делает много метапрограммирования со своими методами управления отношениями, такими как has_one
а также belongs_to
,
Но довольно просто создать свои собственные трюки метапрограммирования, используя class_eval
выполнить динамически написанный код.
В следующем примере объект-оболочка позволяет перенаправлять определенные методы во внутренний объект:
class Wrapper
attr_accessor :internal
def self.forwards(*methods)
methods.each do |method|
define_method method do |*arguments, &block|
internal.send method, *arguments, &block
end
end
end
forwards :to_i, :length, :split
end
w = Wrapper.new
w.internal = "12 13 14"
w.to_i # => 12
w.length # => 8
w.split('1') # => ["", "2 ", "3 ", "4"]
Метод Wrapper.forwards
принимает символы для имен методов и сохраняет их в methods
массив. Затем для каждого из указанных мы используем define_method
создать новый метод, задачей которого является отправка сообщения, включая все аргументы и блоки.
Отличный ресурс для вопросов метапрограммирования - " Почему Лаки Стифф" Видит метапрограммирование ясно ".
Создать массив последовательных чисел:
x = [*0..5]
устанавливает х в [0, 1, 2, 3, 4, 5]
Использовать все, что отвечает ===(obj)
для сравнения случаев:
case foo
when /baz/
do_something_with_the_string_matching_baz
when 12..15
do_something_with_the_integer_between_12_and_15
when lambda { |x| x % 5 == 0 }
# only works in Ruby 1.9 or if you alias Proc#call as Proc#===
do_something_with_the_integer_that_is_a_multiple_of_5
when Bar
do_something_with_the_instance_of_Bar
when some_object
do_something_with_the_thing_that_matches_some_object
end
Module
(и поэтому Class
), Regexp
, Date
и многие другие классы определяют метод экземпляра:===(другой), и все они могут быть использованы.
Спасибо Фаррелу за напоминание Proc#call
будучи псевдонимом как Proc#===
в Ruby 1.9.
"Рубиновый" двоичный файл (по крайней мере, MRI) поддерживает множество переключателей, которые сделали Perl One-Liner довольно популярным.
Значительные из них:
- -n Устанавливает внешний цикл с помощью просто "get", который волшебным образом работает с данным именем файла или STDIN, устанавливая каждую строку чтения в $ _
- -p Аналогично -n, но с автоматическим
put
s в конце каждой итерации цикла - -a Автоматический вызов.split в каждой строке ввода, хранится в $ F
- -i Редактирование входных файлов на месте
- -l Автоматический вызов.chomp при вводе
- -e выполнить кусок кода
- -c Проверить исходный код
- -w с предупреждениями
Некоторые примеры:
# Print each line with its number:
ruby -ne 'print($., ": ", $_)' < /etc/irbrc
# Print each line reversed:
ruby -lne 'puts $_.reverse' < /etc/irbrc
# Print the second column from an input CSV (dumb - no balanced quote support etc):
ruby -F, -ane 'puts $F[1]' < /etc/irbrc
# Print lines that contain "eat"
ruby -ne 'puts $_ if /eat/i' < /etc/irbrc
# Same as above:
ruby -pe 'next unless /eat/i' < /etc/irbrc
# Pass-through (like cat, but with possible line-end munging):
ruby -p -e '' < /etc/irbrc
# Uppercase all input:
ruby -p -e '$_.upcase!' < /etc/irbrc
# Same as above, but actually write to the input file, and make a backup first with extension .bak - Notice that inplace edit REQUIRES input files, not an input STDIN:
ruby -i.bak -p -e '$_.upcase!' /etc/irbrc
Не стесняйтесь гуглить "ruby one-liners" и "perl one-liners" для множества более полезных и практических примеров. По сути, это позволяет вам использовать ruby как довольно мощную замену awk и sed.
Метод send() - это метод общего назначения, который можно использовать с любым классом или объектом в Ruby. Если не переопределено, send() принимает строку и вызывает имя метода, для которого она передана. Например, если пользователь нажимает кнопку "Clr", строка "press_clear" будет отправлена методу send() и будет вызван метод "press_clear". Метод send() позволяет весело и динамично вызывать функции в Ruby.
%w(7 8 9 / 4 5 6 * 1 2 3 - 0 Clr = +).each do |btn|
button btn, :width => 46, :height => 46 do
method = case btn
when /[0-9]/: 'press_'+btn
when 'Clr': 'press_clear'
when '=': 'press_equals'
when '+': 'press_add'
when '-': 'press_sub'
when '*': 'press_times'
when '/': 'press_div'
end
number.send(method)
number_field.replace strong(number)
end
end
Подробнее об этой функции я расскажу в блог-обуви: приложение Simple-Calc
Обмани некоторый класс или модуль, говорящий, что он требует чего-то, что ему действительно не нужно:
$" << "something"
Это полезно, например, когда требуется A, который, в свою очередь, требует B, но нам не нужен B в нашем коде (и A не будет использовать его и в нашем коде):
Например, Backgroundrb's bdrb_test_helper requires
'test/spec'
, но вы не используете его вообще, так что в вашем коде:
$" << "test/spec"
require File.join(File.dirname(__FILE__) + "/../bdrb_test_helper")
Fixnum#to_s(base)
может быть действительно полезным в некоторых случаях. Одним из таких случаев является генерация случайных (псевдо) уникальных токенов путем преобразования случайного числа в строку, используя основание 36.
Токен длины 8:
rand(36**8).to_s(36) => "fmhpjfao"
rand(36**8).to_s(36) => "gcer9ecu"
rand(36**8).to_s(36) => "krpm0h9r"
Жетон длины 6:
rand(36**6).to_s(36) => "bvhl8d"
rand(36**6).to_s(36) => "lb7tis"
rand(36**6).to_s(36) => "ibwgeh"
private unless Rails.env == 'test'
# e.g. a bundle of methods you want to test directly
Выглядит как крутой и (в некоторых случаях) приятный / полезный хак / особенность Ruby.