From: Yehuda Katz Date: 2010-12-06T14:09:47+09:00 Subject: [ruby-core:33588] Re: [Ruby 1.9-Feature#4085] Refinements and nested methods --001485f91eb46bab200496b6e593 Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: quoted-printable I disagree with this argument. If the feature behaved as you expected, you would never be able to include a refinement into a scope without reading th= e implementation of all callees to make sure that the refinements you include= d do not conflict with methods used downstream. Consider this (very simplified) scenario: module ActiveSupport refine String do def camelize dup.split(/_/).map{ |word| word.capitalize }.join('') end end end And now consider some class that uses this refinement: class Constantizer using ActiveSupport def initialize(str) @class_name =3D str.camelize end def const Object.const_get(@class_name) end end Now let's say that I have created my own refinement which refines camelize: module MyApp refine String do def camelize "_" + capitalize + "_" end end end And now I go ahead and use that refinement: class MyApp::Application using MyApp def initialize @cache =3D {} end def cache(name) @cache[name.camelize] =3D Constantizer.new(name).const end def lookup(name) @cache[name.camelize] end end This illustrates that when using a refinement, you would be forced to understand the implementation of all callees, to make sure that you are not accidentally leaking a refinement into a scope that doesn't expect it. Sinc= e the point of encapsulating this functionality is to avoid callers needing t= o know the implementation of the callee (and to allow the callee to change it= s implementation as long as it doesn't modify the interface), this behavior i= s unexpected. Instead of making it easy to isolate the effects of a refinement to a scope= , those effects could leak into other scopes that do not expect to be mutated in that way. Essentially, by leaking the refinement, we are now making every single method call in the callee a part of the public interface (as you believe that the fact that #map calls #each is part of the public interface). While we do have a few informal interfaces that behave this way, the vast majorit= y of Ruby code does not, and this behavior would render the feature much less useful, and result in failure to meet the goals of the feature (the ability to apply a refinement to an area of code without fearing breakage of other unrelated code). Yehuda Katz Architect | Strobe (ph) 718.877.1325 On Wed, Nov 24, 2010 at 11:45 AM, Dave Thomas wrote= : > Issue #4085 has been updated by Dave Thomas. > > > module Quux > using FooExt > > foo =3D Foo.new > foo.bar # =3D> FooExt#bar > foo.baz # =3D> Foo#bar > end > > This behavior makes me nervous=97I can see arguments for it, but at the s= ame > time I can see in leading to problems, particularly in well structured > classes where a large set of behaviors is defined in terms of one method. > I'm sure the syntax below is wrong, but look at the spirit of it. > > module DoubleEach > refine Array do > def each > super do |val| > yield 2*val > end > end > end > end > > using DoubleEach > > [ 1, 2, 3 ].each {|v| p v } #=3D> 2, 4, 6 > > [ 1, 2, 3 ].min #=3D> 1 > > That would be surprising to me, as I'd expect the behavior of all the > Enumerable methods, which depend on each, to change if I change the behav= ior > of each. > ---------------------------------------- > http://redmine.ruby-lang.org/issues/show/4085 > > ---------------------------------------- > http://redmine.ruby-lang.org > > --001485f91eb46bab200496b6e593 Content-Type: text/html; charset=windows-1252 Content-Transfer-Encoding: quoted-printable I disagree with this argument. If the feature behaved as you expected, you = would never be able to include a refinement into a scope without reading th= e implementation of all callees to make sure that the refinements you inclu= ded do not conflict with methods used downstream.

Consider this (very simplified) scenario:

module ActiveSupport
=A0=A0refine String do
=A0= =A0 =A0def camelize
=A0=A0 =A0 =A0dup.split(/_/).map{ |word| word= .capitalize }.join('')
=A0=A0 =A0end
=A0=A0end
end

And now consider some class that uses this refinement:

class Constantizer
=A0=A0using ActiveSupport

=
=A0=A0def initialize(str)
=A0=A0 =A0@class_name =3D str.camelize<= /div>
=A0=A0end

=A0=A0def const
=A0= =A0 =A0Object.const_get(@class_name)
=A0=A0end
end

Now let's say that I have created my own refinemen= t which refines camelize:

module MyApp
=A0=A0refine String do
=A0=A0 =A0def camelize
=A0=A0 =A0 =A0"_" + capitalize = + "_"
=A0=A0 =A0end
=A0=A0end
end

And now I go ahead and use that refinement:

class = MyApp::Application
=A0=A0using MyApp

=A0= =A0def initialize
=A0=A0 =A0@cache =3D {}
=A0=A0end

=A0=A0def cache(name)
=A0=A0 =A0@cache[name.camelize] =3D Co= nstantizer.new(name).const
=A0=A0end

=A0= =A0def lookup(name)
=A0=A0 =A0@cache[name.camelize]
=A0= =A0end
end

This illustrates that when using a refinement, you woul= d be forced to understand the implementation of all callees, to make sure t= hat you are not accidentally leaking a refinement into a scope that doesn&#= 39;t expect it. Since the point of encapsulating this functionality is to a= void callers needing to know the implementation of the callee (and to allow= the callee to change its implementation as long as it doesn't modify t= he interface), this behavior is unexpected.

Instead of making it easy to isolate the effects of a r= efinement to a scope, those effects could leak into other scopes that do no= t expect to be mutated in that way.

Essentially, b= y leaking the refinement, we are now making every single method call in the= callee a part of the public interface (as you believe that the fact that #= map calls #each is part of the public interface). While we do have a few in= formal interfaces that behave this way, the vast majority of Ruby code does= not, and this behavior would render the feature much less useful, and resu= lt in failure to meet the goals of the feature (the ability to apply a refi= nement to an area of code without fearing breakage of other unrelated code)= .

Yehuda Katz
Architect | Strobe
(ph) 718.877.13= 25


On Wed, Nov 24, 2010 at 11:45 AM, Dave T= homas <redmin= e@ruby-lang.org> wrote:
Issue #4085 has been updated by Dave Thomas.


=A0module Quux
=A0 =A0using FooExt

=A0 =A0foo =3D Foo.new
=A0 =A0foo.bar =A0# =3D> FooExt#bar
=A0 =A0foo.baz =A0# =3D> Foo#bar
=A0end

This behavior makes me nervous=97I can see arguments for it, but at t= he same time I can see in leading to problems, particularly in well structu= red classes where a large set of behaviors is defined in terms of one metho= d. I'm sure the syntax below is wrong, but look at the spirit of it.
=A0module DoubleEach
=A0 =A0refine Array do
=A0 =A0 =A0def each
=A0 =A0 =A0 =A0 super do |val|
=A0 =A0 =A0 =A0 =A0 yield 2*val
=A0 =A0 =A0 =A0 end
=A0 =A0 =A0end
=A0 =A0end
=A0end

=A0using DoubleEach

=A0[ 1, 2, 3 ].each {|v| p v } =A0 #=3D> 2, 4, 6

=A0[ 1, 2, 3 ].min =A0 =A0#=3D> 1

That would be surprising to me, as I'd expect the behavior of all the E= numerable methods, which depend on each, to change if I change the behavior= of each.
----------------------------------------
----------------------------------------

--001485f91eb46bab200496b6e593--