When creating a subclass from a parent class I want the constants to carry over to the sub classes. Right now if you mondify those classes the parents class constant gets modified.
class Foo
CONST = [] of Foo.class
macro inherited
{{@type.name.split("::").first.id}}::CONST << {{@type.resolve}}
end
end
class Bar < Foo
end
class Baz < Foo
end
p Bar::CONST
p Baz::CONST
output:
% crystal tmp.cr
[Bar, Baz]
[Bar, Baz]
I'm pretty sure this is the expected behavior since they're all just a reference to the same constant. I.e. same thing as if you did:
class Foo
CONST = [] of Foo.class
end
class Bar < Foo
end
class Baz < Foo
end
Foo::CONST << Bar
Foo::CONST << Baz
Bar::CONST # => [Bar, Baz]
Baz::CONST # => [Bar, Baz]
Yes, this is expected behavior. You can declare the constant again in the subclasses... but maybe explain what you want to accomplish in general?
I want to create a class per DB table and what them to have a constant array of the columns they have.
class TableOne < Table
COLUMNS = [Email, Id]
end
Class TableTwo < Table
COLUMNS = [Email, Id
end
I would rather be able to discover these through a macro.
Whats the reasoning for using a constant for this? Won't you also need something to allow for getting/setting the values related to each column?
Could do something like:
annotation Column; end
class TableOne
@[Column]
getter! id : Int32
@[Column]
property! email : String
end
Then you can access the related ivars/annotation data at compile time via macros.
I’ve run into something similar in a number of spots in Lucky. The use case is that it needs to be a constant so that I can use the data in a macro. As far as I know constants are the only way to get at the information. What we end up doing is copying the constants when inherited and included.
https://github.com/luckyframework/lucky/blob/master/src/lucky/exposable.cr and https://github.com/luckyframework/lucky/blob/master/src/lucky/assignable.cr
I can see why constants are the same, but it’d be awesome if maybe there was a built in crystal method to “copy” the constants. So I could call “copy_constant EXPOSURES” and it’s copy the constant when including and inheriting.
The use case is that it needs to be a constant so that I can use the data in a macro. As far as I know constants are the only way to get at the information.
That's one of two ways. The other being annotations. However each has their pros and cons. Depends on the context which one would be better.
I think the current approach of using constants to store compile-time data, together with inherited/included hooks is very messy. For 2.0, or even during the development of 1.0, we should gather some use cases and think of how to solve this without so many hoops.
@asterite That is a fantastic idea. Maybe some other structure for storing compile time data that isn't a constant would be super cool. Ping me if you want to work on this or need some use-cases. I've got a few in Lucky and Avram :D
You could do this:
class Foo
macro inherited
CONST = [] of Foo.class
{{@type.name.split("::").first.id}}::CONST << {{@type.resolve}}
end
end
class Bar < Foo
end
class Baz < Foo
end
p Bar::CONST
p Baz::CONST
Unless you're expecting to use Foo::CONST directly.
This would not be necessary for me if it was easier to query left an right in namespace nesting. This would be an awesome feature for crystal.
Foo::Bar::Baz
I would like to access all the module/classes right of Bar and the module/class left of Bar.
With a feature like this I would not have to create all the constants.
@jwoertink You solution is really cleaver and I like it.
I think I am finding a way to work with this pattern but I have found a frustrating little quirk.
class Foo
macro inherited
CONST = [] of Foo.class
{{@type.name.split("::").first.id}}::CONST << {{@type.resolve}}
end
def self.const
CONST
end
end
class Bar < Foo
end
class Baz < Foo
end
p Bar.const
output:
Showing last frame. Use --error-trace for full trace.
In tmp.cr:9:5
9 | CONST
^
Error: undefined constant CONST
You can work around the lookup problem by moving everything to the inherited macro. However it would be nice to have this be able to lookup the constant from the generated code.
@wontruefree This works:
class Foo
macro inherited
CONST = [] of Foo.class
{{@type.name.split("::").first.id}}::CONST << {{@type.resolve}}
def self.const
CONST
end
end
end
class Bar < Foo
end
class Baz < Foo
end
p Bar.const
That said, I'm really curious about why all of this is needed...
This works too:
class Foo
macro inherited
CONST = [] of Foo.class
{{@type.name.split("::").first.id}}::CONST << {{@type.resolve}}
end
def self.const
{% begin %}
{{@type}}::CONST
{% end %}
end
end
class Bar < Foo
end
class Baz < Foo
end
p Bar.const
Thanks that seems to work for some things.
I dont know why but this pattern does not work in a few places for me. I have not narrowed down why yet.
I'm closing this because this is expected behavior.