Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby 2.3 in Detail #13

Open
JuanitoFatas opened this issue Dec 9, 2015 · 0 comments
Open

Ruby 2.3 in Detail #13

JuanitoFatas opened this issue Dec 9, 2015 · 0 comments
Labels

Comments

@JuanitoFatas
Copy link
Contributor

According to the Ruby Version Policy, Ruby will release a new MINOR version every Christmas as a Christmas gift, and so we are expecting 2.3.0 this year. This release brings many new features and improvements and this post helps you catch up on the changes.

General

Did you mean gem is now bundled by default

Have you ever made a typo, this gem shows the possible corrections for errors like NoMethodError and NameError:

"".downcasr
NoMethodError: undefined method `downcasr' for "":String

    Did you mean? downcase
                  downcase!

Safe operator (&.)

This new operator helps to avoid method invocation on nil.

Consider this:

user && user.name

Can now can be reduced to:

user&.name

Without method name will raise SyntaxError:

obj&. {} # syntax error

Only invoked when needed:

user&.address( telphone() ) # telphone() is conditionally evaluated

Useful for attribute assignment too:

user&.sign_in_count += 1

Benchmark

require "benchmark/ips"

def fast(user = nil)
  user&.email
end

def slow(user = nil)
  user && user.email
end

Benchmark.ips do |x|
  x.report("&.")  { fast }
  x.report("check") { slow }
  x.compare!
end
Calculating -------------------------------------
                  &.   155.950k i/100ms
               check   150.034k i/100ms
-------------------------------------------------
                  &.      7.750M (± 9.1%) i/s -     38.520M
               check      7.556M (± 9.2%) i/s -     37.508M

Comparison:
                  &.:  7750478.7 i/s
               check:  7555733.4 i/s - 1.03x slower

Trivia: Matz also calls this the "lonely operator" in his RubyConf 2015 keynote RubyConf 2015 (a person sitting alone and looking at a dot).

Further Reading:

Frozen String Literal Pragma

Add this Magic Comment to the top of a file, and all Strings in the file will then be frozen by default.

# frozen_string_literal: true

This gem can help to add magic comments to all your ruby files.

Alternatively, you can also:

  • Compile Ruby with --enable=frozen-string-literal or --disable=frozen-string-literal
  • Invoke Ruby with ENV variable like so $ RUBYOPT="--enable=frozen-string-literal" ruby

Known Issue:

Related Issue:

Further Reading:

String

<<~ Indented heredoc

sql = <<~SQL
  SELECT * FROM MICHELIN_RAMENS 
  WHERE STAR = 1
SQL

squish

Active Support's String#squish and String#squish! has been ported to Ruby 2.3.

Module

#deprecate_constant

class Deppbot
  GITHUB_URL = "http://github.com/jollygoodcode/deppbot"

  deprecate_constant :GITHUB_URL

  GITHUB_URI = "https://github.com/jollygoodcode/deppbot"
end

Deppbot::GITHUB_URL will throw a warning: warning: constant Deppbot::GITHUB_URL is deprecated.

Numeric

#positive?

42.positive? # => true

Benchmark

require "benchmark/ips"

class Numeric
  def my_positive?
    self > 0
  end
end

Benchmark.ips do |x|
  x.report("#positive?")  { 1.positive? }
  x.report("#my_positive?") { 1.my_positive? }
  x.compare!
end
Calculating -------------------------------------
          #positive?   166.229k i/100ms
       #my_positive?   167.341k i/100ms
-------------------------------------------------
          #positive?     10.098M (± 9.7%) i/s -     50.035M
       #my_positive?     10.094M (± 8.9%) i/s -     50.035M

Comparison:
          #positive?: 10098105.6 i/s
       #my_positive?: 10093845.8 i/s - 1.00x slower

#negative?

-13.negative? # => true

Benchmark

require "benchmark/ips"

class Numeric
  def my_negative?
    self < 0
  end
end

Benchmark.ips do |x|
  x.report("#negative?")  { 1.negative? }
  x.report("#my_negative?") { 1.my_negative? }
  x.compare!
end
Calculating -------------------------------------
          #negative?   154.580k i/100ms
       #my_negative?   155.471k i/100ms
-------------------------------------------------
          #negative?      9.907M (±10.3%) i/s -     49.002M
       #my_negative?     10.116M (±10.4%) i/s -     50.062M

Comparison:
       #my_negative?: 10116334.8 i/s
          #negative?:  9907112.3 i/s - 1.02x slower

Array

#dig

foo = [[[0, 1]]]

foo.dig(0, 0, 0) => 0

Faster than foo[0] && foo[0][0] && foo[0][0][0]

Benchmark

require "benchmark/ips"

results = [[[1, 2, 3]]]

Benchmark.ips do |x|
  x.report("Array#dig") { results.dig(0, 0, 0) }
  x.report("&&") { results[0] && results[0][0] && results[0][0][0] }
  x.compare!
end
Calculating -------------------------------------
           Array#dig   144.577k i/100ms
                  &&   142.233k i/100ms
-------------------------------------------------
           Array#dig      8.263M (± 8.3%) i/s -     41.060M
                  &&      7.652M (± 9.3%) i/s -     37.976M

Comparison:
           Array#dig:  8262509.7 i/s
                  &&:  7651601.9 i/s - 1.08x slower

#bsearch_index

While Array#bsearch returns a match in sorted array:

[10, 11, 12].bsearch { |x| x < 12 } # => 10

Array#bsearch_index returns the index instead:

[10, 11, 12].bsearch_index { |x| x < 12 } # => 0

Please note that #bsearch and #bsearch_index only works for sorted array.

Struct

#dig

klass = Struct.new(:a)
o = klass.new(klass.new({b: [1, 2, 3]}))

o.dig(:a, :a, :b, 0)              #=> 1
o.dig(:b, 0)                      #=> nil

OpenStruct

OpenStruct is now 3X-10X faster in Ruby 2.3 than it was in earlier versions of Ruby.

If you are using Hashie in any of your libraries (especially API wrappers), now is a good time to switch back to OpenStruct.

Hash

#dig

info = {
  matz: {
    address: {
      street: "MINASWAN street"
    }
  }
}

info.dig(:matz, :address, :street) => 0

Faster than info[:matz] && info[:matz][:address] && info[:matz][:address][:street]

Benchmark

require "benchmark/ips"

info = {
  user: {
    address: {
      street1: "123 Main street"
    }
  }
}

Benchmark.ips do |x|
  x.report("#dig") { info.dig(:user, :address, :street1) }
  x.report("&&") { info[:user] && info[:user][:address] && info[:user][:address][:street1]  }
  x.compare!
end
Calculating -------------------------------------
            Hash#dig   150.463k i/100ms
                  &&   141.490k i/100ms
-------------------------------------------------
            Hash#dig      7.219M (± 8.1%) i/s -     35.961M
                  &&      5.344M (± 7.6%) i/s -     26.600M

Comparison:
            Hash#dig:  7219097.1 i/s
                  &&:  5344038.3 i/s - 1.35x slower

#>, #<, #>=, #<=

Check if a hash's size is larger / smaller than the other hash, or if a hash is a subset (or equals to) of the other hash.

Check this spec on ruby/rubyspec to learn more.

Further Reading:

Note that there isn't Hash#<=>.

#fetch_values

Extract many values from a Hash (fetch_values is similar to values_at):

jollygoodcoders = {
  principal_engineer: "Winston",
  jolly_good_coder: "Juanito",
}

> jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder)
=> ["Winston", "Juanito"]

> jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder)
=> ["Winston", "Juanito"]

However, fetch_values will throwKeyError` when a given key is not found in the hash:

> jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder, :project_manager)
=> ["Winston", "Juanito", nil]

> jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder, :project_manager)
=> KeyError: key not found: :project_manager

Benchmark

require "benchmark/ips"

jollygoodcoders = {
  principal_engineer: "Winston",
  jolly_good_coder: "Juanito",
}

Benchmark.ips do |x|
  x.report("Hash#values_at") { jollygoodcoders.values_at(:principal_engineer, :jolly_good_coder) }
  x.report("Hash#fetch_values") { jollygoodcoders.fetch_values(:principal_engineer, :jolly_good_coder) }
  x.compare!
end
Calculating -------------------------------------
      Hash#values_at   133.600k i/100ms
   Hash#fetch_values   123.666k i/100ms
-------------------------------------------------
      Hash#values_at      5.869M (± 9.4%) i/s -     29.125M
   Hash#fetch_values      5.583M (± 7.7%) i/s -     27.825M

Comparison:
      Hash#values_at:  5868709.9 i/s
   Hash#fetch_values:  5582932.0 i/s - 1.05x slower

#to_proc

hash = { a: 1, b: 2, c: 3 }

[:a, :b, :c].map &hash
=> [1, 2, 3]

Enumerable

#grep_v

This method returns the inverse of Enumerable#grep. Do not confuse this with the $ grep -v command.

#chunk_while

The positive form of Enumerable#slice_when:

> pi = Math::PI.to_s.scan(/\d/).map(&:to_i)
=> [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3]

> pi.slice_when { |i, j| i.even? != j.even? }.to_a
=> [[3, 1], [4], [1, 5, 9], [2, 6], [5, 3, 5], [8], [9, 7, 9, 3]]

> pi.chunk_while { |i, j| i.even? == j.even? }.to_a
=> [[3, 1], [4], [1, 5, 9], [2, 6], [5, 3, 5], [8], [9, 7, 9, 3]]

Additional Resources

Thanks for reading!

@JuanitoFatas ✏️ Jolly Good Code

About Jolly Good Code

Jolly Good Code

We specialise in Agile practices and Ruby, and we love contributing to open source.
Speak to us about your next big idea, or check out our projects.

@winston winston changed the title Ruby 2.3 in Details Ruby 2.3 in Detail Dec 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant