Modules : used as namespace and partial type at the same time

Thinking about the use of modules as namespaces and partial types, I was surprised to find that it was possible to use them “simultaneously”, but the documentation doesn’t say anything about this.

The code below works perfectly, but it has a form of recursion that puzzles me a little.

module Group
  getter var
  struct Abc
    include Group
    def initialize(@var : String)
    end
    def to_s(io : IO)
      io << var
    end
  end
end
v = Group::Abc.new("Hello")
puts v # => "Hello"

Is this use of modules valid?

1 Like

These concepts actually belong together. Including a module includes its namespace into the current one.

However, until now I hadn’t realized that this applies to types/constants as well. So in your example, the recursion leads to Group::Abc::Abc::Abc::Abc::Abc::Abc being defined (it’s effectively an implicit alias to Group::Abc).
I don’t think this is really useful for anything, but the compiler seems to handle it well :person_shrugging:

I came across this particularity by “accident”, while working on the following use case: is it possible to put several structs in the same namespace, so that each can benefit from common functions and attributes, using a single module?

Clearly not a good idea it seems, but I was surprised to see that it worked, and at the same time, surprised that it didn’t produce an error at compile time, as there’s no base case to stop the recursion.

How many structs Group::Abc::Abc::Abc::Abc… can be generated in this case? (I’ve tried several dozen!!!)

The nested types don’t actually exist. Included constants seem to be implicitly aliased. The program only defines a single type, Group::Abc.
So I don’t think there’s any relevant performance implication from this.

Not sure I understand what you mean. Sure you can define multiple types (regardless if struct or class) in the same namespace. And you can include the same module in all of them (abstract struct might be another option for this).

Yes, of course, I know that.
My thinking on the use case was somewhat “economic” or “aesthetic”: don’t use an additional module if the one being created meets the need.
So, to sum up, I’m sticking with the idea that this syntax is not officially defined in the Crystal language, even though the compiler supports it in its current version without any performance penalty.

On reflection, I find this language construction neither lacking in concision nor elegance! :thinking: :wink:

1 Like

This feature may not be explicitly documented, but it’s certainly intentional and well-defined. include makes everything in the included namespace available, and for this it just doesn’t matter if that includes the current namespace (which leads to the recursion).
So I’d think it’s completely safe to use this. It’s part of the language and it would be impossible to change it without causing a breaking change.

It’s the same in Ruby btw.

Thanks @straight-shoota for these important clarifications.