Ruby Forum Ruby-core > Scoping and locating definitions

Posted by Jos Backus (Guest)
on 19.01.2007 16:29
(Received via mailing list)
Consider the following:

lizzy:~% cat r
require 'lib'
# from 'lib' import 'Foo'

f = Foo.new("string")
p f.bar

p 'abc'.rot13
lizzy:~% cat lib.rb
class String
  def rot13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end
end

class Foo
  def initialize(s)
    @s = s
  end

  def bar
    @s.rot13
  end
end
lizzy:~% ruby r
"fgevat"
"nop"
lizzy:~%

What if I wanted String#rot13 to _not_ be visible in file `r' (because I
didn't ask for it)?  How do I contain the change to String to lib.rb 
only? Or
perhaps to Foo?

(The idea is to be able to reduce side-effects from require'ing files. 
Python
forces one to be explicit about this, and that seems like a Good Thing.)

On a related note: how does one go about finding _where_ String#rot13 
was
defined? One possible way would be for .methods to return an Array of
BoundMethods instead of Strings and have file and linenumber information 
be
part of BoundMethod (if possible).

Thoughts?
Posted by Pit Capitain (Guest)
on 19.01.2007 16:31
(Received via mailing list)
Jos Backus schrieb:
> (...)
> What if I wanted String#rot13 to _not_ be visible in file `r' (because I
> didn't ask for it)?  How do I contain the change to String to lib.rb only? Or
> perhaps to Foo?

Jos, using this library

   http://raa.ruby-lang.org/project/import_module

you can get the desired behaviour. The library hasn't been updated for
some time, but it still seems to work with Ruby 1.8.5. I got some
warnings, but it is easy to change the code to get rid of them.

Regards,
Pit
Posted by Eric Hodel (Guest)
on 19.01.2007 16:31
(Received via mailing list)
On Jan 18, 2007, at 15:53, Jos Backus wrote:

> lizzy:~% cat lib.rb
>
> `r' (because I
> didn't ask for it)?  How do I contain the change to String to  
> lib.rb only?

Currently you can't.

> Or perhaps to Foo?

Define Foo#rot13

> On a related note: how does one go about finding _where_  
> String#rot13 was
> defined? One possible way would be for .methods to return an Array of
> BoundMethods instead of Strings and have file and linenumber  
> information be
> part of BoundMethod (if possible).
>
> Thoughts?

grep?

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net

I LIT YOUR GEM ON FIRE!
Posted by Jos Backus (Guest)
on 19.01.2007 18:18
(Received via mailing list)
Hi Eric,

On Fri, Jan 19, 2007 at 05:03:16PM +0900, Eric Hodel wrote:
> >
> >    @s = s
> >
> >What if I wanted String#rot13 to _not_ be visible in file  
> >`r' (because I
> >didn't ask for it)?  How do I contain the change to String to  
> >lib.rb only?
> 
> Currently you can't.

Right. I think it's important that the language supports this though.
`require' can pollute the caller's code in uncontrollable ways, and it 
would
be very useful to have control over this behavior.

> >
> >Thoughts?
> 
> grep?

Heh. The interpreter knows where it saw the most recent method 
definition; why
not make this information available?

Python, with its closed classes, doesn't have this "issue" because 
method
definitions only live in one place per class. A colleague at work 
considers
the fact that it's hard to find out where methods are defined a 
weakness, and
I'd like to present him with a better answer than `grep'. It sounds like 
some
more introspection could solve this "issue".

Thanks,
Posted by Jos Backus (Guest)
on 19.01.2007 18:19
(Received via mailing list)
On Fri, Jan 19, 2007 at 06:40:03PM +0900, Pit Capitain wrote:
> 
> you can get the desired behaviour. The library hasn't been updated for 
> some time, but it still seems to work with Ruby 1.8.5. I got some 
> warnings, but it is easy to change the code to get rid of them.

Thanks Pit, I'll have a look and will report back.
Posted by Jos Backus (Guest)
on 20.01.2007 00:23
(Received via mailing list)
On Sat, Jan 20, 2007 at 02:18:19AM +0900, Jos Backus wrote:
> >   http://raa.ruby-lang.org/project/import_module
> > 
> > you can get the desired behaviour. The library hasn't been updated for 
> > some time, but it still seems to work with Ruby 1.8.5. I got some 
> > warnings, but it is easy to change the code to get rid of them.
>  
> Thanks Pit, I'll have a look and will report back.

After looking at this I'm not sure how this helps me. Can you give an 
example
please?

I guess I wasn't describing the problem fully. As I see it, there are 
two
issues: restricting imports (which import_module alledgedly addresses) 
and
restricting exports. The earlier example causes String to be extended 
with
.rot13 for everybody who includes `lib.rb'. I'd like there to be some 
way that
String#rot13 would not be available outside `lib.rb' unless asked for. 
IOW,
in addition to being able to control what is being imported it would be 
good
to be able to control what is being exported.

