Скрытые черты Ruby

Продолжая "Скрытые возможности..." meme, позвольте поделиться менее известными, но полезными функциями языка программирования Ruby.

Попытайтесь ограничить это обсуждение основным ядром Ruby без каких-либо материалов Ruby on Rails.

См. также:

(Пожалуйста, только одна скрытая функция для каждого ответа.)

Спасибо

Ответы

Ответ 1

От Ruby 1.9 ProС# === является псевдонимом для вызова ProС#, что означает, что объекты Proc могут использоваться в случаях, например:

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

Ответ 2

Питер Купер имеет хороший список в трюках Ruby. Возможно, мой любимый из них позволяет перечислять как отдельные предметы, так и коллекции. (То есть, рассматривайте объект, не являющийся объектом коллекции, как коллекцию, содержащую только этот объект.) Он выглядит так:

[*items].each do |item|
  # ...
end

Ответ 3

Не знаю, насколько это скрыто, но я нашел это полезным, когда нужно сделать Hash из одномерного массива:

fruit = ["apple","red","banana","yellow"]
=> ["apple", "red", "banana", "yellow"]

Hash[*fruit]    
=> {"apple"=>"red", "banana"=>"yellow"}

Ответ 4

Один трюк, который мне нравится, - использовать расширитель splat (*) на объектах, отличных от Массивов. Здесь пример регулярного выражения соответствует:

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

Ответ 5

Ничего себе, никто не упоминал оператора флип-флопа:

1.upto(100) do |i|
  puts i if (i == 3)..(i == 15)
end

Ответ 6

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

Например, для создания класса с неизвестным суперклассом до времени выполнения, т.е. является случайным, вы можете сделать следующее:

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 будет временем вызова метода (ну, очень близко к ней)

Ответ 7

Еще одна крошечная функция - конвертировать 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

Ответ 8

Хэши со значениями по умолчанию! Массив в этом случае.

parties = Hash.new {|hash, key| hash[key] = [] }
parties["Summer party"]
# => []

parties["Summer party"] << "Joe"
parties["Other party"] << "Jane"

Очень полезно при метапрограммировании.

Ответ 9

Загрузите источник 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 для более аккуратных вещей, скрывающихся.

Ответ 10

Другим интересным дополнением к функциям 1.9 Proc является ProС# curry, который позволяет вам преобразовывать аргументы, принимающие n, в один принимающий n-1. Здесь он сочетается с советом ProС# ===, упомянутым выше:

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

Ответ 11

Булевы операторы по небулевым значениям.

&& и ||

Оба возвращают значение последнего оцениваемого выражения.

Вот почему ||= обновит переменную с возвращенным значением с правой стороны, если переменная undefined. Это явно не документировано, а общеизвестно.

Однако &&= не так широко известен.

string &&= string + "suffix"

эквивалентно

if string
  string = string + "suffix"
end

Это очень удобно для деструктивных операций, которые не должны выполняться, если переменная undefined.

Ответ 12

Функция символа # to_proc, предоставляемая Rails, действительно крута.

Вместо

Employee.collect { |emp| emp.name }

Вы можете написать:

Employee.collect(&:name)

Ответ 13

Один последний - в рубине вы можете использовать любой символ, который вы хотите разграничить строки. Возьмите следующий код:

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'
}

Ответ 14

Я нахожу использование команды 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 методов, которые Essentailly содержат один и тот же код, команда define метода используется для генерации этих методов "на лету" по мере необходимости.

Ответ 16

module_function

Модульные методы, объявленные как module_function, будут создавать копии в виде методов private в классе, который включает в себя модуль:

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 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!'

Ответ 18

Предупреждение: этот предмет был проголосован # 1 Самый ужасный взлом 2008 года, поэтому используйте его с осторожностью. Собственно, избегайте его, как чумы, но это, безусловно, скрытый рубин.

Суператоры Добавьте новых операторов в Ruby

Вам нужен суперсекретный оператор рукопожатия для какой-то уникальной операции в вашем коде? Как играть в гольф? Попробуйте таких операторов, как   - ~ + ~ - или   < --- Этот последний используется в примерах для изменения порядка элемента.

