2

I have this Ruby code where I try to implement the Singleton pattern manually:

class A
  @a = A.new

  def self.instance
    p 'initialized'
    @a
  end

  private_class_method :new
end

A.instance #=> prints 'initialized'

Unfortunately, the object will be created before A.instance is even called. To avoid this, I thought of changing the code:

class A
  @a = nil

  def self.instance
    p 'initialized'
    @a ||= A.new
  end

  private_class_method :new
end

A.instance

I get "private method `new' called for A:Class (NoMethodError)" error though. This is very puzzling, why do I get this error in the second example and not in the first? The only difference is that in the second example .new is called in a class method definition. I deliberately put private_class_method on the bottom so this kind of error is prevented (putting it on the top will give the error for both examples). Btw, I'm aware this will work if I change @a from being a class instance variable to a class variable (to start with @@). I don't understand why this would work, since I know instance variables are relative to SELF and SELF is the class, both where I initialize @a to nil and where I lazy instantiate it in self.instance.

daremkd
  • 8,244
  • 6
  • 40
  • 66
  • You don't need `@a = nil`. Without it, `@a` on the right side of `@a = @a || A.new` (expansion of `@a ||= A.new`) is undefined, so will be evaluated as `nil`. – Cary Swoveland Nov 02 '14 at 16:12

1 Answers1

1

Here is a strange thing.

A.new doesn't work because you should call private method only directly, so you should use explicit new call.

In the other hand new call is something like implicit self.new but self.new will raise an exception like A.new. And this is the strange part I don't understand

class A
  def self.instance
    p 'initialized'
    # ok
    new
    # should be ok but it is not
    self.new rescue p("error self.new: #{$!}")
    # should fail
    A.new rescue p("error A.new: #{$!}")
  end

  private_class_method :new
end

A.instance
# "initialized"
# "error self.new: private method `new' called for A:Class"
# "error A.new: private method `new' called for A:Class"

PS: http://weblog.jamisbuck.org/2007/2/23/method-visibility-in-ruby

You can't use explicit receiver with private methods

SO: Understanding private methods in Ruby

Community
  • 1
  • 1
fl00r
  • 82,987
  • 33
  • 217
  • 237
  • @CarySwoveland `A.new` and `new` will behave differently. `A.new` will raise an exception while `new` won't. The problem is that `self.new` should behave like `new` (it is implicitly the same) but it doesn't! Investigating why is that – fl00r Nov 02 '14 at 16:20
  • Yes, I noticed before you commented, which is why I deleted my comment. (I think invoking private methods with explicit receivers will always raise an exception, which makes sense as it may be being done from outside the class.) I suggest you edit to add your explanation to your answer. – Cary Swoveland Nov 02 '14 at 16:22
  • @CarySwoveland, Yes, but `new` in Ruby semantically the same as `self.new`. But looks like method dispatching doesn't think so. – fl00r Nov 02 '14 at 16:31
  • @CarySwoveland, you actually were right http://weblog.jamisbuck.org/2007/2/23/method-visibility-in-ruby receiver does matter – fl00r Nov 02 '14 at 16:50
  • I think Ruby simply will not send private methods to explicit receivers. When it sees the receiver is explicit, I think it says to itself, "If that receiver is `self` when private `new` is to be sent to it, that's OK; otherwise, I won't do it. I could add some code to check if that's the case when the code is executing, but itt would be more efficient to just ask the coder to not use an explicit receiver in this situation." – Cary Swoveland Nov 02 '14 at 17:03
  • "actually" in your last comment produced a chuckle here. (I know it was not meant as "..for once you were right".) :-) – Cary Swoveland Nov 02 '14 at 17:11
  • I believe there are tons of grammar and syntax mistakes, sorry for that. I don't know english well – fl00r Nov 02 '14 at 17:58
  • Your English is far better than my Rapa Nui! – Cary Swoveland Nov 02 '14 at 18:04