I tried to implement the same thing in Crystal.
I started with writing a class with the singleton property.
The class works just fine.
class Klass
private SINGLETON = ::Mutex.new
@@instance : self?
def self.instance
@@instance || SINGLETON.synchronize do
@@instance ||= new
end
end
end
However, when I tried extracting the functionality into a module, I’ll get a compilation error.
Could you show me how to do it properly? Or if this is possible at all?
One thing I like about Ruby is people always make a trade-off on type-safety with convenience and usabilities.
module Singleton
macro included
private SINGLETON_LOCK = Mutex.new
def self.instance : self
@@instance || SINGLETON_LOCK.synchronize { @@instance ||= new }
end
macro method_added(m)
\{% if m.name == "initialize" %}
private def initialize
previous_def
end
\{% end %}
end
end
end
Constants are guaranteed to be initialized only once, so instead of a class variable, you could just use a constant (being a signleton it won’t need to be reassigned anyway) and don’t need to do mutex synchronization. The runtime already takes care of that.
So the implementation of a singleton is actually quite trivial. IMO there’s no need for a generalized module to include this, when it’s just three loc:
class Foo
INSTANCE = new
private def initialize
end
end
OFC you can still inherit this type, or call #dup/#clone to get a copy. If you want you can also restrict this. But I don’t think that’s strictly necessary. The important thing about a singleton is to not expose a public API for directly creating a new instance (.new).
Another alternative to a singleton instance is simply using a module.
module FooSingleton
class_property foo = true
end
FooSingleton.foo
However, using constant might not fit the bill in some situations.
For example, in my case, the initialization is a binding to C object initialization; the operation of the C library is a side effect of class initialization.
The constant approach won’t work because the side-effects would always occur.
In contrast, singleton instance can be lazily evaluated.
Someone already made the point in the issue.
Now the Mutex is already in the core. Do you guys still work on the singleton module?