Я не имею никакого отношения к Проект Суператоров, не восхищаясь этим.

Ответ 19

Я опаздываю на вечеринку, но:

Вы можете легко взять два массива равной длины и превратить их в хэш с одним массивом, снабжающим ключи, а остальные значения:

a = [:x, :y, :z]
b = [123, 456, 789]

Hash[a.zip(b)]
# => { :x => 123, :y => 456, :z => 789 }

(Это работает, потому что массив # zip "застегивает" значения из двух массивов:

a.zip(b)  # => [[:x, 123], [:y, 456], [:z, 789]]

И Hash [] может принимать только такой массив. Я тоже видел, как люди это делают:

Hash[*a.zip(b).flatten]  # unnecessary!

Что дает тот же результат, но splat и flatten полностью не нужны - возможно, они не были в прошлом?)

Ответ 20

Авто-живительные хэши в 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 } } }

Это может быть просто чертовски удобно.

Ответ 21

Разрушение массива

(a, b), c, d = [ [:a, :b ], :c, [:d1, :d2] ]

Где:

a #=> :a
b #=> :b
c #=> :c
d #=> [:d1, :d2]

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

Ответ 22

Class.new()

Создайте новый класс во время выполнения. Аргументом может быть класс, который должен быть получен, а блок - это тело класса. Вы также можете посмотреть const_set/const_get/const_defined?, чтобы ваш новый класс был правильно зарегистрирован, поэтому inspect выводит имя вместо номера.

Не то, что вам нужно каждый день, но очень удобно, когда вы это делаете.

Ответ 23

Много волшебства, которое вы видите в Rubyland, связано с метапрограммированием, которое просто пишет код, который пишет код для вас. Ruby 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 для создания нового метода, задачей которого является отправить сообщение вместе, включая все аргументы и блоки.

Огромный ресурс для проблем метапрограммирования - Почему Lucky Stiff "Видя метапрограммирование ясно" .

Ответ 24

создать массив последовательных чисел:

x = [*0..5]

устанавливает x в [0, 1, 2, 3, 4, 5]

Ответ 25

используйте все, что отвечает ===(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, а многие другие классы определяют метод экземпляра: === (other) и могут использоваться все.

Благодаря Farrel, чтобы напоминание о Proc#call было псевдонимом как Proc#=== в Ruby 1.9.

Ответ 26

"ruby" (по крайней мере, MRI) поддерживает много переключателей, которые сделали perl one-liners весьма популярными.

Значительные:

  • -n Устанавливает внешний цикл с просто "получает", который волшебным образом работает с заданным именем файла или STDIN, устанавливая каждую прочитанную строку в $_
  • -p Аналогично -n, но с автоматическим put в конце каждой итерации цикла
  • -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

Не стесняйтесь Google "рубиновые однострочники" и "perl one-liners" для более удобных и практичных примеров. Это, по сути, позволяет использовать ruby ​​как довольно мощную замену awk и sed.

Ответ 27

Метод 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

Ответ 28

Обманите какой-нибудь класс или модуль, сообщив, что он потребовал что-то, чего он действительно не требовал:

$" << "something"

Это полезно, например, когда требуется A, которое по очереди требует B, но нам не нужно B в нашем коде (и A не будет использовать его ни через наш код):

Например, Backgroundrb bdrb_test_helper requires 'test/spec', но вы его вообще не используете, поэтому в вашем коде:

$" << "test/spec"
require File.join(File.dirname(__FILE__) + "/../bdrb_test_helper")

Ответ 29

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"

Ответ 30

Определение метода, который принимает любое количество параметров и просто отбрасывает их все

def hello(*)
    super
    puts "hello!"
end

Приведенный выше метод hello должен иметь значение puts "hello" на экране и вызывать super - но поскольку суперкласс hello определяет параметры, которые он должен также использовать, однако, поскольку он действительно не нужен использовать сами параметры - ему не нужно указывать им имя.