Some time ago I realized that we really don’t need include, extend and require be keywords. They can be macros defined at the top-level, marked with @[Primitive] and handled specially in the compiler.
The end result is not too much. But we get these things:
Parsing is slightly easier because we don’t need to consider these cases
They will appear in API docs and we can document them nicely, in addition to documenting them in the Reference guide
One could define an include or require method in a class and use that without self. and it will work. For example, a spec library like Spectator could define an include method so one can do expect(subject).to include(something) (right now that’s not possible)
I played a bit with the compiler and this seems to be really easy to implement.
However, we’d need to take care about calling these macros from locations where they’re currently invalid, such as {% require "foo" %}. I could see some use case from calling require from a macro context if you want to load the source of that file into a macro variable. So it would probably be fine if it worked. But then it creates problems with resolving multiple require calls to the same file. So we probably need to keep some restrictions in place to make sure code from a required file ends up in the program.
Quick question: Will changing include to macro going to change existing semantics? For example now if we include some Module Foo and when we invoke obj.is_a?(Foo) this returns true. Will this behavior remain same after the change?
module Foo
def foo
"foo"
end
end
class Bar
include Foo
end
b = Bar.new
pp b.is_a?(Foo) # => true
For the record: Adding these features to the API docs would work even without making them primitive macros. We could do the same as with pseudo methods (see src/docs_pseudo_methods.cr).
So both changes are technically independent. But the core is a change in how we view them.