From: Shadowfirebird Date: 2008-07-24T17:14:59+09:00 Subject: Re: circular 'require' ::slaps heel of hand to forehead:: The END{ require } thing isn't needed. Setting $" solves the infinite loop -- you can just call require. On Thu, Jul 24, 2008 at 9:02 AM, Shadowfirebird wrote: > I've had my think. > > I think I have some conclusions which might be useful for other people > in the same boat, and since I've not seen them anywhere else, I hope > you'll allow me to bore you with them. No doubt cooler heads than > mine will see things differently, but there you go. And, maybe I'm > missing the obvious, anyway. > > 1) require is mildly broken (a). It doesn't set $" until after the > given file is loaded. This can lead to errors when you have a > circular call of requires. If it set $" first, circular references > would just be ignored. Of course, you should never actually *need* to > have circular 'require' referefences, but: see my next point. > > 2) Require is mildly broken (b). Secondly -- and I suppose that this > is less of a bug and more of a bugbear -- there is no way to > distinguish between require-because-it-wont-load-otherwise and > require-because-some-methods-wont-run-otherwise. This second sort > isn't entirely necessary, but it's aesthetically pleasing and useful > for unit testing. > > 3) You can get around (1) by manually pushing the program name into $" > at the start of each file. This done, you can use a new idiom -- END > { require } -- to specify > require-because-some-methods-wont-run-otherwise. Not the prettiest > thing, but neither the ugliest, I think: "in the end, this is > required." > > > Now I suppose I'd better show my working if I'm going to say anything > as contentious as all that. Apologies for the length of this, but > then, you can stop reading now. OTOH, I'd really like to hear other > points of view. > > A new set of example programs: > > # runner.rb > require 'root' > Root.create_data() > Root.customers.each{|c| puts c.oid, c.name} > > # root.rb > require 'customer' > class Root > @@list = [] > > def self.create_data() > Customer.new("Dolly") > Customer.new("Reymond") > end > > def self.customers() > return @@list.find_all{|x| x.kind_of?(Customer)} > end > > attr_accessor :oid > > def initialize() > @oid = @@list.size > @@list << self > end > end > > # customer.rb > require 'root' > class Customer < Root > attr_accessor :name > > def initialize(name) > super() > @name = name > end > end > > > Broadly speaking this follows the same approach as my actual code. As > listed, of course, it doesn't work: run runner.rb and it loads > root.rb, which starts by loading customer.rb, which starts by loading > root.rb, which... > > I needed to make a distinction (#2 above) between code that referred > to another object when it was loaded; and code that referred to > another object when it was run. root.rb doesn't need a 'require > 'customer'; runner.rb does. But this is rather unhelpful when it > comes to unit testing: when writing the test class for Root I > shouldn't have to remember to require customer. Ideally I should be > able to put the require in root.rb somehow. > > For experimental purposes lets try putting the "require 'customer'" at > the *end* of root.rb. This has practical implications -- no > programmer is going to look for it there -- but it works; running > runner.rb produces no errors. Oddly enough, putting "END {require > 'customer'}" at the start of the file does not work. But, wait. > > What is more interesting, is that if you run root.rb or customer.rb > you get warnings about redefined methods. You shouldn't see this if > require works as described -- a file should never be called twice. > What appears to be happening is that $" is set after the file is > loaded -- that is, it's not being set until the chain of requires has > finished. We can defeat that behaviour by adding $" << "" > to the start of root.rb and customer.rb. The warnings now disappear. > (And that seems like a bug to me, or at least an undocumented feature.) > > This is really odd: if we now try END{ require 'customer'} at the > start of root.rb -- well, after the $" thing -- it *works*. Really > odd, but nice. > > So, root.rb now looks like this: > > # root.rb > $" << "root.rb" > END { require 'customer' } > class Root > @@list = [] > > def self.create_data() > Customer.new("Dolly") > Customer.new("Reymond") > end > > def self.customers() > return @@list.find_all{|x| x.kind_of?(Customer)} > end > > attr_accessor :oid > > def initialize() > @oid = @@list.size > @@list << self > end > end > > And I can live with that. But I'm open to suggestions -- any > suggestions, except rude ones... > > -- Me, I imagine places that I have never seen / The colored lights in fountains, blue and green / And I imagine places that I will never go / Behind these clouds that hang here dark and low But it's there when I'm holding you / There when I'm sleeping too / There when there's nothing left of me / Hanging out behind the burned-out factories / Out of reach but leading me / Into the beautiful sea