Project

General

Profile

Actions

Feature #19742

open

Introduce `Module#anonymous?`

Added by ioquatix (Samuel Williams) over 1 year ago. Updated 8 months ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:113966]

Description

As a follow-on <from https://bugs.ruby-lang.org/issues/19521>, I'd like propose we introduce Module#anonymous?.

In some situations, like logging/formatting, serialisation/deserialization, debugging or meta-programming, we might like to know if a class is a proper constant or not.

However, this brings about some other issues which might need to be discussed.

After assigning a constant, then removing it, the internal state of Ruby still believes that the class name is permanent, even thought it's no longer true.

e.g.

m = Module.new
m.anonymous? # true

M = m
m.anonyomous # false

Object.send(:remove_const, :M)
M # uninitialized constant M (NameError)

m.anonymous? # false

Because RCLASS data structure is not updated after the constant is removed, internally the state still has a "permanent class name".

I want to use this proposal to discuss this issue and whether there is anything we should do about such behaviour (or even if it's desirable).

Proposed PR: https://github.com/ruby/ruby/pull/7966

cc @fxn (Xavier Noria)

Actions #1

Updated by ioquatix (Samuel Williams) over 1 year ago

  • Description updated (diff)
Actions #2

Updated by nobu (Nobuyoshi Nakada) over 1 year ago

  • Tracker changed from Bug to Feature
  • Backport deleted (3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN)

Updated by fxn (Xavier Noria) over 1 year ago

❤️ for anonymous?.

Regarding the broader topic, the fundamental thing to discuss is whether permanent class names and constants should have any more coupling after initial constant assignment (when the name becomes permanent). Because, today, as you know, they don't.

That is, once a first constant assignment is done, class and module objects are like any other Ruby object, where name is just an attribute. You can do with that object whatever you want, store them wherever you want, that storage can be reassigned, etc. They are no different than Object.new.

By the same argument, class and module objects do not belong to a namespace. Constants do, different.

For example, in ancestor chains you store module objects. They are objects, whether they are reachable via a constant at any point in time is irrelevant. If you remove the original constant, the object is still there. Because storage and objects are (almost) orthogonal concepts in Ruby.

Another example:

klass = User
user = User.new
User = 1

user.class.equal?(klass) # => true
user.class.name # => 'User'

So... coupling the name to storage would be an important change. However, if coupled to the original constant removal, it would be symmetrical, it could make sense (assuming any change in the original constant path triggers the logic).

Updated by ioquatix (Samuel Williams) over 1 year ago

There is another formulation of the opposite of "anonymous" which could be the following:

class Module
  def permanent?
    Object.const_get(self.name).equal?(self)
  end
end

This would be true if the object is reachable by the name it says it is.

It's a little slower, but perhaps a better approach. We might be able to optimise it internally if the object/module know's it's parent namespace and name within that namespace (i.e. don't need to use const_get). So it might be true that anonymous? and permanent? are related but different.

My point is, maybe we should try to define these concepts more concretely, and maybe it's okay that anonymous? is true until the first assignment to a constant, and permanent? is only true if the object is reachable by it's name.

Updated by janosch-x (Janosch Müller) over 1 year ago

#permanent? is not very descriptive IMO. If I hadn't seen the code above, or the C code with that vocabulary, I would have a hard time guessing what its about. I'd probably guess it has to do with GC or so.

As seen in Xavier Noria's example, the word anonymous also does not cover all the ways in which a Module#name can become useless or wrong.

Then again, it might be really rare that people need to differentiate between the const-override case and say, classical anonymous modules or removed consts. So maybe it is not worth it to leak these subtleties to end users and the slightly imprecise anonymous is fine. A short and precise name that covers all cases is hard to find. Something like #assigned_to_original_const? doesn't exactly roll off the tongue...

Updated by ioquatix (Samuel Williams) over 1 year ago

Permanent is already a word used in the Ruby documentation and implementation. I agree it might not be clear, but it's already established when talking about "class paths" and names. if we introduce a new word/taxonomy, we should have a strong reason to do so.

Out of the two, I'd say permanent? as proposed has a stronger meaning than anonymous? as in some cases both can be false but in that case, permanent? is correct while anonymous? is probably at best misleading and at worst wrong.

Updated by fxn (Xavier Noria) over 1 year ago

Let me clarify the vocabulary as I see it:

An anonymous module has nil as name: anonymous? makes sense.

Module.new.name # => nil

A module may have a temporary name if it was never assigned to a constant that belongs to a module with a permanent name.

m = Module.new
class m::C; end
m::C.name # => "#<Module:0x000000014f921328>::C"

A module may have a permanent name if it was at some point assigned to a constant in a module with a permanent name.

# The name is permanent because the M constant belongs to the class object
# stored in the Object constant, whose name is "Object" permanent.
module M; end

Please excuse the circularity, but we all know what that means. It is three concepts.

Note that permanent has nothing to do with the name being a working constant path. Permanent means what it means: there is no way the name is going to change (using public API). In that sense, I believe the proposal is not aligned with the current way things work.

Note also that "temporary" and "permanent" are qualities of the name, not the module.

My personal point of view is that any attempt to introduce "parent" concepts, or steering into coupling names and constant paths is going to leak. Because in Ruby objects and storage are decoupled. People used to have types in their language may have expectations or misconceptions, but those should be revised, Ruby does not have syntax for types.

Updated by ioquatix (Samuel Williams) over 1 year ago

The concepts are discussed in the code, but they shouldn't be assumed as canonical, IMHO.

/**
 * Returns +classpath+ of _klass_, if it is named, or +nil+ for
 * anonymous +class+/+module+. A named +classpath+ may contain
 * an anonymous component, but the last component is guaranteed
 * to not be anonymous. <code>*permanent</code> is set to 1
 * if +classpath+ has no anonymous components. There is no builtin
 * Ruby level APIs that can change a permanent +classpath+.
 */
static VALUE
classname(VALUE klass, bool *permanent)

A class/module that is assigned a name via set_temporary_name may still be anonymous, so I believe the above comment needs to be updated.

In the code, when something is not permanent, a temporary class path may be generated:

static VALUE
make_temporary_path(VALUE obj, VALUE klass)
{
    VALUE path;
    switch (klass) {
      case Qnil:
        path = rb_sprintf("#<Class:%p>", (void*)obj);
        break;
      case Qfalse:
        path = rb_sprintf("#<Module:%p>", (void*)obj);
        break;
      default:
        path = rb_sprintf("#<%"PRIsVALUE":%p>", klass, (void*)obj);
        break;
    }
    OBJ_FREEZE(path);
    return path;
}

To clarify, the documentation states that when mod.name == nil, the mod is anonymous. But it does not state that when mod.name =! nil, the module is permanent AFAICT.

In any case, we should probably clarify this in the documentation.

Updated by fxn (Xavier Noria) over 1 year ago

I thought "permanent" was mostly internal jargon, where is in the documentation mentioned?

In the public API you have that a nil name means the module is anonymous. If the name is not nil, the module is not anonymous (and that, by definition, includes temporary names). I believe that is all user-facing?

Updated by ioquatix (Samuel Williams) over 1 year ago

Just because name == nil implies it's anonymous, does not mean that anonymous implies that name == nil. In fact, we don't have any anonymous? implementation yet to define this behaviour. I don't know if the documentation clarifies this or not, but I don't recall seeing it clearly outlined anywhere. If anything, I think this issue is a good place to figure out what we want, and then add clear and canonical documentation (as well as specs).

Until my previous PR set_temporary_name, which includes a discussion of permanent, I don't think there was any user facing documentation, but the implementation is clear on the behaviour. I'd be okay with changing this documentation if we want to clarify some aspects of it (we still have until Ruby 3.3 release which is months away).

Updated by ioquatix (Samuel Williams) over 1 year ago

I'll add my observation that Marshal states it cannot handle anonymous modules, but I say, it cannot handle ANY non-permanent (temporary) modules (assuming permanent? as defined above). The difference is subtle but demonstrated by this example:

# (1) Expected:
point = Struct.new(:x, :y)
Marshal.dump(point.new(1, 2)) # can't dump anonymous class #<Class:0x00000001084f14c0> (TypeError)

# (2) Point2 is not anonymous (it did have a name), but it's also not permanent (the name is no longer valid).
Point = Struct.new(:x, :y)
point = Point
Point2 = Point
Object.send(:remove_const, :Point)
Marshal.dump(Point2.new(1, 2)) # undefined class/module Point (ArgumentError)

# (3) Try to trick Marshal:
Point = Struct.new(:x, :y)
Marshal.dump(point.new(1, 2)) # Point can't be referred to (TypeError)

In all the above examples, the marshalled class may or may not be anonymous, but it's always not permanent, according to my (slightly fixed) definition:

class Module
  def permanent?
    Object.const_get(self.name).equal?(self) rescue nil
  end
end

If being "not anonymous" was the only criteria, re-assigning the constants would be okay (because it's not anonymous after the first name assignment).

Updated by fxn (Xavier Noria) over 1 year ago

The documentation for Module#name says:

Returns the name of the module mod. Returns nil for anonymous modules.

So, if the module is anonymous, then the name is nil. Logically, that does not rule out as a possibility that a non-anonymous module may also have a nil name, but I believe you take that for granted. If you are not anonymous, you have a name (ie, a value that is not nil), that is the common meaning of something not being anonymous.

Updated by fxn (Xavier Noria) over 1 year ago

Let me add for context that Active Support has had Module#anonymous? for many years, I wrote it myself (commit) when anonymous modules had an empty string as name.

Updated by ioquatix (Samuel Williams) over 1 year ago

Anyone can do the following:

my_module = Module.new do
  def self.name
    "Hello World"
  end
end

Is that module anonymous or not?

Updated by fxn (Xavier Noria) over 1 year ago

Agree, but that method is not Module#name.

Updated by ioquatix (Samuel Williams) over 1 year ago

I don't understand, is this correct or not?

class Module
  # Implementation from Rails:
  def anonymous?
    name == '' || name.nil?
  end
end

Module.new.anonymous?
# => true

my_module = Module.new do
  def self.name
    "Hello World"
  end
end

my_module.anonymous?
# => false

Updated by fxn (Xavier Noria) over 1 year ago

Oh, I thought you meant that the module has a name and had not been assigned to a constant.

If the module has a name, then not being considered to be anonymous is the least surprising definition to me.

You could also have

class C
  def self.name
    nil
  end
end

However, I think overriding such a core method for an attribute that is out of reach, managed internally by Ruby, is questionable and predicates may be well-defined for the common case, and obviate situations like this. With this definition, can you say an anonymous module was never assigned to a constant? No, no more. However, you can add "without an overwritten name" to make the statement hold.

We could say that an empty array has 0 size. Can people override Array#size to return 7? Yes, they can. But API docs do not need to cover such possibilities. If you override, you're on your own with the consequences.

Updated by ioquatix (Samuel Williams) over 1 year ago

Should setting a temporary name make a module not anonymous?

I found the following use cases in Rails:

                unless controller_class.anonymous?
                  controller_class.name.delete_suffix("Controller").underscore
                end
    def self.kind
      @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous?
    end
    def self.controller_name
      @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
    end
      def controller_path
        @controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
      end
      def mailer_name
        @mailer_name ||= anonymous? ? "anonymous" : name.underscore
      end

So, I think the usage of anonymous is already ossified by Rails, even if I disagree.

To me, anonymous means it cannot be named, but there is a difference between "proper noun" and "common noun".

If you refer to a "person", the person does not have a proper name, they are anonymous. If you refer to "Samuel", you are referring to a specific person, which is not anonymous. The fact something has a name, does not impact whether it's anonymous or not, it's whether that name can uniquely identify the thing in question. That being said, I agree the proposed definition, based on Ruby's internal class name permanence is probably not ideal either.

Updated by fxn (Xavier Noria) over 1 year ago

Yeah, we do not see it the same way.

Anonymous to me means you don't have a name. If the module has a name, done, it is not anonymous.

If you have a temporary name, in particular you have a name.

To be honest, I would leave modules anonymous until they are assigned to a constant in a non-anonymous module. That is, I'd only have two states, nil and "permanent":

m = Module.new
m.name # => nil

m::C = Class.new
m::C.name # => nil, in my ideal design, not real

A::B = m::C
m::C.name # => "A::B", this does work this way

but this cannot be changed even if we agreed, only sharing for the discussion :).

