-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Forward type declarations #10723
Comments
Related to #11764, in languages that support them, I almost always like to define my methods outside of the class definition. For me, it makes the code much more readable - the class doesn't span pages and pages - and the class definition then serves as an important piece of documentation. I can read through the methods in the class over about one page (and if it's more than that, I probably haven't broken it up enough to be modular) and see how I am supposed to use it, and what is important and what's not. It's also maintaining the concept of interface-implementation abstraction. The class body defines the interface, the method implementation I don't need to care about most of the time. Especially when two spaces of indentation is customary, I find it very hard to get to the jist of what a class does when the methods are defined in full within the class body. Because Crystal allows re-opening a class, I came up with a trivial macro I could use to define methods outside of the class. However, whilst this makes the code much more readable, I then lose my declarations within the class. These could of course be made as empty within the class and overwritten, but unless we add some more magic into the macro, to check if the method already exists in some form, this of course would be dangerous - because if I change the implementation's signature, the compiler will probably now pick the empty no-op overload and not the one I want, and that's potentially a subtle bug to fix. I use something similar in Python for the same reason - the class just has methods with So I think this feature would be useful in another case here as well. May not suit everyone, but I'd definitely make use of the ability to forward-declare a method so I can then define it later on in this or another file. I've also heard of dependency injection form things like C#, and, I don't know the details, so no idea if it'd be a good or a bad thing to consider in Crystal, but it might be possible to attach an implementations to a class or module at runtime or at least bind it late in the compilation process using something like this. |
"Forward" type declarations in Crystal, i.e. defining empty types in one place and then reopening them later, can be used to declare the type hierarchy prior to def definitions, so that the order between overloads is properly computed (see #4897 and #10518), and they might also be useful down the road for incremental compilation. But since Crystal makes no distinction between a type declaration and a type definition, there are two shortcomings to this approach:
Big*
types must be defined even when only one of them is needed, because all of those types includeComparable
.I propose the addition of a
@[Declaration]
annotation or an equivalent keyword. The forward declarations for theBig*
types might look like below:The presence of
@[Declaration]
distinguishes forward type declarations from type definitions. The semantics would be as follows:include
s,extend
s, visibility modifiers, generic type parameters,abstract
, and other nested declarations, nothing else is allowed. In particular, instance / class variables and abstract defs are not declarations.@[Flags]
.@[Declaration]
applies recursively to types within the applied type's scope.@[Declaration]
are always visible in macros and no intermediate state can be observed:class {{ "T#{`date '+%s'`}".id }}
, but types likerecord Foo
can still be declared (arecord
is simply astruct
node and it absolutely can represent a reopened type).@[Declaration]
would count as a definition; things that must apply only to a type's first definition could go there as usual, e.g.include
s as long as the client ensures no overloads are broken, and enum values.typeof(T)
,sizeof(T)
,instance_sizeof(T)
,offsetof(T, ...)
, etc. are also uses of typeT
's definition.It is undecided whether docstrings and annotations other than
@[Declaration]
are allowed on forward declarations.For very large libraries like the entire standard library, it would be cumbersome to write out all forward declarations by hand, but we shouldn't need to;
crystal tool hierarchy
could have a new "decls" format, and we get all the declarations by running it onsrc/**/*.cr
(orsrc/docs_main.cr
for the standard library).The text was updated successfully, but these errors were encountered: