$30 off During Our Annual Pro Sale. View Details »

Why is nobody using Refinements?

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.

lazyatom

November 16, 2015
Tweet

More Decks by lazyatom

Other Decks in Programming

Transcript

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

    View Slide

  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.

    View Slide

  3. What are refinements?
    A mechanism to change the
    behaviour of an object in a
    limited and controlled way

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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"

    View Slide

  10. using
    ( a developer’s guide )

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. class Thing
    def greet
    "hello"
    end
    end
    Thing.new.greet
    using Shouting
    .shout # => NoMethodError

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. class Thing
    using Shouting
    def run
    yield
    end
    end
    Thing.new.run do
    "hello".shout
    end # => NoMethodError

    View Slide

  23. Lexical Scope

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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!”

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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!”

    View Slide

  44. Refinements are activated in
    current and nested lexical scopes

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  50. same class ≠ same scope

    View Slide

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

    View Slide

  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

    View Slide

  53. class hierarchy ≠ scope hierarchy

    View Slide

  54. different file = different scope

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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!”

    View Slide

  62. methods are evaluated using the
    lexical scope at their definition

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  66. blocks are evaluated using the
    lexical scope at their definition

    View Slide

  67. Refinements use lexical scope

    View Slide

  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”

    View Slide

  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

    View Slide

  70. Let’s use refinements!

    View Slide

  71. 1. Monkey patching

    View Slide

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

    View Slide

  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'

    View Slide

  74. Monkey-patching Refinements
    breaks API
    expectations
    cannot affect
    distant code
    makes code harder
    to debug
    leaves lexical clue
    when active

    View Slide

  75. 2. Managing API changes

    View Slide

  76. # Ruby 1.9
    "hello".chars # => #
    # Ruby 2.0+
    "hello".chars # => ["h", "e", "l", "l", "o"]

    View Slide

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

    View Slide

  78. 3. DSLs

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  82. 4. Internal access control

    View Slide

  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

    View Slide

  84. So… why is nobody using
    refinements?

    View Slide

  85. View Slide

  86. View Slide

  87. “Because they’re just bad.”

    View Slide

  88. YOU ARE EVERYTHING
    THAT IS WRONG
    WITH EVERYTHING

    View Slide

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

    View Slide

  90. “Because they’re just bad.”

    View Slide

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

    View Slide

  92. $ gem install awesome
    # this is not awesome

    View Slide

  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”

    View Slide

  94. 1. Lack of understanding?

    View Slide

  95. View Slide

  96. View Slide

  97. View Slide

  98. 2. Adding using everywhere
    is a giant pain in the ass?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  102. 4. Implementation quirks?

    View Slide

  103. using Shouting
    "hello".shout # => “HELLO"
    "hello".respond_to?(:shout) # => false
    "hello".send(:shout) # => NoMethodError
    ["how", "are", "you"].map(&:shout)
    # => NoMethodError

    View Slide

  104. 5. Refinements solve a
    problem that nobody has?

    View Slide

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

    View Slide

  106. class Shouter
    def initialize(string)
    @string = string
    end
    def shout
    @string.upcase + "!"
    end
    end
    shouted_hello = Shouter.new("hello")
    shouted_hello.shout # => "HELLO!"

    View Slide

  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

    View Slide

  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”.

    View Slide

  109. Psssst! This isn’t a talk about
    refinements.

    View Slide

  110. Sharing opinions is good

    View Slide

  111. Blindly adopting opinions
    is… less good

    View Slide

  112. Dude, globes suck.

    View Slide

  113. Awesome!
    Sucks.

    View Slide

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

    View Slide

  115. Explore for yourselves

    View Slide

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

    View Slide