Updated by ioquatix (Samuel Williams) over 1 year ago

I'm not that different, except

Anonymous to me means you don't have a name [that resolves to self]. If the module has a name that resolves to itself, it is not anonymous.

I would say a lot of the rails code should be rewritten just to check name itself, e.g.

@mailer_name ||= anonymous? ? "anonymous" : name.underscore
# becomes
@mailer_name ||= name&.underscore || "anonymous"

@controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
# becomes
@controller_name ||= (name ? name.demodulize.delete_suffix("Controller").underscore : nil)

I think this is much more direct and I don't think Rails should monkey patch Module in this way either. It probably made more sense when name could be an empty string.. but is that still the case?

Updated by janosch-x (Janosch Müller) over 1 year ago

fxn (Xavier Noria) wrote in #note-17:

def self.name [...] I think overriding such a core method for an attribute that is out of reach, managed internally by Ruby, is questionable [...] Can people override Array#size to return 7? Yes, they can.

The problem with name is that it is a common, uh, name. It is also not as obvious that it is a part of the core as with Array#size.

Coincidental overrides like these are probably somewhat common.

Updated by fxn (Xavier Noria) over 1 year ago

@janosch-x (Janosch Müller) agreed, in Zeitwerk I do my best at reaching the original Module#name because of that.

@ioquatix (Samuel Williams) yeah, I understand your point of view. However, we disagree due to two reasons:

The first one is that Ruby docs (today) agree with my definition: If a module is anonymous, then its name is nil. If mod.anonymous? returned true and mod.name was not nil, we'd violate existing Ruby semantics.

The second one is more conceptual: once a name is set, its relationship with storage in the Ruby model is non-existent. You cannot assume or expect anything, because Ruby semantics decouple names and storage. A name, to me, is a string that was once set according to some rules. From then on, it is a mere string. So, I believe trying to mix "anonymous" in between name and storage does not go in the right direction, reachable_from_its_name? would be more aligned (not a proposal, just to illustrate my point).

Updated by ioquatix (Samuel Williams) over 1 year ago

The first one is that Ruby docs (today) agree with my definition: If a module is anonymous, then its name is nil. If mod.anonymous? returned true and mod.name was not nil, we'd violate existing Ruby semantics.

There is no semantics for anonymous? because it does not exist in Ruby yet. The problem is, in your logic, it's "if" not "iff".

I think conceptually, a name is set needs to be clarified - do you mean a permanent name or a non-permanent name? I wonder if we should change name to return nil if set_temporary_name is used to avoid this confusion.

Updated by fxn (Xavier Noria) over 1 year ago

There is no semantics for anonymous? because it does not exist in Ruby yet.

I used the word "semantics" because the docs use the English word "anonymous", which is not formal, but would be surprising that anonymous? does not behave like what "anonymous" says today.

The problem is, in your logic, it's "if" not "iff".

Yes, but the other direction is "if the name is nil, then the module is anonymous". But this is not what we are discussing right? We are discussing if an anonymous module can have a name that is not nil. To see that is not possible, you need the arrow in the docs.

I think conceptually, a name is set needs to be clarified - do you mean a permanent name or a non-permanent name? I wonder if we should change name to return nil if set_temporary_name is used to avoid this confusion.

I personally believe it is fine, even expected, that name returns the temporary name. Because that is what it does when implicitly set by Ruby today, and because, well, it is the name at this point in time. It may change, variable vs constant.

Hey let me say explicitly that I am participating in the discussion to contribute to it, but Ruby core are who define how the language evolves. So, given we come from different angles that won't reconcile, whatever core decides is going to be OK for me :).

Updated by rubyFeedback (robert heiler) over 1 year ago

I don't think Rails should monkey patch Module in this way either.

This may be a problem with some of rails idioms, e. g. how HashWithIndifferentAccess
arose, to give one example. While one may understand the use case (not having to
care about same-named key strings and key symbols), it can be confusing when the
terminology does not match e. g. matz's expectation or terminology.

To the actual topic: this is the first time I heard about "permanent?". I think I
heard about anonymous modules before, but never ".permanent?()". For the purpose
of the proposal it may be easier to ignore .permanent? altogether and just focus
on the question of the usefulness / use case of "Module#anonymous?". (I have no
particular opinion on whether it is needed or not; in my own code I rarely have
unnamed things, not even using Class.new either. I try to keep meta-programming
as simple as possible to avoid my poor brain having to figure out what I did
months ago with some spaghetti code.)

Updated by fxn (Xavier Noria) over 1 year ago

@ioquatix (Samuel Williams) oh wait, we might have two questions in our minds.

I am like: Can a module whose name is not nil be anonymous?. Answer is, if we want to agree with the current docs, it cannot be, because Module#name has to return nil for anonymous modules.

Then, I guess you had in mind the definition of Module#anonymous?. That needs an "iff" indeed. However, if you have to define the predicate, then making that condition an "iff" could not be controversial, it is the natural extension to me.

Updated by Eregon (Benoit Daloze) over 1 year ago

