Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
refinements
the worst feature you ever loved
paolo @nusco perrotta
obvious
“Obvious” problems are those that you can solve yourself, if you have some time and knowledge.
non-obvious
“Non-obvious” problems require more effort. You might not know how to solve them at first.
deep
“Deep” problems might not even have an optimal solution. You have to experiment.
-1-
why refinements
Let’s look at the problem that refinements are designed to solve.
class String
def shout
upcase + "!"
end
end
"hello".shout # => "HELLO!"
monkeypatching
In Ruby, you can do this.
three use cases
Three of the most important use cases for monkeypatching.
describe Fixnum do
it "can be added" do
(2 + 2).should == 4
end
end
domain specific languages
require "active_support/all"
1.hour + 20.minutes # => 4800 seconds
convenience methods
method wrappers
class String
alias_method :old_length, :length
def length
old_length > 5 ? "long" : "short"
end
end
"War a...
class String
alias_method :old_length, :length
def length
old_length > 5 ? "long" : "short"
end
end
"War and Peace".length...
local monkeypatches
This is what we want, and what Refinements are.
-2-
refinements
Let’s look at Refinements, the way they were originally conceived.
module StringExtensions
refine String do
def shout
upcase + "!"
end
end
end
defining a refinement
module ModuleThatUsesTheRefinement
using StringExtensions
"hello".shout # => "HELLO!"
end
using a refinement
Refinement in ...
using StringExtensions
"hello".shout # => "HELLO!"
# EOF
using a refinement
Refinement at the top level. Only active in the...
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
class C
"hello".shout # => ?
end
…but will it work here?
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
class D < C
"hello".shout # => ?
end
…or here?
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
C.class_eval do
"hello".shout # => ?
end
…or here?
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
C.class_eval do
"hello".shout # => "HELLO!"
end
dynamic sco...
-3-
refinement gotchas
But people found potential problems with this.
confusing code
def add(x, y)
x + y
end
SomeClass.class_eval do
add(1, 1) # => 2
end
SomeOtherClass.class_eval do
add(1, 1)...
slows down the language
def add(x, y)
x + y
end
SomeClass.class_eval do
add(1, 1) # => 2
end
SomeOtherClass.class_eval do
...
security threat
def add(x, y)
x + y
end
SomeClass.class_eval do
add(1, 1) # => 2
end
SomeOtherClass.class_eval do
add(1, 1...
surprising corner cases
def add(x, y)
x + y
end
SomeClass.class_eval do
add(1, 1) # => 2
end
SomeOtherClass.class_eval do
...
•they fix monkeypatches
refinements: the good
The good of Dynamically Scoped Refinements.
•they make Ruby code potentially confusing
•they impact performance
•they impact security
•they have weird corner cases
re...
are dynamically scoped
refinements worth it?
no.
This is what the core team decided right before Ruby 2.0.
-4-
refinements today
So we have a different versions of Refinements instead.
module StringExtensions
refine String do
def shout
upcase + "!"
end
end
end
module ModuleThatUsesTheRefinement
using Strin...
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
C.class_eval do
"hello".shout # => NoMethodError
end
…but t...
lexical scope
class C
using StringExtensions
"hello".shout # => "HELLO!"
end
C.class_eval do
"hello".shout # => NoMethodEr...
(almost) no confusion
def add(x, y)
x + y
end
SomeClass.class_eval do
add(1, 1) # => 2
end
SomeOtherClass.class_eval do
ad...
the three use cases again
But how do these new Refinements apply to the three main use cases of Monkeypatching?
describe Fixnum do
it("can be added") do
(2 + 2).should == 4
end
end
# => NoMethodError (undefined method 'it')
domain spe...
class MyClass < ActiveRecord::Base
2.hours
end
# => NoMethodError (undefined method 'hours')
convenience methods
Neither d...
class MyClass < ActiveRecord::Base
using ActiveSupport::CoreExtensions
2.hours # => 7200 seconds
end
convenience methods
(...
module StringExtensions
refine String do
def length
super > 5 ? "long" : "short"
end
end
end
using StringExtensions
"War a...
•they don’t fix monkeypatches in general
•they still have weird corner cases
refinements today: the bad_
The bad of Lexica...
•they do fix some monkeypatching cases
•they don’t make the code confusing
•they don’t impact performance or security
•…an...
-5-
a deep problem
describe Fixnum do
it "can be added" do
(2 + 2).should == 4
end
end
This code relies on multiple features that do not seem...
language design
is a deep problem
The point of this entire speech: we cannot plan. We need to experiment.
thank you
Buy this book. ;)
Upcoming SlideShare
Loading in …5
×

Ruby Refinements: the Worst Feature You Ever Loved

2,967 views

Published on

Are Ruby's Refinements the best idea since blocks and modules, or a terrible mistake? Decide for yourself. (Includes presenter's notes right on the slides. If you want a version without the notes, for presenting it yourself - just mail me at paolo.nusco.perrotta on Gmail).

