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?
class Foo
INSTANCE = new
private def initialize
end
end
Oh, that looks nice. But how does that work? In an interpreted language, that would create an instance as soon as loading the file, something that one would want to avoid. But I’m guessing that the compiler would notice if one didn’t use that constant, and optimize the initialization away, or?
Hi, How you extract the functionality into module? could you please give code sample? And, what is the compilation error?
AFAIK, you code should just work.
module M1
private SINGLETON = ::Mutex.new
@@instance : self?
macro included
def self.instance
@@instance || SINGLETON.synchronize do
@@instance ||= new
end
end
end
end
class C1
include M1
end
c1 = C1.instance # => #<C1:0x7fb698e1be60>
c2 = C1.instance # => #<C1:0x7fb698e1be60>
Although, as said by straight-shoota, code can be reduce to: (ignore make initialize private)
module M1
@@instance : self?
macro included
def self.instance
@@instance ||= new
end
end
end
class C1
include M1
end
p c1 = C1.instance # => #<C1:0x7fb698e1be60>
p c2 = C1.instance # => #<C1:0x7fb698e1be60>
AFAIK, the class variable in Crystal(basically, the ruby equivalent of class instance variable, right?), can be guaranteed to be initialized only once for includeded class?(check following example) could you please give a example for initialized twice? thanks
module M1
@@instance : self?
macro included
def self.instance
@@instance ||= new
end
end
end
class C1
include M1
end
class C2
include M1
end
p C1.instance # => #<C1:0x7fb698e1be60>
p C1.instance # => #<C1:0x7fb698e1be60>
p C2.instance # => #<C2:0x7fb698e1be70>
p C2.instance # => #<C2:0x7fb698e1be70>
I have one more question hope the core team can answer me.
Why we can introduce (very specified) module like OAuth Client into standard lib, but, instead, a minimized implementation Singleton pattern could not be added?
Sure, it’s nothing fancy. Just a trivially minimal singleton implementation. That’s good enough for many use cases where people have asked for this.
I expect a more sophisticated implementation would be accepted into stdlib. There is an open feature request for this: Singleton module · Issue #8846 · crystal-lang/crystal · GitHub
However, little response to that issue indicates not a lot of interest from users. So nobody has picked it up yet. Contributions are welcome, but it’s not a priority for the Core Team.
While a more correct implementation that, for instance, prevents duping, can be considered more “pure”, what a lot of people are probably looking for when they say “singleton” is just a class that makes it easy to get that single instance without too much faffing about.
In that light @straight-shoota’s example is simple and elegant.
Of course, that doesn’t help much if you really want to make sure that nobody ever clones your INSTANCE. I guess it could become a problem in big projects.
But one thing I’ve learned is that you don’t always need the proper textbook implementation of “a thing”. Often something simpler but with limitations works just as well, and can make things simpler. Sometimes you just extract data from HTML with regexps.