What’s the best way to check if a feature/class has been loaded?

Hi!

What’s the best way to check if a feature/class has been loaded?

I’ve been using

defined? A::b::C

but that fails to give me the correct result if A is a class and B::C
is defined, but A::b::C isn’t defined, as Ruby will then first resolve
A::B to the top-level constant B and then resolve C within B.

Here’s a test case:

class A
end

module B
end

p defined? A::b::C # => nil

module B
module C
end
end

p defined? A::b::C # => “constant”

The only solution I can think of is to do

def feature?(path)
path.split(’::’).reduce(Object){ |o, e| begin o.const_get(e, false);
rescue NameError; return nil; end }
end

but that won’t work in 1.8.7, as there’s no second argument to
Module#const_get in 1.8.7. A solution that works across the 1.8/1.9
divide might be

def feature?(path)
const = path.split(’::’).reduce(Object){ |o, e| begin o.const_get(e);
rescue NameError; return nil; end }
const.name == path ? const : nil
end

and even though that works, I’d rather have something simpler,
something built-in.

An alternative would be to check $LOADED_FEATURES, but, again, the
internals of how require works have changed between 1.8 and 1.9 and,
considering things like file-name extensions and one mustn’t forget
about autoload and its semantics, would also be best left to built-in
functionality.

So, how does one check for the presence of A::b::C without
accidentally referencing top-level constants?

Active Support has recently added qualified_const_* methods to Module

https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/module/qualified_const.rb

Maybe that could help? If it does you can either copy the code or depend
on
Active Support (which nowadays allows client libraries to cherry-pick
exactly what they need rather than loading everything).

Ah, that won’t work in 1.8.

In 1.8 you can rely on the fact that const_defined? does not look into
the
ancestors. I guess a portable solution will need to have code that
depends
on the Ruby version.

On Wed, Mar 21, 2012 at 16:43, Xavier N. [email protected] wrote:

Ah, that won’t work in 1.8.

In 1.8 you can rely on the fact that const_defined? does not look into the
ancestors. I guess a portable solution will need to have code that depends
on the Ruby version.

Or simply use my #feature? implementation. But again, I would like to
use something that I don’t have to copy&paste or require into every
project where I need this function. Relying on ActiveSupport is
definitely not an option.

On Wed, Mar 21, 2012 at 8:17 PM, Nikolai W. [email protected] wrote:

use something that I don’t have to copy&paste or require into every
project where I need this function. Relying on ActiveSupport is
definitely not an option.

OK, the implicit answer is that there is no built-in way to do that. You
need to write a short method like the ones we are mentioning.

Take into account that #feature? assumes the argument matches the
constant
name, which may not be the case. For example

module A
  module B
  end
end

module C
  D = A::B
end

In that code A::B and C::smiley: are defined constants that store the same
module
object, but #feature? returns false on C::D.

It would be more correct to rely on const_defined? like Active Support
does.

On Wed, Mar 21, 2012 at 20:48, Xavier N. [email protected] wrote:

On Wed, Mar 21, 2012 at 8:17 PM, Nikolai W. [email protected] wrote:

end

In that code A::B and C::smiley: are defined constants that store the same module
object, but #feature? returns false on C::D.

True, but #feature? wasn’t written to take such things into account.

It would be more correct to rely on const_defined? like Active Support does.

There’s a timing issue with using const_defined? followed by const_get
instead of calling const_get directly and catching NameError. Also,
using const_defined? won’t invoke const_missing, which might set the
constant for you, if that’s what you want to have happen.

I guess

def feature?(path)
path.split(’::’).reduce(Object){ |o, e|
c = begin o.const_get(e); rescue NameError; return nil end
return nil if c == (o != Object and begin Object.const_get(e);
rescue NameError; nil end)
c
}
end

might work, as it tries to make sure that we don’t return to the top
level at any point in the path resolution, but is a lot more
complicated than I think that it should have to be.

