[RubyConf 2018] Ruby Next: Make old Rubies quack like a new one

52cc8a838bf44a589d2572833b2dd1b9?s=47 Vlad Dem
November 18, 2019

[RubyConf 2018] Ruby Next: Make old Rubies quack like a new one

GitHub: https://github.com/ruby-next/ruby-next
Video: https://www.youtube.com/watch?v=T6epHXlUmG0

Ruby 2.7 is just around the corner. It will bring a lot of new features, including new syntax additions: pattern matching, numbered parameters.

That's good news. The bad news is that not many of us will be able to use these goodies right away: the upgrade cost blocks application developers; gem authors have to support older versions.

What if we were able to use Ruby Next features while running Ruby Current? Maybe, we can cast a metaprogramming spell for that? Yes, we can. And I'll show you how.

52cc8a838bf44a589d2572833b2dd1b9?s=128

Vlad Dem

November 18, 2019
Tweet

Transcript

  1. palkan_tula palkan RubyConf 2019 Ruby Next Vladimir Dementyev Evil Martians

  2. 142 palkan_tula palkan RubyConf 2019 2 Ruby

  3. 142 palkan_tula palkan RubyConf 2019 Features & challenges 3

  4. 142 palkan_tula palkan RubyConf 2019 Challenges for Ruby developers 4

  5. 142 palkan_tula palkan RubyConf 2019 5 Ruby Next

  6. 142 palkan_tula palkan RubyConf 2019 Make old Rubies quack like

    a new one 6
  7. 142 palkan_tula palkan RubyConf 2019 $ ruby -v
 2.5.3p105
 $

    ruby -e "
 case {hello: 'RubyConf'}
 in hello: hello if hello =~ /Rails/
 puts 'My name is David'
 in hello: 'RubyConf'
 puts 'My name is Vova'
 end"
 -e:3: syntax error, unexpected in, expecting when in hello: if hello =~ /Rails/ 7 tl;dr
  8. 142 palkan_tula palkan RubyConf 2019 $ ruby -v
 2.5.3p105
 $

    ruby -ruby-next -e "
 case {hello: 'RubyConf'}
 in hello: hello if hello =~ /Rails/
 puts 'My name is David'
 in hello: 'RubyConf'
 puts 'My name is Vova'
 end"
 My name is Vova 8 tl;dr
  9. 142 palkan_tula palkan RubyConf 2019 9 About me

  10. 142 palkan_tula palkan RubyConf 2019 Evil Martian 10

  11. 142 palkan_tula palkan RubyConf 2019 evilmartians.com 11

  12. 142 palkan_tula palkan RubyConf 2019 12 evilmartians.com What's next ?

  13. 142 palkan_tula palkan RubyConf 2019 Ruby and !Ruby developer 13

  14. 142 palkan_tula palkan RubyConf 2019 github.com/palkan 14

  15. 142 palkan_tula palkan RubyConf 2019 Gems author 15

  16. 142 palkan_tula palkan RubyConf 2019 rubygems.org/profiles/palkan 16

  17. 142 palkan_tula palkan RubyConf 2019 I have to write Ruby

    code compatible with older versions 17
  18. 142 palkan_tula palkan RubyConf 2019 18 2.3 — 2019-03-31 2.4

    — 2020-03-31 ⏳ Ruby EOL
  19. 142 palkan_tula palkan RubyConf 2019 spec.require_ruby_version = " >= 2.5.0"

    19
  20. 142 palkan_tula palkan RubyConf 2019 I want to use 2.7

    features 20
  21. 142 palkan_tula palkan RubyConf 2019 Not everyone is a gem

    maintainer 21
  22. 142 palkan_tula palkan RubyConf 2019 jetbrains.com/lp/devecosystem-2019/ruby/ 22

  23. 142 palkan_tula palkan RubyConf 2019 “30% reported that they're not

    about to switch” 23
  24. 142 palkan_tula palkan RubyConf 2019 Performance Why upgrade? 24

  25. 142 palkan_tula palkan RubyConf 2019 Performance New features Why upgrade?

    25
  26. 142 palkan_tula palkan RubyConf 2019 26 Story time

  27. 142 palkan_tula palkan RubyConf 2019 Jan 2018, Ruby 2.5 27

  28. 142 palkan_tula palkan RubyConf 2019 28 #yield_self

  29. 142 palkan_tula palkan RubyConf 2019 module Kernel def yield_self yield

    self end end 29 Metaprogramming for good
  30. 142 palkan_tula palkan RubyConf 2019 30 #then

  31. 142 palkan_tula palkan RubyConf 2019 module YieldSelfThen refine Kernel do

    unless nil.respond_to?(:yield_self) def yield_self yield self end end alias then yield_self end end 31 Metaprogramming for good
  32. 142 palkan_tula palkan RubyConf 2019 Refinements 32

  33. 142 palkan_tula palkan RubyConf 2019 Introduced in 2.0 Has been

    experimental until 2.1 Getting better with every release Won't go away in 3.0 History 33
  34. 142 palkan_tula palkan RubyConf 2019 Lexically scoped monkey-patch 34

  35. 142 palkan_tula palkan RubyConf 2019 Refinements changed the way method

    lookup work 35
  36. 142 palkan_tula palkan RubyConf 2019 def send(mid, *args) # iterate

    through the ancestors chain # (included/prepended modules and classes) k = self.class iter = self.class.ancestors.each loop do if k.instance_methods(false).include?(mid) return k.instance_method(mid) .bind_call(self, *args) end k = iter.next end end 36 Regular lookup
  37. 142 palkan_tula palkan RubyConf 2019 def send(mid, *args) k =

    self.class iter = self.class.ancestors.each loop do meth = + # lookup refinements in the execution context + if context.refinements_for(k)&.include?(mid) + context.refinements_for(k)[mid] - if k.instance_methods(false).include?(mid) + elsif k.instance_methods(false).include?(mid) k.instance_method(mid) end return meth.bind_call(self, *args) if meth k = iter.next end end 37 Refined lookup
  38. 142 palkan_tula palkan RubyConf 2019 “Refinements... Not fun.” — Ruby

    Implementation developer 38
  39. 142 palkan_tula palkan RubyConf 2019 JRuby vs refinements 39 15

    issues were closed in 2019
  40. 142 palkan_tula palkan RubyConf 2019 Define refinement via refine Activate

    refinement via using 40
  41. 142 palkan_tula palkan RubyConf 2019 module YieldSelfThen refine Kernel do

    unless nil.respond_to?(:yield_self) def yield_self yield self end end alias then yield_self end end 41 Define
  42. 142 palkan_tula palkan RubyConf 2019 using YieldSelfThen p Gem ::Version.new(RUBY_VERSION).then

    { |v| v.segments[0 ..1].join(".") } # => "2.5" 42 Activate
  43. 142 palkan_tula palkan RubyConf 2019 # succ.rb
 using YieldSelfThen
 


    p ARGV[0].then { |i| i.to_i + 1 } 
 # prec.rb
 p ARGV[0].then { |i| i.to_i - 1 } 
 $ ruby succ.rb 2
 3 
 $ ruby prec.rb 3
 undefined method `then' for "3":String 43 Lexical
  44. 142 palkan_tula palkan RubyConf 2019 Safe monkey-patch 44

  45. 142 palkan_tula palkan RubyConf 2019 We can use refinements to

    safely “backport” new APIs 45
  46. 142 palkan_tula palkan RubyConf 2019 Ruby gets new APIs with

    every minor release 46
  47. 142 palkan_tula palkan RubyConf 2019 47 Kernel#yield_self Thread#fetch Hash#transform_keys Hash#slice

    2.5
  48. 142 palkan_tula palkan RubyConf 2019 48 Kernel#then Proc# >>/# <<

    Enumerable#filter Array#union/#difference 2.6
  49. 142 palkan_tula palkan RubyConf 2019 49 2.7 Enumerable#tally Enumerable#filter_map Time#ceil/#floor

    UnboundMethod#bind_call
  50. 142 palkan_tula palkan RubyConf 2019 rubyreferences.github.io/rubychanges/ 50

  51. 142 palkan_tula palkan RubyConf 2019 One refinement to rule them

    all 51 Ruby Next 2018
  52. 142 palkan_tula palkan RubyConf 2019 # stats.rb using RubyNext Dir["**/*"].filter_map

    do |path| next if path =~ %r{[$/]m?spec} File.extname(path) end.tally.sort_by(&:last).reverse_each do |(ext, count)| puts " #{count}\t #{ext}" end 52
  53. 142 palkan_tula palkan RubyConf 2019 # stats.rb using RubyNext Dir["**/*"].filter_map

    do |path| next if path =~ %r{[$/]m?spec} File.extname(path) end.tally.sort_by(&:last).reverse_each do |(ext, count)| puts " #{count}\t #{ext}" end 53
  54. 142 palkan_tula palkan RubyConf 2019 $ ruby -ruby-next stats.rb 67

    .rb 50 5 .md 2 .lock 1 .txt 1 .mspec 1 .gemspec 1 .gemfile 1 .bat 1 .dict 54
  55. 142 palkan_tula palkan RubyConf 2019 55 Only contains undefined or

    modified methods => no-op in Ruby 2.7 using RubyNext
  56. 142 palkan_tula palkan RubyConf 2019 That's what Ruby Next could

    be but... 56
  57. 142 palkan_tula palkan RubyConf 2019 Ruby 2.7 adds a lot

    of new syntax features 57
  58. 142 palkan_tula palkan RubyConf 2019 Pattern matching: case i; in

    ... end Numbered params: -> { _1 + _2 } Ruby 2.7 58
  59. 142 palkan_tula palkan RubyConf 2019 Method reference: d.then(JSON.:parse) Args forwarding:

    def foo( ...) bar( ...) Ruby 2.7 59
  60. 142 palkan_tula palkan RubyConf 2019 We cannot refine syntax 60

  61. 142 palkan_tula palkan RubyConf 2019 Let's transpile it! 61

  62. 142 palkan_tula palkan RubyConf 2019 Polyfills (universal refinement) Transpiler RubyNext

    2019 62
  63. 142 palkan_tula palkan RubyConf 2019 Transpiling 63

  64. 142 palkan_tula palkan RubyConf 2019 Transpiling 64 transpiler Ruby 2.7

    source code Ruby 2.5 source code
  65. 142 palkan_tula palkan RubyConf 2019 Ruby Next is Babel for

    Ruby? 65
  66. 142 palkan_tula palkan RubyConf 2019 RubyNext is PostCSS for Ruby?

    66
  67. 142 palkan_tula palkan RubyConf 2019 Transpiling 67 source code new

    old source code AST new old AST
  68. 142 palkan_tula palkan RubyConf 2019 def beach(*temperature) case temperature in

    :celcius | :c, (20 ..45) :favorable in :kelvin | :k, (293 ..318) :scientifically_favorable in :fahrenheit | :f, (68 ..113) :favorable_in_us else :avoid_beach end end 68 Example
  69. 142 palkan_tula palkan RubyConf 2019 69 Ripper Source to AST

  70. 142 palkan_tula palkan RubyConf 2019 $ ruby -r ripper -e

    "pp Ripper.sexp(File.read('beach.rb'))" [:program, [[:def, [:@ident, "beach", [1, 4]], [:paren, [:params, [:rest_param, [:@ident, "temperature", [1, 11]]], ], [:bodystmt, [[:case, [:var_ref, [:@ident, "temperature", [2, 7]]], [:in, [:aryptn, nil, [[:binary, [:symbol_literal, [:symbol, [:@ident, "celcius", [3, 6]]]], 70
  71. 142 palkan_tula palkan RubyConf 2019 Since 1.9 Version-specific Ripper 71

  72. 142 palkan_tula palkan RubyConf 2019 $ ruby -v
 2.5.3p105 $

    ruby -r ripper -e "pp Ripper.sexp(File.read('beach.rb'))" nil 72
  73. 142 palkan_tula palkan RubyConf 2019 preval 73 github.com/kddeisz/preval

  74. 142 palkan_tula palkan RubyConf 2019 74 Ripper RubyVM::AbstractSyntaxTree Source to

    AST
  75. 142 palkan_tula palkan RubyConf 2019 $ ruby -e "pp RubyVM

    ::AbstractSyntaxTree.parse_file('beach.rb')" (SCOPE@1:0-14:3 body: (DEFN@1:0-14:3 mid: :beach body: (SCOPE@1:0-14:3 tbl: [:temperature] args: ... body: (CASE3@2:2-13:5 (LVAR@2:7-2:18 :temperature) (IN@3:2-12:16 (ARYPTN@3:5-3:28 const: nil pre: (LIST@3:5-3:28 75
  76. 142 palkan_tula palkan RubyConf 2019 New in 2.6 MRI-specific Version-specific

    RubyVM ::AbstractSyntaxTree 76
  77. 142 palkan_tula palkan RubyConf 2019 77 Ripper RubyVM::AbstractSyntaxTree Parser Source

    to AST
  78. 142 palkan_tula palkan RubyConf 2019 $ gem install parser $

    ruby-parse ./beach.rb (def :beach (args (restarg :temperature)) (case-match (lvar :temperature) (in-pattern (array-pattern (match-alt (sym :celcius) (sym :c)) (begin (irange (int 20) (int 45)))) nil 78
  79. 142 palkan_tula palkan RubyConf 2019 Written in pure Ruby Version-independent

    Bullet-proofed (e.g., by RuboCop) Parser 79
  80. 142 palkan_tula palkan RubyConf 2019 80 Parser Born at Evil

    Martians
  81. 142 palkan_tula palkan RubyConf 2019 81 Unparser $ gem install

    unparser 
 $ ruby -r parser/current -r unparser -e "
 p Unparser.unparse(
 Parser ::CurrentRuby.parse('%i[a a b c].tally'))
 " "[:a, :a, :b, :c].tally"
  82. 142 palkan_tula palkan RubyConf 2019 82 Unparser $ gem install

    unparser 
 $ ruby -r parser/current -r unparser -e "
 p Unparser.unparse(
 Parser ::CurrentRuby.parse('%i[a a b c].tally'))
 " "[:a, :a, :b, :c].tally"
  83. 142 palkan_tula palkan RubyConf 2019 83 <<"A #{b}C" #{ <<"A

    #{b}C" A #{b}C } str A #{b}C 
 # => "\nstr\n" Parser is not ideal (dstr (begin (dstr (str "A") (begin (send nil :b)))) (str "\n") (str "str\n") (str "A") (begin (send nil :b)))) " #{"A #{b}"}\nstr\nA #{b}" # => undefined local variable or method `b'
  84. 142 palkan_tula palkan RubyConf 2019 Parser is good enough for

    now 84
  85. 142 palkan_tula palkan RubyConf 2019 Parse Ruby code using edge

    parser Replace unsupported AST nodes with equivalents for the target version Generate new source code with unparser Ruby Next 85
  86. 142 palkan_tula palkan RubyConf 2019 def transform(source) Parser.parse(source).then do |ast|

    rewriters.inject(ast) do |tree, rewriter| rewriter.new.process(tree) end.then do |new_ast| Unparser.unparse(new_ast) end end end end 86
  87. 142 palkan_tula palkan RubyConf 2019 module Rewriters class MethodReference <

    Base def on_meth_ref(node) receiver, mid = *node.children node.updated( # (meth-ref :send, # (const nil :C) :m) [ # receiver, # -> :method, # s(:sym, mid) # (send ] # (const nil :C) :method ) # (sym :m) end end end 87
  88. 142 palkan_tula palkan RubyConf 2019 module Rewriters class PatternMatching <

    Base def on_case_match(node) # ~500 LOC end end end 88
  89. 142 palkan_tula palkan RubyConf 2019 if ... else if ...

    else ... end #deconstruct & #deconstruct_keys Type and structure checks “Pattern matching” in <2.7 89
  90. 142 palkan_tula palkan RubyConf 2019 90 def fizzbuzz(num) rems =

    [num % 3, num % 5] if rems == [0, 0]
 'FizzBuzz' elsif rems[0] == 0
 'Fizz'
 elsif rems[1] == 1
 'Buzz' else
 raise "NoMatchingPattern"
 end
 end def fizzbuzz(num) case [num % 3, num % 5] in [0, 0] then 'FizzBuzz' in [0, _] then 'Fizz' in [_, 0] then 'Buzz' end end 2.7 2.5 ad-hoc rewrite
  91. 142 palkan_tula palkan RubyConf 2019 91 def fizzbuzz(num) vals =

    [num % 3, num % 5]
 .deconstruct raise TypeError unless Array === vals if (0 === vals[0]) &&
 (0 === vals[1]) 
 'FizzBuzz' elsif (0 === vals[0])
 && (_ == vals[1] || true)
 'Fizz'
 elsif (_ == vals[0] || true)
 && (1 === vals[1])
 'Buzz' else
 raise "NoMatchingPattern"
 end
 end def fizzbuzz(num) case [num % 3, num % 5] in [0, 0] then 'FizzBuzz' in [0, _] then 'Fizz' in [_, 0] then 'Buzz' end end 2.7 2.5 transpile
  92. 142 palkan_tula palkan RubyConf 2019 $ gem install ruby-next $

    ruby-next nextify ./beach.rb -o stdout def beach(*temperature)
 __matchee __ = temperature
 if (((( __matchee_arr __ = __matchee __.deconstruct) || true) && ((Array === __matchee_arr __) || Kernel.raise(TypeError, __matchee __.inspect))) && ((2 == __matchee_arr __.size) && (((:celcius === __matchee_arr __[0]) || (:c === __matchee_arr __[0])) && ((20 ..45) === __matchee_arr __[1]))))
 :favorable
 else
 if (((:kelvin === __matchee_arr __[0]) || (:k === __matchee_arr __[0])) && ((293 ..318) === __matchee_arr __[1]))
 :scientifically_favorable
 else
 if (((:fahrenheit === __matchee_arr __[0]) || (:f === __matchee_arr __[0])) && ((68 ..113) === __matchee_arr __[1]))
 :favorable_in_us
 else
 :avoid_beach
 end
 end
 end
 end 92
  93. 142 palkan_tula palkan RubyConf 2019 93 def beach(*temperature)
 __matchee __

    = temperature
 if (((( __matchee_arr __ = __matchee __.deconstruct) || true) && ((Array === __matchee_arr __) || Kernel.raise(TypeError, __matchee __.inspect))) && ((2 == __matchee_arr __.size) && (((:celcius === __matchee_arr __[0]) || (:c === __matchee_arr __[0])) && ((20 ..45) === __matchee_arr __[1]))))
 :favorable
 else
 if (((:kelvin === __matchee_arr __[0]) || (:k === __matchee_arr __[0])) && ((293 ..318) === __matchee_arr __[1]))
 :scientifically_favorable
 else
 if (((:fahrenheit === __matchee_arr __[0]) || (:f === __matchee_arr __[0])) && ((68 ..113) === __matchee_arr __[1]))
 :favorable_in_us
 else
 :avoid_beach
 end
 end
 end
 end def beach(*temperature) case temperature in :celcius | :c, (20 ..45) :favorable in :kelvin | :k, (293 ..318) :scientifically_favorable in :fahrenheit | :f, (68 ..113) :favorable_in_us else :avoid_beach end end For humans For machines
  94. 142 palkan_tula palkan RubyConf 2019 Transpiling vs. performance 94

  95. 142 palkan_tula palkan RubyConf 2019 def beach(*temperature) case temperature in

    :celcius | :c, (20 ..45) :favorable in :kelvin | :k, (293 ..318) :scientifically_favorable in :fahrenheit | :f, (68 ..113) :favorable_in_us else :avoid_beach end end $ ruby benchmark/pattern_matching_array.rb Comparison:
 transpiled: 2165271.2 i/s 
 baseline: 1004383.8 i/s - 2.16x slower 95
  96. 142 palkan_tula palkan RubyConf 2019 case JSON.parse(ARGV[0], symbolize_names: true) in

    {name: "Alice", children: [{name: "Bob", age: age}]} p "Bob age is #{age}" in _ "No Alice" end $ ruby benchmark/pattern_matching_mixed.rb Comparison:
 transpiled: 314105.5 i/s
 baseline: 273998.7 i/s - 1.15x slower 96
  97. 142 palkan_tula palkan RubyConf 2019 How to integrate a transpiler

    into an interpreted language? 97
  98. 142 palkan_tula palkan RubyConf 2019 Gems: transpile at “build”/ release

    time Apps/Scripts: transpile at runtime 98
  99. 142 palkan_tula palkan RubyConf 2019 Hijack Kernel#require & co Reimplement

    require mechanism Runtime 99
  100. 142 palkan_tula palkan RubyConf 2019 module Kernel module_function alias_method :require_without_ruby_next,

    :require def require(path) RubyNext.require(path) rescue => e warn "Failed to require ' #{path}': #{e.message}" require_without_ruby_next(path) end end 100 Kernel#require *Don't try this at home
  101. 142 palkan_tula palkan RubyConf 2019 $LOAD_PATH: where to search for

    “features” $LOADED_FEATURES: required files Two pillars of require 101
  102. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def require(path)

    realpath = resolve_feature_path(path) return false if $LOADED_FEATURES.include?(realpath) File.read(realpath) .then(&Language.:transform).then do |source| TOPLEVEL_BINDING.eval source, path $LOADED_FEATURES << path true end end end 102 RubyNext.require
  103. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def require(path)

    realpath = resolve_feature_path(path) return false if $LOADED_FEATURES.include?(realpath) File.read(realpath) .then(&Language.:transform).then do |source| TOPLEVEL_BINDING.eval source, path $LOADED_FEATURES << path true end end end 103 RubyNext.require
  104. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def resolve_feature_path(path)

    path = " #{path}.rb" if File.extname(path).empty? return path if Pathname.new(path).absolute? $LOAD_PATH.find do |lp| lpath = File.join(lp, path) return lpath if File.file?(lpath) end end end 104
  105. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def resolve_feature_path(path)

    path = " #{path}.rb" if File.extname(path).empty? return path if Pathname.new(path).absolute? $LOAD_PATH.find do |lp| lpath = File.join(lp, path) return lpath if File.file?(lpath) end end end 105
  106. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def require(path)

    realpath = resolve_feature_path(path) return false if $LOADED_FEATURES.include?(realpath) File.read(realpath) .then(&Language.:transform).then do |source| TOPLEVEL_BINDING.eval source, path $LOADED_FEATURES << path true end end end 106 RubyNext.require
  107. 142 palkan_tula palkan RubyConf 2019 module RubyNext module_function def require(path)

    realpath = resolve_feature_path(path) return false if $LOADED_FEATURES.include?(realpath) File.read(realpath) .then(&Language.:transform).then do |source| TOPLEVEL_BINDING.eval source, path $LOADED_FEATURES << path true end end end 107 RubyNext.require
  108. 142 palkan_tula palkan RubyConf 2019 $ ruby -ruby-next -e "


    case {eta: ARGV[0].to_i}
 in eta: (0 ...10)
 puts 'The end is near'
 in eta: x if x > 10
 puts 'Boooring'
 end" 8
 The end is near 108 -ruby-next
  109. 142 palkan_tula palkan RubyConf 2019 ruby -ruby-next == ruby -r

    uby-next 109
  110. 142 palkan_tula palkan RubyConf 2019 110

  111. 142 palkan_tula palkan RubyConf 2019 Runtime mode is meant for

    tests and playgrounds 111
  112. 142 palkan_tula palkan RubyConf 2019 112 # test_helper.rb require 'ruby-next/runtime'

    Runtime in tests
  113. 142 palkan_tula palkan RubyConf 2019 113 # boot.rb require 'bootsnap/setup'

    require 'ruby-next/bootsnap' Runtime future
  114. 142 palkan_tula palkan RubyConf 2019 Add transpiled files to releases

    No additional runtime deps* Nextify Gems 114 * polyfills might still be required
  115. 142 palkan_tula palkan RubyConf 2019 $ ruby-next nextify ./lib -V


    Generated: ./lib/.rbnext/rubanok/rule.rb
 Generated: ./lib/.rbnext/rubanok/dsl/matching.rb 115
  116. 142 palkan_tula palkan RubyConf 2019 116 # lib/my_gem.rb require "ruby-next/language/setup"

    RubyNext ::Language.setup_gem_load_path
  117. 142 palkan_tula palkan RubyConf 2019 117 def setup_gem_load_path lib_dir =

    File.dirname(caller_locations(1, 1).first.path) current_index = $LOAD_PATH.index(lib_dir) next_dir = File.join(lib_dir, ".rbnext") if File.exist?(next_dir) $LOAD_PATH.insert current_index, next_dir current_index += 1 end end setup_gem_load_path
  118. 142 palkan_tula palkan RubyConf 2019 github.com/ruby-next/ruby-next 118

  119. 142 palkan_tula palkan RubyConf 2019 Supported features 119 github.com/ruby-next/ruby-next/blob/master/SUPPORTED_FEATURES.md

  120. 142 palkan_tula palkan RubyConf 2019 Compatibility matters 120

  121. 142 palkan_tula palkan RubyConf 2019 ruby/spec ruby/ruby/test/* Ruby Next tests

    121
  122. 142 palkan_tula palkan RubyConf 2019 What's next? 122

  123. 142 palkan_tula palkan RubyConf 2019 Make old all Rubies quack

    like a new one 123
  124. 142 palkan_tula palkan RubyConf 2019 JRuby TruffleRuby mruby Opal Artichoke

    Prism Other Rubies 124
  125. 142 palkan_tula palkan RubyConf 2019 The role of Ruby Next?

    125
  126. 142 palkan_tula palkan RubyConf 2019 Performance New features Why upgrade?

    126
  127. 142 palkan_tula palkan RubyConf 2019 Performance Experimental features Why upgrade?

    127
  128. 142 palkan_tula palkan RubyConf 2019 128 Experimental

  129. 142 palkan_tula palkan RubyConf 2019 Experimental 129

  130. 142 palkan_tula palkan RubyConf 2019 Experimental 130

  131. 142 palkan_tula palkan RubyConf 2019 Expelliarmus 131

  132. 142 palkan_tula palkan RubyConf 2019 We need an experiment 132

  133. 142 palkan_tula palkan RubyConf 2019 133 |> 2019-06-13 — 2019-08-29

  134. 142 palkan_tula palkan RubyConf 2019 134 .: 2018-12-31 — 2019-11-12

  135. 142 palkan_tula palkan RubyConf 2019 Do we have a process?

    135
  136. 142 palkan_tula palkan RubyConf 2019 Example: TC39 136

  137. 142 palkan_tula palkan RubyConf 2019 “will require feedback from ...

    users” ☝ 137
  138. 142 palkan_tula palkan RubyConf 2019 Babel 138 https://scotch.io/tutorials/javascript-transpilers-what-they-are-why-we-need-them

  139. 142 palkan_tula palkan RubyConf 2019 The best way to taste

    new features is to start using them every day 139
  140. 142 palkan_tula palkan RubyConf 2019 Don't wait for Christmas to

    give new Ruby a try 140
  141. 142 palkan_tula palkan RubyConf 2019 Start using RubyNext 141

  142. palkan_tula palkan RubyConf 2019 Thank you! Vladimir Dementyev @ruby-next Evil

    Martians @palkan @palkan_tula evilmartians.com @evilmartians