Published in: Software
  • Be the first to comment

Ruby Refinements: the Worst Feature You Ever Loved

  1. 1. refinements the worst feature you ever loved paolo @nusco perrotta
  2. 2. obvious “Obvious” problems are those that you can solve yourself, if you have some time and knowledge.
  3. 3. non-obvious “Non-obvious” problems require more effort. You might not know how to solve them at first.
  4. 4. deep “Deep” problems might not even have an optimal solution. You have to experiment.
  5. 5. -1- why refinements Let’s look at the problem that refinements are designed to solve.
  6. 6. class String def shout upcase + "!" end end "hello".shout # => "HELLO!" monkeypatching In Ruby, you can do this.
  7. 7. three use cases Three of the most important use cases for monkeypatching.
  8. 8. describe Fixnum do it "can be added" do (2 + 2).should == 4 end end domain specific languages
  9. 9. require "active_support/all" 1.hour + 20.minutes # => 4800 seconds convenience methods
  10. 10. method wrappers class String alias_method :old_length, :length def length old_length > 5 ? "long" : "short" end end "War and Peace".length # => "long"
  11. 11. class String alias_method :old_length, :length def length old_length > 5 ? "long" : "short" end end "War and Peace".length # => "long" !This example also shows why monkeypatches are dangerous: because they are global.
  12. 12. local monkeypatches This is what we want, and what Refinements are.
  13. 13. -2- refinements Let’s look at Refinements, the way they were originally conceived.
  14. 14. module StringExtensions refine String do def shout upcase + "!" end end end defining a refinement
  15. 15. module ModuleThatUsesTheRefinement using StringExtensions "hello".shout # => "HELLO!" end using a refinement Refinement in a class/module. Only active in the marked area.
  16. 16. using StringExtensions "hello".shout # => "HELLO!" # EOF using a refinement Refinement at the top level. Only active in the marked area.
  17. 17. class C using StringExtensions "hello".shout # => "HELLO!" end class C "hello".shout # => ? end …but will it work here?
  18. 18. class C using StringExtensions "hello".shout # => "HELLO!" end class D < C "hello".shout # => ? end …or here?
  19. 19. class C using StringExtensions "hello".shout # => "HELLO!" end C.class_eval do "hello".shout # => ? end …or here?
  20. 20. class C using StringExtensions "hello".shout # => "HELLO!" end C.class_eval do "hello".shout # => "HELLO!" end dynamic scope The original proposal (Dynamically Scoped Refinements) says yes, in all three cases.
  21. 21. -3- refinement gotchas But people found potential problems with this.
  22. 22. confusing code def add(x, y) x + y end SomeClass.class_eval do add(1, 1) # => 2 end SomeOtherClass.class_eval do add(1, 1) # => "11" end using FloatPointMath # refines Fixnum#+ add(1, 1) # => 2.0 You need to look at the implementations to understand the interface.
  23. 23. slows down the language def add(x, y) x + y end SomeClass.class_eval do add(1, 1) # => 2 end SomeOtherClass.class_eval do add(1, 1) # => "11" end using FloatPointMath # refines Fixnum#+ add(1, 1) # => 2.0 The interpreter also needs to do the same, so Refinements can slow down the entire interpreter.
  24. 24. security threat def add(x, y) x + y end SomeClass.class_eval do add(1, 1) # => 2 end SomeOtherClass.class_eval do add(1, 1) # => "11" end using FloatPointMath # refines Fixnum#+ add(1, 1) # => 2.0 Less understanding potentially means less security.
  25. 25. surprising corner cases def add(x, y) x + y end SomeClass.class_eval do add(1, 1) # => 2 end SomeOtherClass.class_eval do add(1, 1) # => "11" end using FloatPointMath # refines Fixnum#+ add(1, 1) # => 2.0 Some things might not work as you expect. (For example, the last line doesn’t work in irb).
  26. 26. •they fix monkeypatches refinements: the good The good of Dynamically Scoped Refinements.
  27. 27. •they make Ruby code potentially confusing •they impact performance •they impact security •they have weird corner cases refinements: the bad_ The bad of Dynamically Scoped Refinements.
  28. 28. are dynamically scoped refinements worth it?
  29. 29. no. This is what the core team decided right before Ruby 2.0.
  30. 30. -4- refinements today So we have a different versions of Refinements instead.
  31. 31. module StringExtensions refine String do def shout upcase + "!" end end end module ModuleThatUsesTheRefinement using StringExtensions "hello".shout # => "HELLO!" end This stuff is the same…
  32. 32. class C using StringExtensions "hello".shout # => "HELLO!" end C.class_eval do "hello".shout # => NoMethodError end …but this doesn’t work.
  33. 33. lexical scope class C using StringExtensions "hello".shout # => "HELLO!" end C.class_eval do "hello".shout # => NoMethodError end No matter how you change the scope, Refinements *only* work in the lexical scope.
  34. 34. (almost) no confusion def add(x, y) x + y end SomeClass.class_eval do add(1, 1) # => 2 end SomeOtherClass.class_eval do add(1, 1) # => 2 end using FloatPointMath # refines Fixnum#+ add(1, 1) # => 2 (Note that the very last line might still surprise you, until you wrap your head around lexical scoping).
  35. 35. the three use cases again But how do these new Refinements apply to the three main use cases of Monkeypatching?
  36. 36. describe Fixnum do it("can be added") do (2 + 2).should == 4 end end # => NoMethodError (undefined method 'it') domain specific languages This doesn’t work anymore.
  37. 37. class MyClass < ActiveRecord::Base 2.hours end # => NoMethodError (undefined method 'hours') convenience methods Neither does this.
  38. 38. class MyClass < ActiveRecord::Base using ActiveSupport::CoreExtensions 2.hours # => 7200 seconds end convenience methods (Unless I use using() in each and every class where I want the refinements - not very DRY).
  39. 39. module StringExtensions refine String do def length super > 5 ? "long" : "short" end end end using StringExtensions "War and Peace".length # => "long" method wrappers This one does work, thans to the way “super” works in Refinements.
  40. 40. •they don’t fix monkeypatches in general •they still have weird corner cases refinements today: the bad_ The bad of Lexically Scoped Refinements.
  41. 41. •they do fix some monkeypatching cases •they don’t make the code confusing •they don’t impact performance or security •…and besides, they open the road for more refinements today: the good The good of Lexically Scoped Refinements.
  42. 42. -5- a deep problem
  43. 43. describe Fixnum do it "can be added" do (2 + 2).should == 4 end end This code relies on multiple features that do not seem to make much sense (singleton classes, optional parentheses), but ended up being “abused” by the community. It is hard to plan for this.
  44. 44. language design is a deep problem The point of this entire speech: we cannot plan. We need to experiment.
  45. 45. thank you
  46. 46. Buy this book. ;)

×