Ruby Refinements: the Worst Feature You Ever Loved
-
Upload
paoloperrotta -
Category
Software
-
view
2.101 -
download
0
Transcript of Ruby Refinements: the Worst Feature You Ever Loved
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.
class String def shout upcase + "!" end end
"hello".shout # => "HELLO!"
monkeypatching
In Ruby, you can do this.
method wrappers
class String alias_method :old_length, :length
def length old_length > 5 ? "long" : "short" end end
"War and Peace".length # => "long"
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.
module ModuleThatUsesTheRefinement using StringExtensions
"hello".shout # => "HELLO!" end
using a refinement
Refinement in a class/module. Only active in the marked area.
using StringExtensions
"hello".shout # => "HELLO!" # EOF
using a refinement
Refinement at the top level. Only active in the marked area.
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 scope
The original proposal (Dynamically Scoped Refinements) says yes, in all three cases.
confusing codedef 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.
slows down the languagedef 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.
security threatdef 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.
surprising corner casesdef 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).
•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.
module StringExtensions refine String do def shout upcase + "!" end end end
module ModuleThatUsesTheRefinement using StringExtensions
"hello".shout # => "HELLO!" end
This stuff is the same…
class C using StringExtensions "hello".shout # => "HELLO!" end
C.class_eval do "hello".shout # => NoMethodError end
…but this doesn’t work.
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.
(almost) no confusiondef 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).
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 specific languages
This doesn’t work anymore.
class MyClass < ActiveRecord::Base 2.hours end
# => NoMethodError (undefined method 'hours')
convenience methods
Neither does this.
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).
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.
•they don’t fix monkeypatches in general
•they still have weird corner cases
refinements today: the bad_
The bad of Lexically Scoped Refinements.
•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.
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.
language design is a deep problem
The point of this entire speech: we cannot plan. We need to experiment.