On Wed, Mar 21, 2012 at 9:51 PM, Nikolai W. [email protected] wrote:

There’s a timing issue with using const_defined? followed by const_get

instead of calling const_get directly and catching NameError.

What do you mean?

The problem with const_get is that it follows the ancestors, of course,
and
you have no way around that in 1.8.

I guess

def feature?(path)
path.split(’::’).reduce(Object){ |o, e|
c = begin o.const_get(e); rescue NameError; return nil end
return nil if c == (o != Object and begin Object.const_get(e);
rescue NameError; nil end)
c
}
end

Nikolai, I don’t really understand what you need. You are askind for
defined? X::Y that goes step by step and your original code does raise
an
error for X::Y integer.

Then this second version cannot say that X::Y is defined in

module X
  Y = nil

end

And you refuse to reuse much more simple and proven existing solutions.

You want something with so special semantics that definitely is NOT
built-in. That answers you original question.

Good luck!

On Thu, Mar 22, 2012 at 6:15 AM, Nikolai W. [email protected] wrote:

I mean that if const_defined? returns true, then between that code

being executed and the call to const_get, some other piece of code may
remove the constant, resulting in an uncaught NameError.

That’s true.

The problem with const_get is that it follows the ancestors, of
course,
and

you have no way around that in 1.8.

Yes, we’ve already come to that conclusion. You don’t need to keep
repeating it.

Because the proposed solutions are based on const_get, and they fail for
some inputs (in my interpretation of failure, which may not be yours).

I see that you completely cut out the part about const_defined? not

calling const_missing, which is a rather big part of the problem with
using const_defined? in the first place.

We are supposedly emulating defined? somehow but for constant paths and
without going up the ancestor chain in each step doesn’t it? defined?
does
not call const_missing.

Nikolai, I don’t really understand what you need.

Xavier, please don’t begin sentences with the person you’re responding
to’s name like that. It’s like your speaking to me as if I was a
child and it feels very condescending.

Sorry, it was not my intention to sound like that.

And you refuse to reuse much more simple and proven existing solutions.

You’re making it sound like I’m being stubborn. I just don’t think
your proposed solution is better.

The problem is that you don’t really want qualified_const_defined?, you
need something else but I don’t see clearly what it is.

I want a way to tell if a class or module has been defined/loaded.

The check would then be to see if X was loaded in your example above,
not X::Y.

I don’t follow, in order to see if X::Y is a defined constant I need
first
to check if X is a defined constant? Constants have single names “X”,
and
“Y”. X::Y is a constant path. “X::Y” as such does not exist.

An alternative is to check $LOADED_FEATURES. This isn’t

straightforward either, as it doesn’t contain the exact argument given
to require. There are internal functions like rb_provided that could
have been exposed to make it easy to check if a feature had been
loaded/is available.

File names and class objects, and module objects, and constants… you
cannot derive one from the other. They are decoupled in Ruby except for
the
fact that the class/module keywords assign, and that if you assign an
anonymus class/module to a constant, then its name is set after the
constant.

But in Ruby file foo.rb can define the constant Bar, which may hold a
module whose name is “Wadus”. They are quite orthogonal features.

On Thu, Mar 22, 2012 at 06:56, Xavier N. [email protected] wrote:

On Thu, Mar 22, 2012 at 6:15 AM, Nikolai W. [email protected] wrote:

I see that you completely cut out the part about const_defined? not
calling const_missing, which is a rather big part of the problem with
using const_defined? in the first place.

We are supposedly emulating defined? somehow but for constant paths and
without going up the ancestor chain in each step doesn’t it? defined? does
not call const_missing.

True, I was seeing the wrong test output for that one.

An alternative is to check $LOADED_FEATURES. This isn’t
straightforward either, as it doesn’t contain the exact argument given
to require. There are internal functions like rb_provided that could
have been exposed to make it easy to check if a feature had been
loaded/is available.

