Skip to content

Instantly share code, notes, and snippets.

@shugo
Created January 12, 2012 07:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save shugo/1599198 to your computer and use it in GitHub Desktop.
Save shugo/1599198 to your computer and use it in GitHub Desktop.
How to invoke Module#constants on Module itself
# Module.constants returns constants accessible at the place where the
# method is called.
class A
X = 1
p Module.constants.include?(:X) #=> true
end
# Module#constants returns constants defined in the receiver. However,
# you need a trick to invoke Module#constants on Module itself.
p Module.instance_method(:constants).bind(Module).call #=> []
# Another way to invoke Module#constants on Module itself suggested by
# Marc-Andre Lafortune in [ruby-core:42091]
# It's available only in CRuby 1.9, and I doubt that Matz has accepted the
# feature because it has been introduced by nobu secretly.
p Module.constants(Module)
@godfat
Copy link

godfat commented Jan 12, 2012

I guess you missed something:
https://gist.github.com/1599287

class A
  p Module.instance_method(:constants).bind(Module).call #=> []
end

Otherwise calling Module.constants outside A is still []

@shugo
Copy link
Author

shugo commented Jan 12, 2012

Module.instance_method(:constants).bind(Module).call invokes Module#constants, whose behavior doesn't depend on places where it is called, not Module.constants.
No constants are defined in the class Module itself, so Module.instance_method(:constants).bind(Module).call returns an empty array. If you define a constant in Module, an invocation of Module#constants on Module returns that constant name.

class Module
  FOO = 1
end
p Module.instance_method(:constants).bind(Module).call #=> [:FOO]

Do I still miss something?

@godfat
Copy link

godfat commented Jan 12, 2012

Yes, you're right, and calling Module.constants shouldn't be [],
I am not sure why I wrote that in the first place. I guess I was a bit
confused with the class A, which is not really used here.

I guess this would make things more clear:

X = 1

# Module.constants returns constants accessible at the place where the
# method is called.
p Module.constants.include?(:X) #=> true

# Module#constants returns constants defined in the receiver. However,
# you need a trick to invoke Module#constants on Module itself.
p Module.instance_method(:constants).bind(Module).call #=> []

And adding below could clarify that the X is defined in TOPLEVEL_BINDING
which is a context of Object instead of Module.

p Module.instance_method(:constants).bind(Object).call.include?(:X) #=> true

@shugo
Copy link
Author

shugo commented Jan 12, 2012

My intention was to show that the behavior of Module.constants depends on places where it is called. class A is used to provide a certain scope there.
The documentation of Module.constants says "Returns an array of the names of all constants defined in the system", but it is incorrect.

@godfat
Copy link

godfat commented Jan 12, 2012

After some experiments, I realized that it's the difference between Module#constants and Module.constants. They are different methods. The former would only return names in the exact context, so here it is []. But the latter would return all the names in the TOPLEVEL_BINDING.

>> Module.instance_method(:constants)
=> #<UnboundMethod: Module#constants>

>> Module.method(:constants).unbind
=> #<UnboundMethod: #<Class:Module>#constants>

I guess the document is saying about the latter, not the former.

@godfat
Copy link

godfat commented Jan 12, 2012

sorry, this might be wrong

---And the former is used in something like String.constants and the latter is used in something like Object.constants---

@shugo
Copy link
Author

shugo commented Jan 12, 2012

Yes, Module#constants and Module.constants are different methods. That's why you need the above trick to invoke Module#constants on Module itself. I'm sure the documentation is saying about the latter, and it's incorrect.

from eval.c:

/*
 *  call-seq:
 *     Module.constants   -> array
 *
 *  Returns an array of the names of all constants defined in the
 *  system. This list includes the names of all modules and classes.
 *
 *     p Module.constants.sort[1..5]
 *
 *  <em>produces:</em>
 *
 *     ["ARGV", "ArgumentError", "Array", "Bignum", "Binding"]
 */

@shugo
Copy link
Author

shugo commented Jan 12, 2012

You said "the latter would return all the names in the TOPLEVEL_BINDING", but it's wrong.
Module.constants returns all the constant names accessible at the place where Module.constant is called.

Please read my original gist. :X is included in the result of Module.constants even if X is not accessible at top level.

@godfat
Copy link

godfat commented Jan 12, 2012

Good point. So I guess the clearest demonstration would be:

class A
  X = 1
  p Module.constants.include?(:X) #=> true
end
p Module.constants.include?(:X) #=> false

And I think Module.instance_method(:constants).bind(Module).call is actually a totally different function, which is defined in variable.c:1775 (from ruby-1.9.3-p0)
https://github.com/ruby/ruby/blob/v1_9_3_0/variable.c#L1775-1809 rb_mod_constants (Module#constants)
https://github.com/ruby/ruby/blob/v1_9_3_0/eval.c#L286-328 rb_mod_s_constants (Module.constants)

I guess rb_mod_s_constants is overriding rb_mod_constants inside Module,
and which will also call rb_mod_constants when argv > 0.

This is so confusing :o
Thanks for the discussion.

/*
 *  call-seq:
 *     mod.constants(inherit=true)    -> array
 *
 *  Returns an array of the names of the constants accessible in
 *  <i>mod</i>. This includes the names of constants in any included
 *  modules (example at start of section), unless the <i>all</i>
 *  parameter is set to <code>false</code>.
 *
 *    IO.constants.include?(:SYNC)        #=> true
 *    IO.constants(false).include?(:SYNC) #=> false
 *
 *  Also see <code>Module::const_defined?</code>.
 */

VALUE
rb_mod_constants(int argc, VALUE *argv, VALUE mod)
{
    VALUE inherit;
    st_table *tbl;

    if (argc == 0) {
    inherit = Qtrue;
    }
    else {
    rb_scan_args(argc, argv, "01", &inherit);
    }
    if (RTEST(inherit)) {
    tbl = rb_mod_const_of(mod, 0);
    }
    else {
    tbl = rb_mod_const_at(mod, 0);
    }
    return rb_const_list(tbl);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment