Kernel#autoload ignores custom monkey patched Kernel#require

Discussion in 'Ruby' started by Lars Gierth, Mar 8, 2010.

  1. Lars Gierth

    Lars Gierth Guest

    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 via http://www.ruby-forum.com/.
    Lars Gierth, Mar 8, 2010
    #1
    1. Advertising

  2. Lars Gierth

    Luis Lavena Guest

    Luis Lavena, Mar 8, 2010
    #2
    1. Advertising

  3. Lars Gierth

    Lars Gierth Guest

    Lars Gierth, Mar 8, 2010
    #3
  4. Lars Gierth

    Lars Gierth Guest

    Lars Gierth, Mar 19, 2010
    #4
  5. 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.
    David Masover, Mar 19, 2010
    #5
  6. Lars Gierth

    Lars Gierth Guest

    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 via http://www.ruby-forum.com/.
    Lars Gierth, Mar 20, 2010
    #6
  7. 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.)
    David Masover, Mar 20, 2010
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. A. B., Khalid
    Replies:
    5
    Views:
    303
    A. B., Khalid
    Nov 22, 2004
  2. yogesh
    Replies:
    3
    Views:
    574
    Kenny McCormack
    Feb 12, 2006
  3. George Sakkis
    Replies:
    3
    Views:
    339
  4. J Krugman

    *{$AUTOLOAD} vs *$AUTOLOAD

    J Krugman, Jul 13, 2005, in forum: Perl Misc
    Replies:
    1
    Views:
    83
    Tassilo v. Parseval
    Jul 14, 2005
  5. Marc Girod

    require from autoload module

    Marc Girod, Jun 13, 2009, in forum: Perl Misc
    Replies:
    9
    Views:
    112
    Marc Girod
    Jun 15, 2009
Loading...

Share This Page