As an example, consider the following syntax. Its effect would be that 
String
is only extended inside class Foo; references to String#rot13 outside of 
class
Foo should fail. This would allow one to locally extend String without
polluting String everywhere as is the case today.

    class Foo
      with class String
  def rot13
    tr 'A-Za-z', 'N-ZA-Mn-za-m'
  end
      end
      def initialize(s)
  @s = s
      end

      def bar
  @s.rot13
      end
    end

As an aside, a while ago I ran into the fact that Process is a built-in 
class
whereas I wanted my own class to be named Process instead, given that I 
wasn't
going to use the built-in Process class anyway.
Perhaps it would be better if Ruby made less classes available without
require'ing them (maybe limited to a set of primitives). So to get what 
one
has today one would say

    require 'core'

or something like it. core in turn could be subdivided into useful 
subgroups.
Posted by Nicolas Desprès (Guest)
on 20.01.2007 02:14
(Received via mailing list)
On 1/20/07, Jos Backus <jos@catnook.com> wrote:
> On Sat, Jan 20, 2007 at 02:18:19AM +0900, Jos Backus wrote:
> > On Fri, Jan 19, 2007 at 06:40:03PM +0900, Pit Capitain wrote:
> > > Jos Backus schrieb:
> > > >(...)

[...]

>
> As an aside, a while ago I ran into the fact that Process is a built-in class
> whereas I wanted my own class to be named Process instead, given that I wasn't
> going to use the built-in Process class anyway.

Maybe, you can copy the Process class to ProcessOriginal and then
extend the Process class to fit your desired behavior. (I don't now
how to "dup" a class : maybe the dup method do the job ; I haven't
checked)

> Perhaps it would be better if Ruby made less classes available without
> require'ing them (maybe limited to a set of primitives). So to get what one
> has today one would say
>

The ruby-core is already small. I think it's hard to split it in
several sub groups and starting doing that is the first step to an
unless sub grouping...

Cheers,
Posted by Jos Backus (Guest)
on 20.01.2007 05:29
(Received via mailing list)
On Sat, Jan 20, 2007 at 10:13:15AM +0900, Nicolas Desprs wrote:
> >class
> >whereas I wanted my own class to be named Process instead, given that I 
> >wasn't
> >going to use the built-in Process class anyway.
> 
> Maybe, you can copy the Process class to ProcessOriginal and then
> extend the Process class to fit your desired behavior. (I don't now
> how to "dup" a class : maybe the dup method do the job ; I haven't
> checked)

Well, it's not there there is no workaround (I called it AProcess) but I
shouldn't have to. I'd rather specify what I need in the first place, it
avoids conflicts like these in the first place.

Part of a possible solution would be to use modules as namespaces, e.g.
Std::Kernel::Process.

> >Perhaps it would be better if Ruby made less classes available without
> >require'ing them (maybe limited to a set of primitives). So to get what one
> >has today one would say
> >
> 
> The ruby-core is already small. I think it's hard to split it in
> several sub groups and starting doing that is the first step to an
> unless sub grouping...

I don't agree. The Java core is much smaller; it perhaps would make for 
a good
precedent/model to follow.

Thanks for the input, Nicolas.

Cheers,
Posted by Sam Roberts (Guest)
on 20.01.2007 18:45
(Received via mailing list)
Quoting nicolas.despres@gmail.com, on Sat, Jan 20, 2007 at 10:13:15AM 
+0900:
> >built-in class whereas I wanted my own class to be named Process
> >instead, given that I wasn't going to use the built-in Process class
> >anyway.

So replace Process with your class:

Process = Class.new # Causes a warning, as well it should, but you
  # want to do it anyhow...

class Process
  #...
end

One day you may use a library that uses Process, and then you may regret
this, it would be more typical to put your Process in a module:

module MyModule
  class Process
    #...
  end

  class ProcessUser
    def use
       Process.new.increase_speed!
    end

  end

end
Posted by Pit Capitain (Guest)
on 20.01.2007 20:40
(Received via mailing list)
Jos Backus schrieb:
> After looking at this I'm not sure how this helps me. Can you give an example
> please?

Jos, here's your original example using import-module:

lib.rb:

   require "import-module"

   module LibStringEnhancements
     def rot13
       tr 'A-Za-z', 'N-ZA-Mn-za-m'
     end
   end

   def within_lib
     String.import_module LibStringEnhancements do
       yield
     end
   end

   class Foo
     def initialize(s)
       @s = s
     end

     def bar
       within_lib do
         @s.rot13
       end
     end
   end

r.rb:

   require 'lib'

   f = Foo.new("string")
   p f.bar                         # => "fgevat"

   p 'abc'.rot13 rescue puts $!    # => undefined method (...)

   within_lib do
     p 'abc'.rot13                 # => "nop"
   end

I haven't really used import-module yet, so I can't tell you much about
it. The code above just shows how I would try to solve your problem.

Feel free to ask again if this isn't what you wanted or if you have more
questions.

Regards,
Pit
Posted by Jos Backus (Guest)
on 20.01.2007 21:52
(Received via mailing list)
On Sun, Jan 21, 2007 at 02:44:49AM +0900, Sam Roberts wrote:
> So replace Process with your class:
> 
> Process = Class.new # Causes a warning, as well it should, but you
>   # want to do it anyhow...
> 
> class Process
>   #...
> end

Personally I think that's ugly. Except for some primitive classes I 
shouldn't
even be able to access it unless I specifically ask for it, just like I 
don't
get the Thread class unless I `require "thread"'. The idea is to reduce
namespace pollution and possibly unwanted side-effects.

>        Process.new.increase_speed!
>     end
> 
>   end
> 
> end

While I agree that this is good practice, by default at least some of 
the
currently-standard classes should be in their own module already. 
Perhaps all
except for a number of primitive types.  One could then access these 
other
classes using something along the lines of `using namespace std;' in 
C++.

Thanks for the feedback, Sam.
Posted by Jos Backus (Guest)
on 21.01.2007 00:02
(Received via mailing list)
On Sun, Jan 21, 2007 at 04:39:52AM +0900, Pit Capitain wrote:
> Jos Backus schrieb:
> >After looking at this I'm not sure how this helps me. Can you give an 
> >example
> >please?
> 
> Jos, here's your original example using import-module:
> 
[great example snipped]
> I haven't really used import-module yet, so I can't tell you much about 
> it. The code above just shows how I would try to solve your problem.
> 
> Feel free to ask again if this isn't what you wanted or if you have more 
> questions.

That's a great example. I understand how this could help much better 
now. But
I'm not sure how well this would scale if you had multiple
classes/modules/methods you'd want to restrict this way. Also, turning 
rot13
into a module method doesn't look like the most natural way to me 
because I'd
really like to be able to just add it to String. And having to add it to 
each
method call, while good as far a granularity goes, will cause a lot of 
extra
work, which is why my example used a class-based approach.

Perhaps what I'm really looking for is a way to tag classes and methods 
for
conditional export, such that they are only available in other classes 
if
specifically imported. This will almost certainly not be possible 
without some
language changes.

The background behind this is that I'm trying to sell Ruby at work as a 
viable
alternative to Perl/Python, and this lack of explicit import/export 
controls
is seen as a weakness. As one colleague put it "How do I find out where 
rot13
is defined? Finding it in Python is straightforward (because classes and
functions have to imported explicitly) but in Ruby it is not. This poses 
a
long-term readability problem.".

(My apologies for the poor description of the issue as I see it. I'm 
clearly
not a language designer.)

Thanks for your excellent (as always) suggestions, Pit.
Posted by Pit Capitain (Guest)
on 21.01.2007 00:20
(Received via mailing list)
Jos Backus schrieb:
> (...)
> The background behind this is that I'm trying to sell Ruby at work as a viable
> alternative to Perl/Python, and this lack of explicit import/export controls
> is seen as a weakness. As one colleague put it "How do I find out where rot13
> is defined? Finding it in Python is straightforward (because classes and
> functions have to imported explicitly) but in Ruby it is not. This poses a
> long-term readability problem.".

