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

Ruby Refinements: the Worst Feature You Ever Loved

Jul 28, 2015

Download

Software

paoloperrotta
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Ruby Refinements: the Worst Feature You Ever Loved

refinements the worst feature you ever loved

paolo @nusco perrotta

Page 2: 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.

Page 3: Ruby Refinements: the Worst Feature You Ever Loved

non-obvious

“Non-obvious” problems require more effort. You might not know how to solve them at first.

Page 4: Ruby Refinements: the Worst Feature You Ever Loved

deep

“Deep” problems might not even have an optimal solution. You have to experiment.

Page 5: Ruby Refinements: the Worst Feature You Ever Loved

-1- why refinements

Let’s look at the problem that refinements are designed to solve.

Page 6: Ruby Refinements: the Worst Feature You Ever Loved

class String def shout upcase + "!" end end

"hello".shout # => "HELLO!"

monkeypatching

In Ruby, you can do this.

Page 7: Ruby Refinements: the Worst Feature You Ever Loved

three use cases

Three of the most important use cases for monkeypatching.

Page 8: Ruby Refinements: the Worst Feature You Ever Loved

describe Fixnum do it "can be added" do (2 + 2).should == 4 end end

domain specific languages

Page 9: Ruby Refinements: the Worst Feature You Ever Loved

require "active_support/all"

1.hour + 20.minutes # => 4800 seconds

convenience methods

Page 10: Ruby Refinements: the Worst Feature You Ever Loved

method wrappers

class String alias_method :old_length, :length

def length old_length > 5 ? "long" : "short" end end

"War and Peace".length # => "long"

Page 11: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 12: Ruby Refinements: the Worst Feature You Ever Loved

local monkeypatches

This is what we want, and what Refinements are.

Page 13: Ruby Refinements: the Worst Feature You Ever Loved

-2- refinements

Let’s look at Refinements, the way they were originally conceived.

Page 14: Ruby Refinements: the Worst Feature You Ever Loved

module StringExtensions refine String do def shout upcase + "!" end end end

defining a refinement

Page 15: Ruby Refinements: the Worst Feature You Ever Loved

module ModuleThatUsesTheRefinement using StringExtensions

"hello".shout # => "HELLO!" end

using a refinement

Refinement in a class/module. Only active in the marked area.

Page 16: Ruby Refinements: the Worst Feature You Ever Loved

using StringExtensions

"hello".shout # => "HELLO!" # EOF

using a refinement

Refinement at the top level. Only active in the marked area.

Page 17: Ruby Refinements: the Worst Feature You Ever Loved

class C using StringExtensions "hello".shout # => "HELLO!" end

class C "hello".shout # => ? end

…but will it work here?

Page 18: Ruby Refinements: the Worst Feature You Ever Loved

class C using StringExtensions "hello".shout # => "HELLO!" end

class D < C "hello".shout # => ? end

…or here?

Page 19: Ruby Refinements: the Worst Feature You Ever Loved

class C using StringExtensions "hello".shout # => "HELLO!" end

C.class_eval do "hello".shout # => ? end

…or here?

Page 20: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 21: Ruby Refinements: the Worst Feature You Ever Loved

-3- refinement gotchas

But people found potential problems with this.

Page 22: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 23: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 24: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 25: Ruby Refinements: the Worst Feature You Ever Loved

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

Page 26: Ruby Refinements: the Worst Feature You Ever Loved

•they fix monkeypatches

refinements: the good

The good of Dynamically Scoped Refinements.

Page 27: Ruby Refinements: the Worst Feature You Ever Loved

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

Page 28: Ruby Refinements: the Worst Feature You Ever Loved

are dynamically scoped refinements worth it?

Page 29: Ruby Refinements: the Worst Feature You Ever Loved

no.

This is what the core team decided right before Ruby 2.0.

Page 30: Ruby Refinements: the Worst Feature You Ever Loved

-4- refinements today

So we have a different versions of Refinements instead.

Page 31: Ruby Refinements: the Worst Feature You Ever Loved

module StringExtensions refine String do def shout upcase + "!" end end end

module ModuleThatUsesTheRefinement using StringExtensions

"hello".shout # => "HELLO!" end

This stuff is the same…

Page 32: Ruby Refinements: the Worst Feature You Ever Loved

class C using StringExtensions "hello".shout # => "HELLO!" end

C.class_eval do "hello".shout # => NoMethodError end

…but this doesn’t work.

Page 33: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 34: Ruby Refinements: the Worst Feature You Ever Loved

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

Page 35: Ruby Refinements: the Worst Feature You Ever Loved

the three use cases again

But how do these new Refinements apply to the three main use cases of Monkeypatching?

Page 36: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 37: Ruby Refinements: the Worst Feature You Ever Loved

class MyClass < ActiveRecord::Base 2.hours end

# => NoMethodError (undefined method 'hours')

convenience methods

Neither does this.

Page 38: Ruby Refinements: the Worst Feature You Ever Loved

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

Page 39: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 40: Ruby Refinements: the Worst Feature You Ever Loved

•they don’t fix monkeypatches in general

•they still have weird corner cases

refinements today: the bad_

The bad of Lexically Scoped Refinements.

Page 41: Ruby Refinements: the Worst Feature You Ever Loved

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

Page 42: Ruby Refinements: the Worst Feature You Ever Loved

-5- a deep problem

Page 43: Ruby Refinements: the Worst Feature You Ever Loved

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.

Page 44: Ruby Refinements: the Worst Feature You Ever Loved

language design is a deep problem

The point of this entire speech: we cannot plan. We need to experiment.

Page 45: Ruby Refinements: the Worst Feature You Ever Loved

thank you

Page 46: Ruby Refinements: the Worst Feature You Ever Loved

Buy this book. ;)