I want to use this proposal to discuss this issue and whether there is anything we should do about such behaviour (or even if it's desirable).

I think it would be great to reflect it in the Module#name if a Module is no longer reachable through that name.
Either setting it back to nil/temporary name or something like #<Module:0x01 no longer reachable, was: Foo::Bar>.

Regarding Module#anonymous?, I think it should be true for Module.new::C = Module.new #=> #<Module:0x00007f92d6c66770>::C (note, the .name of that is "#<Module:0x00007f92d6c66770>::C", it's not nil)
I.e., it should only return false if Module#name is a valid constant path, i.e., if all components of the the constant path are valid constant names.

Updated by Eregon (Benoit Daloze) over 1 year ago

Eregon (Benoit Daloze) wrote in #note-27:

Regarding Module#anonymous?, I think it should be true for Module.new::C = Module.new #=> #<Module:0x00007f92d6c66770>::C (note, the .name of that is "#<Module:0x00007f92d6c66770>::C", it's not nil)
I.e., it should only return false if Module#name is a valid constant path, i.e., if all components of the the constant path are valid constant names.

It would mean on remove_const and on const_set(name, v) when there was already a constant name, to change the name of the old constant if it is a module.
And also do so recursively for any constant in that module, i.e., mirroring what we do when we name a module, it also names all module constant of that module.
Then I think we could finally trust without exception that a non-anonymous Module#name is a valid way to reach that Module. That would be great.

Updated by fxn (Xavier Noria) over 1 year ago

I think it would be great to reflect it in the Module#name if a Module is no longer reachable through that name.
Either setting it back to nil/temporary name or something like #<Module:0x01 no longer reachable, was: Foo::Bar>.

It would mean on remove_const and on const_set(name, v) when there was already a constant name, to change the name of the old constant if it is a module.

If I understand this correctly, I believe it is not possible preserving today's rules.

Nowadays, if a module was ever stored in a constant that belonged to a module with a permanent name, it has a permanent name.

In other words, today, this cannot happen:

A::B.non_permanent_name? # => true is not possible today

If a reassignment or remove_const changed the name of the module to temporary or nil, you could fall into that easily:

X::Y = Module.new
Z::W = X::Y
X::Y = Module.new

After that, Z::W.name cannot be nil or temporary.

Let me say it again: Ruby is not Java, there are no types or type identifiers or anything. It's all storage and objects. Chasing that a name is a constant path that resolves to the same object is going to leak some way or another, or will need to change the language.

If a logical system does not have enough constraints, you won't get properties that do not follow.

Updated by ioquatix (Samuel Williams) about 1 year ago

We can consider introducing the PR unchanged, it depends on permanent_classpath which is an internal implementation detail. If we fix const_set and remove_const to deal with changing class names, anonymous? will reflect that change, since itself it is not making any determination and rather just returning the existing state that Ruby knows about.

VALUE
rb_mod_anonymous_p(VALUE mod)
{
    return RBOOL(!RCLASS_EXT(mod)->permanent_classpath);
}

I also think we should consider Module#permanent? as described above - that the current name reflects the valid constant path for self. However, I agree, re-assignment can cause significant confusion. I am okay with the first assignment being considered special. Certainly that's the case today. After the first assignment, a module is considered permanent. If that binding is removed, e.g. remove_const, we could mark that as such. Rebinding a constant to a new constant path could reset the permanent name. However, I feel like that should be solved outside the scope of this PR.

So, in order to keep things focused, are there any changes we'd want to make to the above implementation?

Alternatively, we could consider:

VALUE
rb_mod_permanent_p(VALUE mod)
{
    return RBOOL(RCLASS_EXT(mod)->permanent_classpath);
}

However, I believe we'd want to follow up with another PR to correctly manipulate permanent_classpath to make more sense on remove_const et al. As has been discussed previously, I don't think anonymous?/permanent? are semantically opposite. I could ask Matz at the next developer meeting what he prefers.

Updated by ko1 (Koichi Sasada) 10 months ago

Another idea is introducing #permanent_name for Ruby 3.2's name which returns nil if it is not named.

Updated by fxn (Xavier Noria) 10 months ago

@ko1 (Koichi Sasada) I think that is a good direction, because with the introduction of temporary names as a blessed concept with API and all, I believe there are several matching APIs that may arise. One proposal could be:

Module#name           -> as it is today
Module#temporary_name -> temporary name or nil
Module#permanent_name -> permanent name or nil
Module#anonymous?     -> both temporary and permanent names are nil

For example, edges case that test how do we think about this:

m = Module.new

m.name           #=> nil
m.temporary_name #=> nil
m.permanent_name #=> nil
m.anonymous?     #=> true

m::C = Class.new
m::C.name           #=> "#<Module:0x0000000102cd4620>::C"
m::C.temporary_name #=> "#<Module:0x0000000102cd4620>::C"
m::C.permanent_name #=> nil
m::C.anonymous?     #=> false

M = m
m::C.name           #=> "M::C"
m::C.temporary_name #=> nil
m::C.permanent_name #=> "M::C"
m::C.anonymous?     #=> false

Updated by Dan0042 (Daniel DeLorme) 10 months ago

After reading all the above it doesn't seem like there's a clear agreement on what this proposed #anonymous? is supposed to be. If it's just equivalent to .name.nil? then what is the point? Else, what is the behavior exactly?

m::A.name       #=> "#<Module:0x00007f27f032d678>::A"
m::A.anonymous? #=> true or false?
M = m
m::A.name       #=> "M::A"
m::A.anonymous? #=> false
Object.send(:remove_const, :M)
m::A.name       #=> "M::A"
m::A.anonymous? #=> true or false?

And once the behavior is clarified, what is an example use case? It's very rare to use either an anonymous namespace or remove_const, so what would you use this #anonymous? method for? Some things have been mentioned like logging/formatting, serialisation/deserialization, debugging or meta-programming, but concrete examples are missing.

Since Ruby 3.0, Module#name can return a string like "#<Module:0x00007f27f032d678>::A" whereas previously it returned nil. I can imagine #anonymous? could be a way to get back the equivalent of .name.nil? in Ruby 2.7. The current implementation in Rails is probably buggy since Ruby 3.0, but no one has ever triggered the bug since no one ever uses anonymous modules as namespaces.

Updated by Eregon (Benoit Daloze) 10 months ago

Agreed, anonymous? seems not well defined for the temporary name case.
In fact I would think of anonymous? as !fully_named and indeed for the anecdote TruffleRuby already has this meaning for anonymous internally.

Adding Module#permanent_name seems potentially useful.
Maybe Module#temporary_name too, although if there is Module#permanent_name it's the same as mod.name unless mod.permanent_name.

Updated by Eregon (Benoit Daloze) 10 months ago

Ah BTW one line below that link we see something interesting which can be repro'd on CRuby like:

irb(main):006:0> m = Module.new
=> #<Module:0x00007fd24624d540>
irb(main):007:0> m::C = Class.new
=> #<Module:0x00007fd24624d540>::C
irb(main):008:0> c=m::C.new
=> #<#<Module:0x00007fd24624d540>::C:0x00007fd246515f20>
irb(main):009:0> Marshal.dump c
(irb):9:in `dump': can't dump anonymous class #<Module:0x00007fd24624d540>::C (TypeError)

So Ruby already calls a module/class with a temporary name as "anonymous" (yet m::C.name returns a String).

Updated by fxn (Xavier Noria) 10 months ago

@Dan0042 (Daniel DeLorme) yes, there are different points of view expressed, and at some point Ruby core will take one direction or another.

In my personal proposal, none of those objects are anonymous?, because, for me, a temporary name is a name. And a permanent name is a name too. If you have a name, you are not anonymous. (@ioquatix (Samuel Williams) had a different point of view.)

The last of your examples is important. In Ruby "permanent name" and "being reachable through the contant path" are orthogonal concepts. Once the permanent name is set, there is no public API to change it. It is frozen, and what happens to the perhaps dozen places where the object was stored is irrelevant.

The point of anonymous? is to have a predicate that expresses name.nil? with more concision. If you grep the Rails project, you'll see it used in a bunch of places (it is defined by Active Support).

Updated by Dan0042 (Daniel DeLorme) 10 months ago

In Rails this method is used for things like name.underscore unless anonymous? so it's pretty clear to me it should return true in case of a temporary name, otherwise name.underscore results in garbage like "#<module:0x00007fe87f033288>/x"

Updated by fxn (Xavier Noria) 10 months ago

@Dan0042 (Daniel DeLorme) right, it could be the case that in Rails anything autoloadable has a permanent name and that has made the predicate sufficient in practice after 3.0. I introduced that predicate myself back in 2010 because you got an empty string in 1.8 and nil in 1.9, so anonymous? at the same time had meaning and was portable.

Updated by fxn (Xavier Noria) 10 months ago

Correction: I have looked at the logs. It was introduced because of conciseness initially (the same way in Ruby we prefer array.empty? to array.length == 0, you prefer a method that captures the meaning). Right after that, the abstraction was leveraged to make the code portable.

Updated by jeremyevans0 (Jeremy Evans) 10 months ago

I'm against Module#anonymous?, because it's ambiguous. I would expect modules with temporary and not permanent names to be anonymous. If you could never refer to module by absolute constant reference, I would expect it to be should be considered anonymous, even if it has a temporary name. However, taken literally, anonymous means no name at all, so I can certainly understand arguments that any name should mean anonymous? returns false.

So permanent_name makes more sense to me, as it is less ambiguous. For Rails, permanent_name&.underscore seems better than name.underscore unless anonymous?.

Updated by fxn (Xavier Noria) 10 months ago

@jeremyevans0 (Jeremy Evans) I agree.

My defense of anonymous == name.nil? is based on the meaning of the adjective, as you said. You are anonymous unless you have a name. The discrepancy in this thread comes from "I have use cases in which I want to know if it does not have a permanent name". Well, fair enough, but that does not mean anonymous? is a good name. For example, permanent_name.nil? is crystal clear in contrast, and the corresponding predicate would be permanent_name? (not proposing, just for the sake of the argument).

For example, marshal.c now just checks if the name starts with #, I suspect this is also out-of-date now. You should check if the permanent name is nil.

Updated by ioquatix (Samuel Williams) 10 months ago

I think we can assume this is a fair definition, but I'd like to confirm it with @matz (Yukihiro Matsumoto):

class Module
  def permanent?
    if name = self.name
      Object.const_get(name).equal?(self)
    end
  rescue NameError
    false
  end

  def anonymous?
    !permanent?
  end
end

However, alternatively, we could defer to whether a class has (or had) a permanent name. The problem is, they are subtly different. The former (example) is quite a robust definition, while the latter (depending on whether a permanent name has or was assigned) is more prone to edge cases (like const_remove as discussed). If we fixed those edge cases, it might be sufficiently good enough to use and/or equivalent to the above. And I suppose my point is, maybe we should aim for them to be equivalent.

Updated by Dan0042 (Daniel DeLorme) 10 months ago

@ioquatix (Samuel Williams) I think your definition of "permanent" doesn't quite work, semantically. A name can be said to be permanent if doesn't change (even after remove_const for example). What you have there is the opposite; if a module stops being permanent after remove_const, it can't really be said to have been permanent in the first place. Maybe something like #namespace? would be a better name for that, in the sense of "this module can currently be used as a namespace".

Updated by fxn (Xavier Noria) 10 months ago

Same. The name is called "permanent" because it does not change, the predicate above would be reachable_through_its_name?.

But then, the Ruby language does no have any expectation about the name of classes and modules. If there was some, it would be present and enforced by the language.

The name is an attribute of class and module objects, a string, and that is pretty much it. To me, X = Module.new and Y = Object.new are essentially the same. It is true that the first one has a side-effect, but other than that this is storage and objects, and those are orthogonal.

You don't even have "namespaces", a module does not have a namespace. A module is an object and can be stored in 7 constants and 4 variables. In Ruby, class and module objects have constants and that is all. It's a pretty weak model in that sense (meaning, you cannot derive much from it).

Updated by fxn (Xavier Noria) 10 months ago

I have all these matters in my mind pretty clearly (and I am writing a book about constants), but in case someone is reading and is not used to the orthogonality of all this, you can even have a permanent name set that is not reachable even when created:

M = Module.new
N = M

Object.send(:remove_const, :M)

N::C = Class.new
N::C.name # => "M::C"

module N
  class D
  end
end

N::D.name # => "M::D"

The reason is that the resolution algorithms determine on which class or module object you are creating the constants. The answer is "in the one stored in the N constant stored in the class object stored in the Object constant". OK, you got an object that was stored somewhere. It has a name attribute, and from that string you derive a new string.

It is all quite orthogonal.

Updated by matz (Yukihiro Matsumoto) 10 months ago

First, given the existence of the Module#set_temporary_name method as well, it seems somewhat confusing what the name of this method implies.
So, it may be necessary to first clarify what this method really wants to accomplish.

In general, in Ruby, the referenced object does not know information about the referencing object (or variables/constants). Class names are an exception, but we recognize that they are for convenience purposes only and are not essential information. The proposed method would have a greater impact than OP thinks, since it would make the ability to retain this class name essential information in Ruby.

So currently, I am not positive for adding the method (not regarding the name issue).

Matz.

Updated by fxn (Xavier Noria) 9 months ago

Now, the name of classes and modules can be one of three things:

  1. nil
  2. Temporary
  3. Permanent

Maybe it would make sense to provide matching predicates, say:

  1. mod.anonymous?
  2. mod.temporary_name?
  3. mod.permanent_name?

so that users have an encapsulated way to check the status of the name.

For example, in the Rails code base, logic like this:

@controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?

could be rewritten as

@controller_path ||= name.delete_suffix("Controller").underscore if permanent_name?

What do you think?

Updated by fxn (Xavier Noria) 8 months ago · Edited

A KISS version of the above is to provide permanent_name? only, since the other two can be derived with the help of mod.name.nil?, and I suspect that there are not a lot of cases in which you need to know if the name is exactly temporary.

Actions

Also available in: Atom PDF

Like1
Like0Like0Like1Like0Like1Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like1Like1Like1Like1Like1Like0Like0Like0Like1Like0Like0Like0Like0Like0Like0Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0