File names and class objects, and module objects, and constants… you
cannot derive one from the other. They are decoupled in Ruby except for the
fact that the class/module keywords assign, and that if you assign an
anonymus class/module to a constant, then its name is set after the
constant.

But in Ruby file foo.rb can define the constant Bar, which may hold a module
whose name is “Wadus”. They are quite orthogonal features.

Yes, I realize that, but let’s forget the constant bit (as I tried to
do in the part that you cut out from the rest of this discussion on
$LOADED_FEATURES, require, and provided?) and focus on the original
problem of determining if a feature is available or not. In my first
e-mail I explained that I’d been using defined? to perform such tests,
but that it doesn’t work as intended. I proposed an alternative to
defined? that tried to walk a constant path without ever returning to
the top level, but I wasn’t happy with the solution and, as we’ve
seen, there are semantic issues with such a solution (should
const_missing be called or not?). I was, however, originally looking
for a better alternative to the constant lookup altogether. That’s
why I mentioned “feature” in my first e-mail.

As I said, Ruby uses the (expanded) path of the argument to require as
the “feature”. If Ruby provided a convenient way to check if a path
was in $LOADED_FEATURES that’d solve my use case. This can of course
be emulated, and I’m surely making too big a deal about this, but I’d
rather have Kernel.provided? that wraps the extant rb_provided than
having to define

def provided?(path)
$LOADED_FEATURES.any?{ |e| e.end_with? path + File.extname(e) }
end

for each project that needs this functionality. Finally, such a
definition can never truly emulate rb_provided (or what the return
value would be from require), as Ruby doesn’t expose the “loading”
table. (This solution won’t take autoloads into account either, if
one wants that to be done, but they’re going away in 3.0, so let’s
ignore them :wink:

On Wed, Mar 21, 2012 at 22:11, Xavier N. removed_email_address[email protected] wrote:

On Wed, Mar 21, 2012 at 9:51 PM, Nikolai W. [email protected] wrote:

There’s a timing issue with using const_defined? followed by const_get
instead of calling const_get directly and catching NameError.

What do you mean?

I mean that if const_defined? returns true, then between that code
being executed and the call to const_get, some other piece of code may
remove the constant, resulting in an uncaught NameError.

The problem with const_get is that it follows the ancestors, of course, and
you have no way around that in 1.8.

Yes, we’ve already come to that conclusion. You don’t need to keep
repeating it.

I see that you completely cut out the part about const_defined? not
calling const_missing, which is a rather big part of the problem with
using const_defined? in the first place.

I guess

def feature?(path)
path.split(’::’).reduce(Object){ |o, e|
c = begin o.const_get(e); rescue NameError; return nil end
return nil if c == (o != Object and begin Object.const_get(e);
rescue NameError; nil end)
c
}
end

Nikolai, I don’t really understand what you need.

Xavier, please don’t begin sentences with the person you’re responding
to’s name like that. It’s like your speaking to me as if I was a
child and it feels very condescending.

You are askind for
defined? X::Y that goes step by step and your original code does raise an
error for X::Y integer.

I don’t follow.

Then this second version cannot say that X::Y is defined in

module X
  Y = nil

end

True.

And you refuse to reuse much more simple and proven existing solutions.

You’re making it sound like I’m being stubborn. I just don’t think
your proposed solution is better.

You want something with so special semantics that definitely is NOT
built-in. That answers you original question.

Thank you for building me a straw man. I’ve always wanted one.

I want a way to tell if a class or module has been defined/loaded.
The check would then be to see if X was loaded in your example above,
not X::Y.

An alternative is to check $LOADED_FEATURES. This isn’t
straightforward either, as it doesn’t contain the exact argument given
to require. There are internal functions like rb_provided that could
have been exposed to make it easy to check if a feature had been
loaded/is available.

This part of Ruby is surely inspired by Emacs’ way of doing this, see

http://www.gnu.org/software/emacs/manual/html_node/elisp/Named-Features.html

but doesn’t provide a way to check if a feature has been loaded.
(There’s a difference here, of course, as Ruby implicitly uses the
(expanded) path, with possible suffixes/extensions, of the argument to
require as the feature, not a symbol as Emacs forces you to explicitly
use as an argument to provide.)

To sum up, ruby should provide a method “provided?” that checks if its
argument would return true or false when given as an argument to
require. Provided? would return this results negation.

Checking the presence of constants is a workaround that I’ve been
using in the past, but was recently burned by, as, even in the case of
using defined?, this may travel unwanted paths in the resolution of
such constants.

Your emails mix classes, constants, and paths.

If paths is what you want to check then we need to leave classes and
constants completely out of the thread. Do you agree with that?

Do you also agree in that checking if a path belongs to $" tells you
nothing about the constant it defines, if any?

Sent from my iPhone

On Thu, Mar 22, 2012 at 09:07, Xavier N. [email protected] wrote:

Your emails mix classes, constants, and paths.

Yes, so?

If paths is what you want to check then we need to leave classes and constants
completely out of the thread. Do you agree with that?

No. This whole thread has been about finding a reasonable (best,
even) way to check if a feature/class has been loaded.

Do you also agree in that checking if a path belongs to $" tells you nothing
about the constant it defines, if any?

Only if you agree that I never made any such claim.

On Thu, Mar 22, 2012 at 09:35, Xavier N. [email protected] wrote:

Albeit your last email is leaning towards paths, are you still trying to solve
if in an existing C::String the String constant is stored in C or in Object?

I ask that because paths are not going to solve that problem in any way.

See my response to your other email.

Yes, you didn’t claim that.

We are now down to the definition of “feature”. Is feature a path for
you? the ones stored in $"?

If so, you say you want to know is a “feature/class” is loaded. Since we
agree both concepts are unrelated, is that an “or”? two different
problems?

And “class” is a class object, not a constant? or you want to check
feature/constant?

Sent from my iPhone

OK, I think I know what you are looking for:

Given a name “C::String” you want to know if there exists a constant C,
and inside that one you want to check for a constant String. In the
latter check, only the constants in C have to be checked, nor ancestors
neither Object.

In addition to that you want const_missing to be triggered as needed
(and that does not hold for qualified_const_defined? in particular).

Is that right?

Is there anything else to add related to required paths?

Sent from my iPhone

And another question.

Albeit your last email is leaning towards paths, are you still trying to
solve if in an existing C::String the String constant is stored in C or
in Object?

I ask that because paths are not going to solve that problem in any way.

Sent from my iPhone

You guys seemed to talk about different things, one talking about cats,
the other one about dogs… :stuck_out_tongue:

I think the question could have been simplified though, it was a bit
generic:

“What’s the best way to check if a feature/class has been loaded?”

When my brain notices that, it gets confused… it knows what a class
is, but what is a feature? EVERYTHING, even bugs, can be a feature. :smiley:

I never used “defined?” in my code so far, I feel it is very inelegant.

On Thu, Mar 22, 2012 at 10:09, Xavier N. [email protected] wrote:

OK, I think I know what you are looking for:

Given a name “C::String” you want to know if there exists a constant C, and
inside that one you want to check for a constant String. In the latter check, only
the constants in C have to be checked, nor ancestors neither Object.

In addition to that you want const_missing to be triggered as needed (and that
does not hold for qualified_const_defined? in particular).

Is that right?

I suppose so. I have, however, reworked my code and am instead using
the $LOADED_FEATURES check. It works for what I need and since
there’s really no way to solve it with constants in a uniform and
simple way, that’ll do.

Is there anything else to add related to required paths?

The point was that it needn’t necessarily be a check for constants
being defined.

I don’t really think we can get any further in this thread. Thank you
for your input and I hope that this has been an educational thread on
dealing with constants in Ruby to more subscribers than me.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs