Why is nobody using Refinements?

Acd62030df551952268e84c8fff26a5b?s=47 lazyatom
November 16, 2015

Why is nobody using Refinements?

From RubyConf 2015; transcript will be at http://lazyatom/refinements

Refinements have been a feature in Ruby for several years now, added as a more structured alternative to the "scourge" of monkey patching, but it seems like almost nobody is using them. My question is this: why not? Are they a bad idea? Are they broken? Or do we just not understand them?

Let's figure out once and for all what refinements are good for, and what their limitations might be, so that future generations can either use them for glory, or rightfully ignore them forevermore.

Acd62030df551952268e84c8fff26a5b?s=128

lazyatom

November 16, 2015
Tweet

Transcript

  1. module WhyIsNobody using Refinements end # lazyatom.com/refinements

  2. Disclaimer This is not a sales pitch for refinements. I

    am not going to show you how they are amazing and will solve all your programming problems. They might be good… but also they might be bad. They might even be terrible! Using refinements might make your application implode, or make you smell really bad, or lower your credit rating, or make your cat regard you with an even stronger level of apathetic disdain. Please do not hold me responsible if use of refinements leaves you with an unexplainable rash, or an overwhelming sense of ennui. Other side effects may include: syntax itch, nil nausea, const-ipation, drowsiness, and global interpreter lockjaw.
  3. What are refinements? A mechanism to change the behaviour of

    an object in a limited and controlled way
  4. module Shouting refine String do def shout self.upcase + "!"

    end end end
  5. module Shouting refine String do def shout self.upcase + "!"

    end end end class Thing using Shouting "Hello".shout # => "HELLO!" end
  6. module Shouting refine String do def shout self.upcase + "!"

    end end end class Thing using Shouting "Hello".shout # => "HELLO!" end "Hello".shout # => NoMethodError: undefined method 'shout' for "Oh no":String
  7. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end
  8. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end class RubyConf using TexasTime Time.parse("12:00").to_s # => "2015-11-16 12:00:00" Time.parse("14:15").to_s # => "Afternoon, y’all!" end
  9. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end class RubyConf using TexasTime Time.parse("12:00").to_s # => "2015-11-16 12:00:00" Time.parse("14:15").to_s # => "Afternoon, y’all!" end Time.parse("14:15").to_s # => "2015-11-16 14:15:00"
  10. using ( a developer’s guide )

  11. class Thing "hello".shout # => "HELLO!" end using Shouting

  12. class Thing "hello".shout # => "HELLO!" end using Shouting

  13. class Thing def greet "hello".shout end end Thing.new.greet # =>

    "HELLO!" using Shouting
  14. class Thing def greet "hello".shout end end Thing.new.greet # =>

    "HELLO!" using Shouting
  15. class Thing def greet "hello" end end Thing.new.greet using Shouting

    .shout # => NoMethodError
  16. class Thing using Shouting end class Thing "hello".shout # =>

    NoMethodError end
  17. class Thing using Shouting class OtherThing "hello".shout # => "HELLO!"

    end end
  18. class Thing using Shouting class OtherThing < Thing "hello".shout end

    end # => NoMethodError
  19. class Thing using Shouting class OtherThing < Thing "hello".shout end

    end # => "HELLO!"
  20. class Thing using Shouting class OtherThing "hello".shout # => "HELLO!"

    end end
  21. class Thing using Shouting end class Thing::OtherThing "hello".shout # =>

    NoMethodError end
  22. class Thing using Shouting def run yield end end Thing.new.run

    do "hello".shout end # => NoMethodError
  23. Lexical Scope

  24. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout

  25. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level
  26. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level
  27. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level A
  28. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none A Parent Top Level
  29. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none A Parent Top Level
  30. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none A Parent Top Level
  31. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  32. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  33. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  34. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  35. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout # => “HELLO!”
  36. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  37. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  38. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  39. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  40. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting # => NoMethodError file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  41. file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout Top

    Level Parent none Refinements Shouting A Parent Top Level Refinements Shouting
  42. Top Level Parent none Refinements Shouting A Parent Top Level

    Refinements Shouting file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout
  43. Top Level Parent none Refinements Shouting A Parent Top Level

    Refinements Shouting file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout # => “HELLO!” # => “GOODBYE!”
  44. Refinements are activated in current and nested lexical scopes

  45. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end
  46. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty
  47. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  48. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  49. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  50. same class ≠ same scope

  51. file: thing.rb class Thing using Shouting end class Other <

    Thing "hello".shout end
  52. file: thing.rb class Thing using Shouting end class Other <

    Thing "hello".shout end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  53. class hierarchy ≠ scope hierarchy

  54. different file = different scope

  55. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet
  56. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  57. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  58. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  59. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  60. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting Thing#greet Lexical scope A file: thing.rb class Thing using Shouting def greet "hello".shout end end Thing.new.greet
  61. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting Thing#greet Lexical scope A file: thing.rb class Thing using Shouting def greet "hello".shout end end Thing.new.greet # => “HELLO!”
  62. methods are evaluated using the lexical scope at their definition

  63. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end # => NoMethodError
  64. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end Top Level Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  65. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end Top Level Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A Main#block Lexical scope Top level
  66. blocks are evaluated using the lexical scope at their definition

  67. Refinements use lexical scope

  68. You get a new lexical scope when 1. Entering a

    different file 2. opening a class or module definition 3. running code from a string using “eval”
  69. Refinement principles 1. activated within current and nested scopes 2.

    scope hierarchy is not the same as class hierarchy 3. different files get different top level scopes 4.methods are evaluated using lexical scope at their definition 5. blocks are evaluated using lexical scope at their definition
  70. Let’s use refinements!

  71. 1. Monkey patching

  72. class String def camelize self.split("_").map(&:capitalize).join end end "ruby_conf_2015".camelize # =>

    "RubyConf2015"
  73. /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:261:in `const_get': wrong constant name Admin/adminHelper (NameError) from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/

    inflector/methods.rb:261:in `block in constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `each' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `inject' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ core_ext/string/inflections.rb:66:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:156:in `block in modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:144:in `map!' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:144:in `modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/action_controller/ metal/helpers.rb:93:in `modules_for_helpers'
  74. Monkey-patching Refinements breaks API expectations cannot affect distant code makes

    code harder to debug leaves lexical clue when active
  75. 2. Managing API changes

  76. # Ruby 1.9 "hello".chars # => #<Enumerator: "hello".each_char> # Ruby

    2.0+ "hello".chars # => ["h", "e", "l", "l", "o"]
  77. module OldChars refine String do def chars; each_char; end end

    end "hello".chars # => ["h", "e", "l", "l", “o"] using OldChars "hello".chars # => #<Enumerator: "hello".each_char>
  78. 3. DSLs

  79. describe "Ruby" do it "should bring happiness" do developer =

    Developer.new(uses: "ruby") developer.should be_happy end end
  80. describe "Ruby" do it "should bring happiness" do developer =

    Developer.new(uses: "ruby") developer.should be_happy end end
  81. module RSpec refine Object do def should(expectation) expectation.satisfied_by?(self) end end

    end using RSpec 123.should eq 123 # => true false.should eq true # => false
  82. 4. Internal access control

  83. module UserAdmin refine User do def purge! user.associated_records.delete_all! user.delete! end

    end end # in app/controllers/admin_controller.rb class AdminController < ApplicationController using UserAdmin def purge_user User.find(params[:id]).purge! end end
  84. So… why is nobody using refinements?

  85. None
  86. None
  87. “Because they’re just bad.”

  88. YOU ARE EVERYTHING THAT IS WRONG WITH EVERYTHING

  89. “ermm — are they? Why do you think that?”

  90. “Because they’re just bad.”

  91. awesome (adj.) — inspiring awe. Credit: pfala/Flickr

  92. $ gem install awesome # this is not awesome

  93. http://blog.honeybadger.io/benchmarking-ruby- refinements/ “TL;DR: Refinements aren’t slow. Or at least they

    don’t seem to be slower than ‘normal’ methods”
  94. 1. Lack of understanding?

  95. None
  96. None
  97. None
  98. 2. Adding using everywhere is a giant pain in the

    ass?
  99. 3. Rails (and Ruby) doesn't use them

  100. $ fgrep 'refine ' -R rails | wc -l #

    => 0
  101. $ fgrep 'refine ' -R ruby-2.2.3/ext | wc -l #

    => 0
  102. 4. Implementation quirks?

  103. using Shouting "hello".shout # => “HELLO" "hello".respond_to?(:shout) # => false

    "hello".send(:shout) # => NoMethodError ["how", "are", "you"].map(&:shout) # => NoMethodError
  104. 5. Refinements solve a problem that nobody has?

  105. 6. The rise of Object-Oriented Design?

  106. class Shouter def initialize(string) @string = string end def shout

    @string.upcase + "!" end end shouted_hello = Shouter.new("hello") shouted_hello.shout # => "HELLO!"
  107. I don’t know. 1. Lack of understanding 2. Adding using

    everywhere is a pain in the ass 3. Rails (and Ruby) doesn’t use them 4. Implementation quirks 5. They solve a problem that nobody has 6. Object-oriented design
  108. I don’t know. 1. Lack of understanding 2. Adding using

    everywhere is a pain in the ass 3. Rails (and Ruby) doesn’t use them 4. Implementation quirks 5. They solve a problem that nobody has 6. Object-oriented design 7. Because other people have told us that they are “bad”.
  109. Psssst! This isn’t a talk about refinements.

  110. Sharing opinions is good

  111. Blindly adopting opinions is… less good

  112. Dude, globes suck.

  113. Awesome! Sucks.

  114. “I think you’ll find it’s a bit more complicated than

    that.”
  115. Explore for yourselves

  116. Thanks! James Adam @lazyatom lazyatom.com/refinements