For my project, I need to use singleton in Crystal.
I have a working example that’s OK if the class, where the singleton is defined, is used.
But when I create a child class (I inherit from the previous class), the singleton instance is still typed as the parent class.
class Person
def self.instance
@@instance ||= new
end
end
class John < Person
end
# typeof(John.instance) # => Person
p! typeof(John.instance)
How can I achieve a singleton pattern that’s consistent across inheritance (i.e. the correct output would be typeof(John.instance) # => John)?
class Person
private class_property instance : self?
def self.instance : self
self.instance ||= self.new
end
end
class John < Person
end
# typeof(John.instance) # => John
p! typeof(John.instance)
Right, the type of @@instance is Person and John is a Person. The type of a class variable is only one for the entire hierarchy, in this case it’s Person. But the runtime type of it is different.
Okay, I played around with it a bit and discovered something interesting
The solution by @RespiteSage has the correct compile type indeed, but when accessing it, a stack overflow error is thrown (carc.in here). So that’s a no-go. (btw, why does that happen?)
So to be more exact, I am trying to do something like
class Person
def self.instance
@@instance ||= new
end
end
class John < Person
def hello
p "Hello from John"
end
end
class Greeter
@@person : John = John.instance
def self.greet
@@person.hello
end
end
Greeter.greet
This code above does NOT compile, because the compile type of instance is Person+ and not John. So even though @asterite is correct about the runtime type, it doesn’t really help in this scenario.
How hard could it be to add the as implicitly if the method has a type annotation?
class Person
def self.instance : self
@@instance ||= new # implicit: .as(self)
end
end
This should go, of course, in every returned object of the body of a method. If the cast is invalid because of an error, that would be caught correctly. WDYT?