Jos, just out of curiosity, could you show us (or me via private mail,
if you think it's too offtopic for ruby-core) how you would do what you
want in Python?

Regards,
Pit
Posted by Jos Backus (Guest)
on 21.01.2007 00:31
(Received via mailing list)
On Sun, Jan 21, 2007 at 08:19:47AM +0900, Pit Capitain wrote:
> Jos, just out of curiosity, could you show us (or me via private mail, 
> if you think it's too offtopic for ruby-core) how you would do what you 
> want in Python?

Pit, I'm really sorry but I'm not well-versed enough in Python to be 
able to
demonstrate this. I've cc'd my colleague and will ask him for an 
example.

Thanks!
Posted by Terry Weissman (Guest)
on 21.01.2007 01:02
(Received via mailing list)
On Jan 20, 2007, at 3:31 PM, Jos Backus wrote:

> example.
I'm that colleague.  I'm not the world's best Python expert either,
but I think the answer is that "you don't".

That is, you don't extend the String class to add a rot13 method,
because Python doesn't have open classes like that.  (I think it can
possibly be done, but only awkwardly; the language does not encourage
it.)

If you want to provide rot13 functionaliy, you'll put it in a
function in a package that does it.  Anyone who wants to rot13 will
explicitly import your package.

The point is, in Python, if file A chooses to import package B, it is
very unlikely to affect code operating in completely unrelated file
C.  In Ruby, it's entirely possible that the import of B could have
broken something that C depended on.

If you are in a world where all the code is written by good,
competent programmers who've agreed on the ground rules, Ruby can
give them all a lot of power.  But if you're not so sure about your
fellow programmers, or about code that you are loading off the net,
then Ruby's model offers much less protection than Python's.

This kind of thing would make me much more scared to work on a big
project in Ruby than I would be in Python.  (I haven't yet had the
opportunity to work on a big project in either, so I'm not actually
talking from experience.)

- Terry
Posted by Wayne Vucenic (Guest)
on 21.01.2007 19:58
(Received via mailing list)
Hi Terry,

On 1/20/07, Terry Weissman <terry@liveops.com> wrote:
> The point is, in Python, if file A chooses to import package B, it is
> very unlikely to affect code operating in completely unrelated file
> C.  In Ruby, it's entirely possible that the import of B could have
> broken something that C depended on.

As you pointed out, this is a trade off between having a powerful
language and having a safe one.  Most languages that I know won't let
me redefine the + operator for integers.  I really like that Ruby lets
me do it.  (I haven't needed to redefine +, at least not yet.)

I can imagine a reasonable language that has the features you'd like.
I just wouldn't call it Ruby.

I'm glad Matz chose to position Ruby much more towards the "powerful"
end of the spectrum rather than the "safe" end.  There are domains
that can benefit from a lot of safety, and others that can benefit
from a lot of power.  If I were programming the control system for a
nuclear reactor, Ruby probably wouldn't be my first choice.  But
that's one reason I don't do those kinds of projects.

> If you are in a world where all the code is written by good,
> competent programmers who've agreed on the ground rules, Ruby can
> give them all a lot of power.  But if you're not so sure about your
> fellow programmers, or about code that you are loading off the net,
> then Ruby's model offers much less protection than Python's.
>
> This kind of thing would make me much more scared to work on a big
> project in Ruby than I would be in Python.

I wouldn't want to program a large project in Ruby with programmers
that I didn't trust.  But having been on projects like this using
"safer" languages, I also wouldn't want to program a large project in
_any_ language with programmers that I didn't trust.  There are just
so many ways another programmer can make a project difficult, and any
language can only protect you from a handful of them.

(Hi Jos!  How are you doing?)

Wayne

---
Wayne Vucenic
No Bugs Software
Ruby, C#, and Erlang Agile Contract Programming in Silicon Valley
Posted by Jos Backus (Guest)
on 21.01.2007 21:39
(Received via mailing list)
Hi Wayne,

On Mon, Jan 22, 2007 at 03:57:59AM +0900, Wayne Vucenic wrote:
> Hi Terry,
> 
> On 1/20/07, Terry Weissman <terry@liveops.com> wrote:
> >The point is, in Python, if file A chooses to import package B, it is
> >very unlikely to affect code operating in completely unrelated file
> >C.  In Ruby, it's entirely possible that the import of B could have
> >broken something that C depended on.

A typical example (correct me if I'm wrong) would be for somebody to 
override
an existing method, introducing differing behavior whereas you'd be 
relying on
the behavior of the original method. E.g.:

    lizzy:/tmp% cat x
    class Foo
      def bar
  puts "hi"
      end
    end

    class Foo
      def bar
  puts "ho"
      end
    end

    Foo.new.bar
    lizzy:/tmp% ruby x
    ho
    lizzy:/tmp%

In Ruby's defense, there's:

    lizzy:/tmp% ruby -w x
    x:8: warning: method redefined; discarding old bar
    ho
    lizzy:/tmp%

To make this more useful, one of my proposals is to augment this 
information
such that the interpreter would be able to tell you where it found the 
old
definition:

    lizzy:/tmp% ruby -w x
    x:8: warning: method redefined; discarding old bar at x:2
    ho
    lizzy:/tmp%

This would be helpful when trying to figure out where methods are 
defined.

Similarly:

    lizzy:/tmp% cat x
    FOO = 1
    FOO = 2
    lizzy:/tmp% ruby x
    x:2: warning: already initialized constant FOO at x:1
    lizzy:/tmp%

> that can benefit from a lot of safety, and others that can benefit
> >This kind of thing would make me much more scared to work on a big
> >project in Ruby than I would be in Python.
> 
> I wouldn't want to program a large project in Ruby with programmers
> that I didn't trust.  But having been on projects like this using
> "safer" languages, I also wouldn't want to program a large project in
> _any_ language with programmers that I didn't trust.  There are just
> so many ways another programmer can make a project difficult, and any
> language can only protect you from a handful of them.
> 

True. But I still wonder if some compromise wouldn't be possible through 
the
use of scoping control.

> (Hi Jos!  How are you doing?)

Doing fine :-) *waves*