Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Deep Dive into New Ruby Features

A Deep Dive into New Ruby Features

Ruby 2.5 was released on Christmas Day and it came with a bunch of new features! We'll take a look at how they can be implemented in pure Ruby and explore practical use cases. Finally, we'll take a peek at a couple of exciting features planned for Ruby 2.6 like a JIT and Threadlets.

Shannon Skipper

April 10, 2018
Tweet

Other Decks in Programming

Transcript

  1. Kernel#yield_self static VALUE rb_obj_size(VALUE self, VALUE args, VALUE obj) {

    return LONG2FIX(1); } static VALUE rb_obj_yield_self(VALUE obj) { RETURN_SIZED_ENUMERATOR(obj, 0, 0, rb_obj_size); return rb_yield_values2(1, &obj); } CRuby Implementation https://github.com/ruby/ruby/commit/dc6d7cc58e78903e8309ff94c9e7112d661646ee
  2. Kernel#yield_self module Kernel def yield_self if block_given? yield self else

    [self].to_enum { 1 } end end end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/164
  3. Kernel#yield_self module Kernel def yield_self if block_given? yield self else

    [self].to_enum { 1 } end end end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/164
  4. Kernel#yield_self module Kernel def yield_self if block_given? yield self else

    [self].to_enum { 1 } end end end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/164
  5. Kernel#yield_self module Kernel def yield_self if block_given? yield self else

    [self].to_enum { 1 } end end end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/164
  6. Kernel#yield_self ▸ 42.tap { |n| n * 2 } #=>

    42 ▸ 42.yield_self { |n| n * 2 } #=> 84 ▸ enum = 42.yield_self #=> #<Enumerator: …> ▸ enum.one? #=> true ▸ enum.first #=> 42 ▸ 42.yield_self.to_a #=> [42] Examples
  7. Kernel#yield_self ▸ 42.tap { |n| n * 2 } #=>

    42 ▸ 42.yield_self { |n| n * 2 } #=> 84 ▸ enum = 42.yield_self #=> #<Enumerator: …> ▸ enum.one? #=> true ▸ enum.first #=> 42 ▸ 42.yield_self.to_a #=> [42] Examples
  8. Kernel#yield_self ▸ 42.tap { |n| n * 2 } #=>

    42 ▸ 42.yield_self { |n| n * 2 } #=> 84 ▸ enum = 42.yield_self #=> #<Enumerator: …> ▸ enum.one? #=> true ▸ enum.first #=> 42 ▸ 42.yield_self.to_a #=> [42] Examples
  9. Kernel#yield_self ▸ 42.tap { |n| n * 2 } #=>

    42 ▸ 42.yield_self { |n| n * 2 } #=> 84 ▸ enum = 42.yield_self #=> #<Enumerator: …> ▸ enum.one? #=> true ▸ enum.first #=> 42 ▸ 42.yield_self.to_a #=> [42] Examples
  10. Keyword Structs Point = Struct.new :x, :y #=> Point treasure

    = Point.new 42, 0 #=> #<struct Point x=42, y=0> Point = Struct.new :x, :y, keyword_init: true #=> Point(keyword_init: true) treasure = Point.new x: 42, y: 0 #=> #<struct Point x=42, y=0> The Old Way
  11. Keyword Structs Point = Struct.new :x, :y #=> Point treasure

    = Point.new 42, 0 #=> #<struct Point x=42, y=0> Point = Struct.new :x, :y, keyword_init: true #=> Point(keyword_init: true) treasure = Point.new x: 42, y: 0 #=> #<struct Point x=42, y=0> The New Way
  12. Keyword Structs Point = Struct.new :x, :y #=> Point treasure

    = Point.new 42, 0 #=> #<struct Point x=42, y=0> Point = Struct.new :x, :y, keyword_init: true #=> Point(keyword_init: true) treasure = Point.new x: 42, y: 0 #=> #<struct Point x=42, y=0> The New Way Compared
  13. Keyword Structs Connascence (born together and coupled) ▸ Type ▸

    Position ▸ Name ▸ Meaning ▸ Execution ▸ Value ▸ Identity ▸ ... Jim Weirich — Connascence Examined def foo(first, second, third) Connascence of Position foo(1, 2, 3)
  14. Keyword Structs Connascence (born together and coupled) ▸ Type ▸

    Position ▸ Name ▸ Meaning ▸ Execution ▸ Value ▸ Identity ▸ ... Jim Weirich — Connascence Examined def foo(first:, second:, third:) Connascence of Name foo(third: 3, first: 1, second: 2)
  15. Keyword Structs Caveat Point = Struct.new :x, :y, keyword_init: true

    #=> Point(keyword_init: true) treasure = Point.new x: 42, y: 0 #=> #<struct Point x=42, y=0> ## # The fix will be released with Ruby 2.5.1: Marshal.load Marshal.dump treasure #!> ArgumentError # Can’t handle keyword_init yet Ruby 2.5.0 Keywords Structs Can’t Deserialize
  16. Method#=== & Proc#=== class Method alias === call end class

    Proc alias === call end Pure Ruby Implementation
  17. String#=== and Numeric#=== Examples String === 'example' #=> true String

    === 42 #=> false case 'example' when Numeric :unexpected when String :expected end #=> :expected Threequals Examples
  18. Method#=== Examples 0.method(:<) === 42 #=> true 100.method(:<) === 42

    #=> false case 42 when 0.method(:<) :less_than_zero when 100.method(:<) :less_than_one_hundred end #=> :less_than_one_hundred Threequals Examples
  19. More Method#=== Examples message = 'Hello World ' case ''

    when message.method(:start_with?) :starting_diamond when message.method(:end_with?) :ending_diamond else :neither end #=> :ending_diamond Threequals Examples
  20. Enumerable predicate method patterns def all?(pattern = undefined) if !undefined.equal?(pattern)

    each do |*o| o = o.first if o.size == 1 return false unless pattern === o elsif block_given? #... end end true end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/943
  21. Enumerable predicate method patterns def all?(pattern = undefined) if !undefined.equal?(pattern)

    each do |*o| o = o.first if o.size == 1 return false unless pattern === o elsif block_given? #... end end true end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/943
  22. Enumerable predicate method patterns words = %w[nope underneath nada delineate’]

    words.grep /neat/ #=> ["underneath", "delineate"] words.grep_v /neat/ #=> ["nope", “nada"] words.all? { |word| word =~ /neat/ } #=> false words.all? { |word| /neat/ === word } #=> false words.all? /neat/ #=> false Predicate Pattern Examples
  23. Enumerable predicate method patterns words = %w[nope underneath nada delineate’]

    words.grep /neat/ #=> ["underneath", "delineate"] words.grep_v /neat/ #=> ["nope", “nada"] words.all? { |word| word =~ /neat/ } #=> false words.all? { |word| /neat/ === word } #=> false words.all? /neat/ #=> false Predicate Pattern Examples
  24. Enumerable predicate method patterns words = %w[nope underneath nada delineate’]

    words.grep /neat/ #=> ["underneath", "delineate"] words.grep_v /neat/ #=> ["nope", “nada"] words.all? { |word| word =~ /neat/ } #=> false words.all? { |word| /neat/ === word } #=> false words.all? /neat/ #=> false Predicate Pattern Examples
  25. Enumerable predicate method patterns words = %w[nope underneath nada delineate’]

    words.grep /neat/ #=> ["underneath", "delineate"] words.grep_v /neat/ #=> ["nope", “nada"] words.all? { |word| word =~ /neat/ } #=> false words.all? { |word| /neat/ === word } #=> false words.all? /neat/ # New in Ruby 2.5 #=> false Predicate Pattern Examples
  26. Enumerable predicate method patterns words = %w[nope underneath nada delineate’]

    words.all? /neat/ #=> false words.any? /neat/ #=> true words.none? /neat/ #=> false words.one? /neat/ #=> false Predicate Pattern Examples
  27. String#grapheme_clusters '"'.bytes #=> [240, 159, 135, 171, 240, 159, 135,

    174] '"'.codepoints #=> [127467, 127470] [127467, 127470].map { |n| n.chr Encoding::UTF_8 } #=> ["", ""] '"'.chars #=> ["", ""] '"'.grapheme_clusters #=> ["""] String Examples
  28. String#each_grapheme_cluster Caveat '"'.each_grapheme_cluster.to_a #=> ["""] '"'.each_grapheme_cluster.to_a.size #=> 1 '"'.each_grapheme_cluster.size #=>

    2 # oops! '"'.each_grapheme_cluster.count #=> 1 Lazily calculating the size of #each_grapheme_cluster is wrong in 2.5.0 https://bugs.ruby-lang.org/issues/14363
  29. String #start_with? & #end_with? Regexp s = 'Once upon a

    time ...' s.start_with? 'Once' #=> true s.match? /\Aonce/i # The old way with #match? & anchor #=> true s.start_with? /once/i # The new way #=> true s.end_with? /\.+/ #=> true String Examples
  30. String #delete_prefix & #delete_suffix String Examples s = 'Once upon

    a time ...' s.delete_prefix 'Once ' #=> "upon a time ..." s.delete_suffix '.' #=> "Once upon a time .." s.delete_suffix /\.+/ #!> TypeError: no implicit conversion of Regexp …
  31. Hash #transform_values & #transform_keys def transform_values return to_enum(:transform_values) { size

    } unless block_given? h = {} each_pair do |key, value| h[key] = yield(value) end h end def transform_keys return to_enum(:transform_keys) { size } unless block_given? h = {} each_pair do |key, value| h[yield(key)] = value end h end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/952
  32. Hash #transform_values & #transform_keys def transform_values return to_enum(:transform_values) { size

    } unless block_given? h = {} each_pair do |key, value| h[key] = yield(value) end h end def transform_keys return to_enum(:transform_keys) { size } unless block_given? h = {} each_pair do |key, value| h[yield(key)] = value end h end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/952
  33. Hash #transform_values! & #transform_keys! def transform_values! return to_enum(:transform_values!) { size

    } unless block_given? raise RuntimeError, "can't modify frozen Hash" if frozen? each_pair do |key, value| self[key] = yield(value) end self end def transform_keys! return to_enum(:transform_keys!) { size } unless block_given? raise RuntimeError, "can't modify frozen Hash" if frozen? keys.each do |key| self[yield(key)] = delete(key) end self end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/952
  34. Hash #transform_values! & #transform_keys! def transform_values! return to_enum(:transform_values!) { size

    } unless block_given? raise RuntimeError, "can't modify frozen Hash" if frozen? each_pair do |key, value| self[key] = yield(value) end self end def transform_keys! return to_enum(:transform_keys!) { size } unless block_given? raise RuntimeError, "can't modify frozen Hash" if frozen? keys.each do |key| self[yield(key)] = delete(key) end self end Pure Ruby Implementation https://github.com/graalvm/truffleruby/pull/952
  35. Hash #transform_keys & #slice h = {a: 1, b: 2,

    c: 3} #=> {:a=>1, :b=>2, :c=>3} h.transform_values &:to_s #=> {:a=>"1", :b=>"2", :c=>"3"} h.transform_keys &:to_s #=> {"a"=>1, "b"=>2, “c"=>3} h.slice :a, :c #=> {:a=>1, :c=>3} h.slice :b #=> {:b=>2} Hash Examples
  36. Hash #slice & #transform_keys h = {a: 1, b: 2,

    c: 3} #=> {:a=>1, :b=>2, :c=>3} h.transform_values &:to_s #=> {:a=>"1", :b=>"2", :c=>"3"} h.transform_keys &:to_s #=> {"a"=>1, "b"=>2, “c"=>3} h.slice :a, :c #=> {:a=>1, :c=>3} h.slice :b #=> {:b=>2} Hash Examples
  37. Array #prepend & #append numbers = [1, 2, 3] #=>

    [1, 2, 3] numbers.unshift 0 #=> [0, 1, 2, 3] numbers << 4 #=> [1, 2, 3, 4] numbers.prepend 0 #=> [0, 1, 2, 3] numbers.append 4 #=> [1, 2, 3, 4] Array Old Methods
  38. Array #prepend & #append numbers = [1, 2, 3] #=>

    [1, 2, 3] numbers.unshift 0 #=> [0, 1, 2, 3] numbers << 4 #=> [1, 2, 3, 4] numbers.prepend 0 #=> [0, 1, 2, 3] numbers.append 4 #=> [1, 2, 3, 4] Array New Aliases
  39. SecureRandom.alphanumeric require 'securerandom' module SecureRandom ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']

    def alphanumeric(n=nil) n = 16 if n.nil? choose(ALPHANUMERIC, n) end end SecureRandom.choose #!> NoMethodError: private method `choose' called for SecureRandom:Module SecureRandom.send(:choose, %w[Hello World], 2) #=> "WorldHello" SecureRandom.send(:choose, %w[Hello World], 2) #=> "HelloHello" SecureRandom Implementation https://bugs.ruby-lang.org/issues/10849
  40. SecureRandom.alphanumeric require 'securerandom' module SecureRandom ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *’0’..'9']

    def alphanumeric(n=nil) n = 16 if n.nil? choose(ALPHANUMERIC, n) end end SecureRandom.choose #!> NoMethodError: private method `choose' called for SecureRandom:Module SecureRandom.send(:choose, %w[Hello World], 2) #=> "WorldHello" SecureRandom.send(:choose, %w[Hello World], 2) #=> "HelloHello" SecureRandom Implementation https://bugs.ruby-lang.org/issues/10849
  41. SecureRandom.alphanumeric require 'securerandom' alnum = SecureRandom.alphanumeric #=> "gKS5JuHBb2Xu3JtR" alnum.size #=>

    16 SecureRandom.alphanumeric(1) #=> "x" SecureRandom.alphanumeric(2) #=> "8M" SecureRandom Examples
  42. SecureRandom.alphanumeric require 'securerandom' alnum = SecureRandom.alphanumeric #=> “gKS5JuHBb2Xu3JtR" alnum.size #=>

    16 SecureRandom.alphanumeric(1) #=> "x" SecureRandom.alphanumeric(2) #=> "8M" SecureRandom Examples
  43. “Bundled” and “Default” Gems https://stdgems.org/ Bundled (unrelated to Bundler) ▸

    Not Maintained by Ruby Core ▸ Can be Uninstalled Default ▸ Maintained by Ruby Core ▸ Cannot be Uninstalled
  44. Ruby 2.4 Default Gems https://stdgems.org/2.4.0/ ▸ bigdecimal ▸ io-console ▸

    json ▸ openssl ▸ psych ▸ rdoc ▸ rubygems ▸ webrick
  45. Ruby 2.5 Default Gems https://stdgems.org/2.5.0/ ▸ bigdecimal ▸ io-console ▸

    json ▸ openssl ▸ psych ▸ rdoc ▸ rubygems ▸ webrick ▸ cmath ▸ csv ▸ date ▸ dbm ▸ gdbm ▸ sdbm ▸ etc ▸ fcntl ▸ fiddle ▸ fileutils ▸ ipaddr ▸ scanf ▸ stringio ▸ strscan ▸ zlib
  46. More Ruby 2.5 Features https://github.com/ruby/ruby/blob/v2_5_0/NEWS ▸ In do blocks, rescue

    no longer needs begin/end ▸ Process.last_status alias added for cryptic $? ▸ Net::HTTP::STATUS_CODES added ▸ Refinements now work in String interpolation ▸ Exception#full_message returns a String just like the error message raised ▸ A bunch of File/Dir related methods now release the GVL ▸ Janky top-level constant lookup removed
  47. More Ruby 2.5 Features https://github.com/ruby/ruby/blob/v2_5_0/NEWS ▸ Dir#children and Dir#each_child ▸

    Integer.sqrt, #allbits?, #anybits? and #nobits? added ▸ Integer#pow new second argument for modulo ▸ No longer need to require ‘pp’ ▸ Nicely ordered backtrace ▸ Thread.report_on_exception true by default ▸ IO.write accepts multiple arguments ▸ And many more new features!
  48. Ruby 2.5 Performance Improvements https://github.com/ruby/ruby/blob/v2_5_0/NEWS ▸ 5-10% faster overall because

    trace instructions are now removed from bytecode unless TracePoint is active ▸ 2x faster ERB ▸ 3x faster lazy proc allocation of block args ▸ Hashes switch from SipHash24 to SipHash13 ▸ Mutex locks are faster and smaller ▸ Rdoc now uses Ripper to generate much more quickly ▸ Optimized dozens of methods like String#index, Enumerable#sort_by, etc. ▸ And many, many more performance improvements!
  49. Ruby 2.5 Installation Installation ruby-install (chruby) ▸ ruby-install --latest ruby-2.5

    RVM ▸ rvm get head && rvm install ruby-2.5 ruby-build (rbenv) ▸ brew update && brew upgrade ruby-build ▸ rbenv install 2.5.0 # manually specify the latest version
  50. Threadlets (possibly) Feature proposal implemented by normalperson “What [a Threadlet]

    provides is an option to reduce memory usage and improve scalability without rewriting existing synchronous codebases (e.g. Rack + middlewares).” ~ Eric (normalperson) Wong https://bugs.ruby-lang.org/issues/13618
  51. Threadlets (possibly) Feature proposal implemented by normalperson Threads ▸ Larger

    stack ▸ Automatically scheduled by system Fibers ▸ Smaller stack ▸ Manually scheduled by programmer Threadlets (new in 2.6?) ▸ Smaller stack ▸ Automatically scheduled by system ▸ Similar to options available in Go, Erlang/Elixir, Haskell and Crystal https://bugs.ruby-lang.org/issues/13618
  52. Threadlets (possibly) Feature proposal implemented by normalperson Benchmarking Thread and

    Threadlet Startup Speed and Memory https://bugs.ruby-lang.org/issues/13618
  53. Threadlets (possibly) Feature proposal implemented by normalperson Open Question: What

    should a Threadlet be named? ▸ Goroutine (Go) ▸ Lane (Lua) ▸ Spark (Haskell) ▸ Task (Elixir) ▸ Process (Erlang) ▸ Fiber (Crystal) https://bugs.ruby-lang.org/issues/13618
  54. Feature proposal implemented by normalperson RTL-MJIT (Vladimir Makarov) ▸ RTL

    (Register Transfer Language) ▸ MJIT (MRI Just in Time) ▸ Extreme changes to Ruby internals YARV-MJIT (Takashi Kokubun) ▸ Uses the MJIT from RTL-MJIT but it’s still YARV ▸ No VM instruction changes, just the JIT from RTL-MJIT ▸ Possibly ready for Ruby 2.6! https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch MJIT — Ruby’s New JIT
  55. MJIT — Ruby’s New JIT Feature proposal implemented by normalperson

    --jit use MJIT with default options --jit-option use MJIT with an experimental option MJIT options (experimental): --jit-cc=cc C compiler to generate native code (gcc, clang) --jit-warnings Enable printing MJIT warnings --jit-debug Enable MJIT debugging (very slow) https://bugs.ruby-lang.org/issues/14235
  56. Feature proposal implemented by normalperson --jit use MJIT with default

    options --jit-option use MJIT with an experimental option ruby example.rb # Without JIT MJIT options (experimental): --jit-cc=cc C compiler to generate native code (gcc, clang) --jit-warnings Enable printing MJIT warnings --jit-debug Enable MJIT debugging (very slow) https://bugs.ruby-lang.org/issues/14235 MJIT — Ruby’s New JIT
  57. MJIT — Ruby’s New JIT Feature proposal implemented by normalperson

    --jit use MJIT with default options --jit-option use MJIT with an experimental option ruby --jit example.rb # With JIT MJIT options (experimental): --jit-cc=cc C compiler to generate native code (gcc, clang) --jit-warnings Enable printing MJIT warnings --jit-debug Enable MJIT debugging (very slow) https://bugs.ruby-lang.org/issues/14235
  58. MJIT — Ruby’s New JIT Feature proposal implemented by normalperson

    https://bugs.ruby-lang.org/issues/14235 ▸ Startup time is substantially slower with the JIT. ▸ There are plans to increase JIT startup speed.