Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A "namespace" feature proposal #265

Closed
907th opened this issue Nov 28, 2014 · 10 comments
Closed

A "namespace" feature proposal #265

907th opened this issue Nov 28, 2014 · 10 comments

Comments

@907th
Copy link

907th commented Nov 28, 2014

I want to know your opinion about introducing a "namespace" feature. It is better to describe it by code example (because I'm not so good in English):

Use namespace:

# models/payment.rb
class Payment < Parent
end

# services/payment/some_service.rb
namespace Payment  # I don't use a full declaration of the Payment here
  class SomeService
  end
end

Instead of:

# models/payment.rb
class Payment < Parent
end

# services/payment/some_service.rb
class Payment < Parent  # WTF? I don't want to redeclare the Payment class again and again!
  class SomeService
  end
end

So, using a namespace is just like saying: hey, put a full declaration of that const right here.
I think it could be implemented using macros, am I right?

Thank you! Cool project!

@asterite
Copy link
Member

One thing: when you reopen a type you don't need to re-specify its superclass. So the second example can just be:

class Payment
  class SomeService
  end
end

In fact, the second one can be written like this (both in Ruby and Crystal):

class Payment::SomeService
end

So there you define something inside Payment without having to say Payment is a class.

However, I think I understand what you mean. If I want to reopen Payment and add some methods, I would do this:

class Payment
  def some_method; ... end
end

Later if I change Payment to be a module, or a struct, I would have to change it everywhere. Annoying... right?

I think this could be done with macros, as you say. Something like this:

macro namespace(type)
  {{typeof(type).kind}} {{type}}
    {{yield}}
  {{:end.id}}
end

But we'd need to add a few things to macros:

  1. Add typeof in macros, to get the type of a path, so typeof(String) would give you a type (String there is an ASTNode).
  2. Add the kind methods to a type, that would give you something like class, module, struct, etc.

I think I would name it reopen instead of namespace, so it will look like:

reopen String do
  ...
end

However, I'm not totally convinced of this. The times I had to change the type of a type were very few and the refactor takes very little time, just some seconds. Another "issue" is that errors inside definitions there would end up being shown as inside macro expansions, which is always uglier than just regular errors.

But I'll leave this issue open, maybe later we find more reasons to add it.

@jhass
Copy link
Member

jhass commented Nov 28, 2014

-1

Opening classes and modules is not redeclaring them, it's opening them. And as @asterite already said, you don't change a class to a module or a module to a class very often, and doing a s/module Foo/class Foo/ across an project is not hat big of a deal really.

Another point is that reopening a class is actually a pretty rare thing to do, most of the time you reopen a module that you use as a namespace. And changing a namespace module into a class is even more rare thing to do in my experience.

Additionally if we maybe get something like Rubys refinements in Crystal at some point, the cases for reopening a class are reduced to a minimum.

@papamarkou
Copy link
Contributor

I was meant to ask, is it a usual Crystal or Ruby programming practice to reopen a module? I had once to reopen a class so far, yet I have been trying to find out what is the most conventional way to spread a module across different files. What I had in mind was to place the math-related functionality in a math directory where in there I would add some files (gamma.cr, bessel.cr for example, etc). Would it be normal to then define the Math module across these files, or is it more usual to use include or extend? I am only now getting used to Ruby's idiom, so excuse the ignorance.

@jhass
Copy link
Member

jhass commented Nov 28, 2014

include/extend are for separation on the logical level, not at the file level. You would just reopen the module in this case, yes.

@papamarkou
Copy link
Contributor

Thanks for the tip @jhass.

@907th
Copy link
Author

907th commented Nov 29, 2014

Thank you for your answers!
I didn't know I can reopen a class without a need to redeclare its superclass. That is great!

Prehistory (why did I proposed that):
In Rails' development environment classes are autoloaded and loading order is not always deterministic, so I got next issue several times before:

class A ; end

# b.rb
class B < A ; end

# b/c.rb
class B
  class C ; end
end

If b.rb loaded before b/c.rb - all OK. But sometimes b/c.rb loaded before b.rb which raise superclass mismatch for class B error. It can be solved either by redeclaring a class B's superclass (which is annoying) or by require_dependency/require (which is annoying too, because I don't want to reopen B actually, I just want to use it as a namespace for C). It is because I started to think about a "namespace" feature.

Does Crystal support const-autoloading/const_missing?

@907th
Copy link
Author

907th commented Nov 29, 2014

Comparison of different types of class/module/etc opening:

# reopen X
reopen X
  ...
end

# class X
class X
  ...
end

# class X::Y
class X::Y
  ...
end
Feature reopen X class X class X::Y
Lexical scoping lost No No Yes
Raise error "const missing" Yes No Yes
Create const if it not exist No Yes No

@asterite
Copy link
Member

Autoloading is not supported, but might be supported in the future. That is, the compiler would add requires based on a type's name. I think I tried to implement that once and ran into some issues, but it's not something impossible to do.

@asterite
Copy link
Member

I'll close this for now, reopening types works as expected and there's no need to redeclare the supertype.

@907th
Copy link
Author

907th commented Jun 17, 2015

@asterite Ok. But does Crystal supports const-autoloading/const_missing?

I just want you to understand what the proposal is. There is difference between 'opening a class the first time' (aka class declaration/creation/etc) and 'reopening class'. If your language supports const autoloading you will never be sure in which order classes are loaded. So you will never be sure what happens first - class declaration or class reopening. If the reopening happens first and you don't declared the class' supertype - you will actually declare a new class with wrong inheritance information. PS. Yes, crystal is compiling language, so your program will just not compile in such cases, but the issue is the hell in Ruby/Rails.

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

No branches or pull requests

4 participants