Общие рубиновые идиомы

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

Однако, вдохновленный этим вопросом: объяснил Ruby Code и описание того, как ||= работает в рубине, я думал о рубиновых идиомах, которые я не использую, так как, честно говоря, я не полностью их понимаю.

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

Кстати, из упомянутого вопроса

a ||= b 

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

if a == nil || a == false
  a = b
end

(Спасибо Ян Террелл за исправление)

Изменить: Оказывается, этот момент не является абсолютно бесспорным. На самом деле правильное расширение

(a || (a = (b))) 

Посмотрите эти ссылки, почему:

Благодаря Jörg W Mittag для указания этого.

Ответ 1

Предложение magic if, которое позволяет использовать тот же файл в качестве библиотеки или script:

if __FILE__ == $0
  # this library may be run as a standalone script
end

Упаковка и распаковка массивов:

# put the first two words in a and b and the rest in arr
a,b,*arr = *%w{a dog was following me, but then he decided to chase bob}
# this holds for method definitions to
def catall(first, *rest)
  rest.map { |word| first + word }
end
catall( 'franken', 'stein', 'berry', 'sense' ) #=> [ 'frankenstein', 'frankenberry', 'frankensense' ]

Синтаксический сахар для хэшей в качестве аргументов метода

this(:is => :the, :same => :as)
this({:is => :the, :same => :as})

Инициализаторы хеширования:

# this
animals = Hash.new { [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {}
# is not the same as this
animals = Hash.new { |_animals, type| _animals[type] = [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {:squirrels=>[:Rocket, :Secret], :dogs=>[:Scooby, :Scrappy, :DynoMutt]}
Синтаксис метакласса

x = Array.new
y = Array.new
class << x
  # this acts like a class definition, but only applies to x
  def custom_method
     :pow
  end
end
x.custom_method #=> :pow
y.custom_method # raises NoMethodError

переменные экземпляра класса

class Ticket
  @remaining = 3
  def self.new
    if @remaining > 0
      @remaining -= 1
      super
    else
      "IOU"
    end
  end
end
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> "IOU"

Блоки, procs и лямбды. Живи и дыши их.

 # know how to pack them into an object
 block = lambda { |e| puts e }
 # unpack them for a method
 %w{ and then what? }.each(&block)
 # create them as needed
 %w{ I saw a ghost! }.each { |w| puts w.upcase }
 # and from the method side, how to call them
 def ok
   yield :ok
 end
 # or pack them into a block to give to someone else
 def ok_dokey_ok(&block)
    ok(&block)
    block[:dokey] # same as block.call(:dokey)
    ok(&block)
 end
 # know where the parentheses go when a method takes arguments and a block.
 %w{ a bunch of words }.inject(0) { |size,w| size + 1 } #=> 4
 pusher = lambda { |array, word| array.unshift(word) }
 %w{ eat more fish }.inject([], &pusher) #=> ['fish', 'more', 'eat' ]

Ответ 2

Этот полностью завершен в основных идиомах Ruby, как в:

  • Поменяйте два значения:

    x, y = y, x

  • Параметры, которые, если не указаны, принимают значение по умолчанию

    def somemethod(x, y=nil)

  • Выгружает посторонние параметры в массив

    def substitute(re, str, *rest)

И так далее...

Ответ 3

Еще несколько идиом:

Использование разделителей %w, %r и %(

%w{ An array of strings %}
%r{ ^http:// }
%{ I don't care if the string has 'single' or "double" strings }

Сравнение типов в случае операторов

def something(x)
  case x
    when Array
      # Do something with array
    when String
      # Do something with string
    else
      # You should really teach your objects how to 'quack', don't you?
  end
end

... и общее злоупотребление методом === в операторах case

case x
  when 'something concrete' then ...
  when SomeClass then ...
  when /matches this/ then ...
  when (10...20) then ...
  when some_condition >= some_value then ...
  else ...
end

Что-то, что должно выглядеть естественно для рубистов, но, возможно, не так для людей, пришедших с других языков: использование each в пользу for .. in

some_iterable_object.each{|item| ... }

В Ruby 1.9+, Rails или путем исправления метода символа # to_proc this становится все более популярным идиомом:

strings.map(&:upcase)

Условный метод/определение константы

SOME_CONSTANT = "value" unless defined?(SOME_CONSTANT)

Методы запросов и деструктивные методы (bang)

def is_awesome?
  # Return some state of the object, usually a boolean
end

def make_awesome!
  # Modify the state of the object
end

Неявные параметры отображения

[[1, 2], [3, 4], [5, 6]].each{ |first, second| puts "(#{first}, #{second})" }

Ответ 4

Мне это нравится:

str = "Something evil this way comes!"
regexp = /(\w[aeiou])/

str[regexp, 1] # <- This

Что (примерно) эквивалентно:

str_match = str.match(regexp)
str_match[1] unless str_match.nil?

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

Ответ 5

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

Некоторые примеры, с которыми я столкнулся:

if params[:controller] == 'discussions' or params[:controller] == 'account'
  # do something here
end

соответствующего

if ['account', 'discussions'].include? params[:controller]
  # do something here
end

который позже будет реорганизован на

if ALLOWED_CONTROLLERS.include? params[:controller]
  # do something here
end

Ответ 6

Вот несколько, взятых из разных источников:

используйте "except" и "until" вместо "if not" и "while not". Старайтесь не использовать "если", когда существует условие "else".

Помните, что вы можете сразу назначить несколько переменных:

a,b,c = 1,2,3

и даже переменную swap без temp:

a,b = b,a

Используйте подходящие условные обозначения, например

do_something_interesting unless want_to_be_bored?

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

class Animal
  class<<self
    def class_method
      puts "call me using Animal.class_method"
    end
  end
end

Некоторые ссылки:

Ответ 7

Кстати, из ссылки вопрос

a ||= b 

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

if a == nil   
  a = b 
end

Это тонко некорректно и является источником ошибок в приложениях Ruby для новичков.

Поскольку оба (и только) nil и false оцениваются как логическое false, a ||= b фактически (почти *) эквивалентно:

if a == nil || a == false
  a = b
end

Или, чтобы переписать его с другой идиомой Ruby:

a = b unless a

(* Поскольку каждый оператор имеет значение, это не технически эквивалентно a ||= b. Но если вы не полагаетесь на значение инструкции, вы не увидите разницы.)

Ответ 9

Вы можете легко копировать объект Marshaling. - взято с языка программирования Ruby

def deepcopy(o)
  Marshal.load(Marshal.dump(o))
end

Обратите внимание, что файлы и потоки ввода-вывода, как а также объекты метода и связывания, слишком динамичны для маршалинга; там не было бы надежным способом восстановления их состояние.

Ответ 10

a = (b && b.attribute) || "default"

примерно:

if ( ! b.nil? && ! b == false) && ( ! b.attribute.nil? && ! b.attribute.false) a = b
else a = "default"

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

Ответ 11

Я всегда забываю точный синтаксис этого сокращения, если оператор else (и имя оператора. комментирует кто-нибудь?) Я думаю, что он широко используется за пределами ruby, но в случае, если кто-то хочет синтаксис здесь:

refactor < 3 ? puts("No need to refactor YET") : puts("You need to refactor this into a  method")

расширяется до

if refactor < 3
  puts("No need to refactor YET")
else
  puts("You need to refactor this into a  method")
end

Обновление

называется тернарным оператором:

return myvar? myvar.size: 0

Ответ 12

Мне нравится, как if-then-elses или case-when могут быть сокращены, потому что они возвращают значение:

if test>0
  result = "positive"
elsif test==0
  result = "zero"
else
  result = "negative"
end

может быть перезаписано

result = if test>0
  "positive"
elsif test==0
  "zero"
else
  "negative"
end

То же самое можно применить к case-when:

result = case test
when test>0 ; "positive"
when test==0 ; "zero"
else "negative"
end

Ответ 13

Array.pack и String.unpack для работы с двоичными файлами:

# extracts four binary sint32s to four Integers in an Array
data.unpack("iiii") 

Ответ 14

не хватает магии

class Dummy  
  def method_missing(m, *args, &block)  
    "You just called method with name #{m} and arguments- #{args}"  
  end  
end

Dummy.new.anything(10, 20)
=> "You just called method with name anything and arguments- [10, 20]"

если вы вызываете методы, которые не существуют в ruby-объектах, ruby-интерпретатор будет вызывать метод, называемый method_missing, если он определен, вы можете использовать это для некоторых трюков, например, для написания api wrappers или dsl, где вы не знаете все имена методов и параметров

Ответ 15

Хороший вопрос!

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

Карта

Мы можем использовать метод отображения по-разному:

user_ids = users.map { |user| user.id }

Или:

user_ids = users.map(&:id)

Пример

Мы можем использовать метод rand:

[1, 2, 3][rand(3)]

В случайном порядке:

[1, 2, 3].shuffle.first

И идиоматический, простой и простой способ... образец!

[1, 2, 3].sample

Двойные трубы/мемонирование

Как вы сказали в описании, мы можем использовать memoization:

some_variable ||= 10
puts some_variable # => 10

some_variable ||= 99
puts some_variable # => 10

Статический метод/метод класса

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

GetSearchResult.call(params)

Simple. Красивый. Интуитивно. Что происходит в фоновом режиме?

class GetSearchResult
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
  end
end

Для получения дополнительной информации, чтобы написать идиоматический код Ruby, прочитайте здесь