Forum: Ruby Kernel#autoload ignores custom monkey patched Kernel#require

Posted by Lars Gierth (lgierth)
on 2010-03-08 11:25
Hi,

I'm playing with FakeFS and want to fake Kernel#require. That's working
quite well now, but somehow Kernel#autoload doesn't recognize my custom
#require. The documentation says that #autoload would try to load the
file via Kernel::require, which is contradictory, as there's only
Kernel#require.

The following snippet doesn't print "foo/bar", as I would expect.

module Kernel
  def require fn
    puts fn
  end
end

module Foo
  autoload :Bar, "foo/bar"
end

Foo::Bar

Does anyone have an idea on this?

Best regards,
Lars
Posted by Luis Lavena (luislavena)
on 2010-03-08 13:45
(Received via mailing list)
On Mar 8, 11:25 am, Lars Gierth <lars.gie...@altefeuerwachekoeln.de>
wrote:
> Hi,
>
> I'm playing with FakeFS and want to fake Kernel#require. That's working
> quite well now, but somehow Kernel#autoload doesn't recognize my custom
> #require. The documentation says that #autoload would try to load the
> file via Kernel::require, which is contradictory, as there's only
> Kernel#require.
>

[ruby-core:20190]
http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-core/20190?20046-21072+split-mode-vertical
Posted by Lars Gierth (lgierth)
on 2010-03-08 14:58
Hi,

thanks a lot for pointing me on this discussion, quite interesting ;)

Best regards,
Lars

Luis Lavena wrote:
> [ruby-core:20190]
> http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-core/20190?20046-21072+split-mode-vertical
Posted by Lars Gierth (lgierth)
on 2010-03-19 17:23
Lars Gierth wrote:
> I'm playing with FakeFS and want to fake Kernel#require.

Hi again, I git this working by overwriting #const_missing?, #autoload? 
and #autoload.

Source is on GitHub: 
http://github.com/lgierth/fakefs/blob/topic/fake-require/lib/fakefs/require.rb
Posted by David Masover (Guest)
on 2010-03-19 20:33
(Received via mailing list)
On Friday 19 March 2010 11:23:37 am Lars Gierth wrote:
> Lars Gierth wrote:
> > I'm playing with FakeFS and want to fake Kernel#require.
> 
> Hi again, I git this working by overwriting #const_missing?, #autoload?
> and #autoload.

You probably didn't.

One example I know where Kernel#autoload works and const_missing doesn't 
is
when defining a const. For example:

irb(main):001:0> autoload :CSV, 'csv'
=> nil
irb(main):002:0> module CSV
irb(main):003:1> end
TypeError: CSV is not a module
        from (irb):2
        from /home/ruby19/bin/irb:12:in `<main>'

The obvious danger here is when you try monkeypatching something first, 
and
then using it:

autoload :CSV, 'csv'
class CSV
  def foo
    # ...
  end
end

With a real Kernel#autoload, that class definition will be referring to 
CSV,
and actually autoload the file first, so you'll be extending CSV. With 
this
one, it won't, because neither of the examples above will ever hit
const_missing, or anything else I know of that the application can 
override.

This is why we need to actually need to have Kernel#autoload behave 
properly
with an overridden Kernel#require, or at least have enough primitives 
exposed
that we can redefine Kernel#autoload. At the moment, the _only_ option 
is to
override $:, which only makes sense if you're _always_ going to autoload 
a
file, as opposed to a URL, an expression, or anything else.

This has apparently been known about for years. It's one of two fairly 
brain-
dead decisions I've seen the Ruby language make, and unfortunately, is 
the
kind of problem that can really only be fixed by hacking on the 
interpreter
itself.
Posted by Lars Gierth (lgierth)
on 2010-03-20 17:31
Hi David,

thanks a lot for pointing this out. It's indeed one more limitation of 
FakeFS::Require. The other one, of which I have not thought that much 
yet, is that you can't be 100% sure that #const_missing gets hit. What 
if a class/module that uses #autoload defines its own #const_missing? 
I'll think about this when I have some time for it, but for now - and 
for my usecase - it's fine: testing a library that loads ruby source 
files by configuration, assisted by libraries that autoload files (Rack) 
or load files from stdlib during runtime (Usher).

Btw, what's this second braindead decision? I would be very interested 
in it :)

Best regards,
Lars
Posted by David Masover (Guest)
on 2010-03-20 23:35
(Received via mailing list)
On Saturday 20 March 2010 11:31:19 am Lars Gierth wrote:
> The other one, of which I have not thought that much
> yet, is that you can't be 100% sure that #const_missing gets hit.

That was mostly my point, and the examples I used are cases where
const_missing isn't hit.

> What
> if a class/module that uses #autoload defines its own #const_missing?

If they don't do a proper alias_method_chain on it, that's their own 
problem.

Put another way: You're allowed to redefine Fixnum#+, or NilClass#nil?, 
or any
number of other things. If you abuse this ability, you get to keep both
pieces. Even the authors of irb don't plan for every contingency:

irb(main):001:0> class NilClass
irb(main):002:1> def nil?
irb(main):003:2> false
irb(main):004:2> end
irb(main):005:1> end
=>
irb(main):006:0> true
/usr/lib/ruby/1.9.1/irb/slex.rb:234:in `match_io': undefined method 
`call' for
nil:NilClass (NoMethodError)
        from /usr/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 
levels) in
each_top_level_statement'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in
each_top_level_statement'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch'
        from /usr/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in
`each_top_level_statement'
        from /usr/lib/ruby/1.9.1/irb.rb:145:in `eval_input'
        from /usr/lib/ruby/1.9.1/irb.rb:69:in `block in start'
        from /usr/lib/ruby/1.9.1/irb.rb:68:in `catch'
        from /usr/lib/ruby/1.9.1/irb.rb:68:in `start'
        from /home/ruby19/bin/irb:12:in `<main>'

> Btw, what's this second braindead decision? I would be very interested
> in it :)

The behavior of UnboundMethod -- you can only bind an UnboundMethod to 
an
object of an appropriate class. So, for example:

class Foo
  def bar
    :whatever
  end
end
umeth = Foo.instance_method(:bar)

What do you do with an UnboundMethod? Well, this is roughly how things 
like
BlankSlate can hide/unhide methods -- it removes all methods from an 
object
and stuffs them into a hash, but you can have it re-apply those methods 
again,
kind of like this:

bmeth = umeth.bind(Foo.new)

Once it's bound, you can call it:

bmeth.call

Or shortcut the process:

umeth.bind(Foo.new).call

Here's the problem: In my mind, one of the cooler things about being 
able to
unbind methods like this is to allow the kind of free-for-all that you 
have in
JavaScript, where you can pull methods out of one object, leave them 
around in
the closure to reapply to the same object, or apply them directly to a
different object... Yes, you could do prototypal inheritance, but you 
could
also do ad-hoc code reuse.

As an example, in Ruby, Object#extend and Module#include either are or 
depend
on very low-level constructs that aren't really accessible to you. In
JavaScript, there is no Object.extend, but you can easily write it 
yourself,
using the fact that methods are just functions that you apply to (or 
attach
to) a given object, and functions are themselves first-class objects.

In Ruby, this isn't quite the case. Try something like this instead:

umeth.bind(Object.new).call

You get a TypeError. That's very Java-like behavior. That's 
anal-retentively-
strict type-checking sneaking into an otherwise beautiful, dynamic, 
duck-typed
language. It's the polar opposite of duck-typing.

There may be performance reasons to do it this way, I'm not sure. (A
counterargument: How is Ruby doing versus Google's v8 interpreter for
JavaScript?) When I've brought it up before, people essentially argued 
that
it's never sane to do that, so I shouldn't be able to. Erm, I can 
redefine
Fixnum#+, you've got open classes, define_method, and eval, and you 
won't let
me re-bind an existing method? Sorry, not buying it -- I thought the 
whole
point is that it's up to the programmer to decide what's sane, and if 
you do
something insane, you get to keep both pieces.

I thought it was only languages like Java that try to keep you from 
shooting
yourself in the foot by limiting your possibilities.

But I digress -- I did find an ugly workaround for the project I needed 
that
on, and that project has stagnated for awhile anyway. Still, it's one of 
very
few things that still bother me about Ruby.

(Another is that I really like Python's significant indentation, and 
much
prefer it to Ruby's end-end-end-end. However, that's a dead horse, and 
there
seem to be entirely too many Ruby people who don't want to see that even
become an option, and there's too many other things I like about Ruby 
for that
one feature to send me back to Python.)
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.