ruby refinements: the worst feature you ever loved

46
refinements the worst feature you ever loved paolo @nusco perrotta

Upload: paoloperrotta

Post on 28-Jul-2015

2.099 views

Category:

Software


0 download

TRANSCRIPT

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

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

-3- refinement gotchas

But people found potential problems with this.

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

refinements: the bad_

The bad of Dynamically Scoped Refinements.

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

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

thank you

Buy this book. ;)