Is there a way to make this work other than by defining a method on each class?
class C
CONST = 1
end
klass = C
p klass
p klass.new
#p klass::CONST
#p klass.CONST
#p klass.constant("CONST")
Probably never going to happen.
You should define a class method and access the constant through it. Then you can override that method in subclasses.
That's the correct way to do it in OOP. It works and there's no need to add more things to the language for just this thing.
Semi-related: https://github.com/crystal-lang/crystal/issues/3241
That's the correct way to do it in OOP. It works and there's no need to add more things to the language for just this thing.
@asterite
Why does klass.new
work at all if it's companion accessors don't work automatically? What purpose does the extra ceremony of writing methods serve? I don't see the logic.
Maybe copying OOP that way other languages have done it isn't the best option.
@postmodern just replace that with methods, and let subclasses implement them. The methods should return the actual class instances. And you will need a base type to be able to type it.
There are many languages without that feature, yet they have CRC logic. Crystal is turing complete.
If you can't live with Ary's suggestion, you can also hack your way around with an inherited macro:
abstract class Base
macro inherited
def foo
FOO
end
end
end
class Sub < Base
FOO = "hey"
end
Not saying I endorse doing this a lot.
@jhass No need for macro I think:
class Base
def self.foo
{% begin %}
{{ @type }}::FOO
{% end %}
end
end
class Sub < Base
FOO = "hey"
end
Also:
class Object
macro const_get(const)
{% begin %}
{{@type}}::{{const.id}}
{% end %}
end
end
class Foo
X = 1
def self.x
const_get(X)
end
end
class Bar < Foo
X = 2
end
p Foo.const_get(X) # => 1
p Bar.const_get(X) # => 2
p Foo.x # => 1
p Bar.x # => 2
@asterite since this Ruby-like Object#const_get
macro is that simple... wouldn't it fit great in corelib?
Sure!
Just note that I couldn't find a way to do this by calling it on a class instance... it just works if the class is an explicit identifier.
Also, I'm starting to think that it would be great to have other reflection macros as methods too, like superclass, subclasses, ancestors, includers, methods, etc. Even though they might not be useful at runtime, they can help quickly debug or understand something.
I really need for const_get
right now. When I have module Adapter
, and some classes inside, like MySQL
or PostgreSQL
, and I want to get a class by identifier from config, like adapter: postgresql
.
@AlexWayfer That can't be implemented, and will never will.
The macro const_get
here must be passed an identifier known at compile time.
That is it will never work with a const_get
like macro, one approach you can take is to build up a runtime registry (Hash), possibly automatically using things like the inherited marco such that you can do something like my_adatpter = adapters[config_value]
where adapters is something like {"mysql" => MySQL.new, "postgresql" => PostgreSQL.new}
FWIW, I recently used the same mechanism that @jhass proposed above to build a plugin architecture for some software that I am working on. So far, so good. It seems to be working well enough. The inherited macro lets any plugin classes that are compiled register their existence with a registry class, so that the class can be looked up via a name, and instantiated as needed.
Most helpful comment
Also: