0

I want to have more than one constructor in Ruby. I know that I have to use self methods, but the thing is that I don't know how to implement them. For example:

def initialize(n1 = 0,n2 = 0,n3 = 0)
  @num1 = n1
  @num2 = n2
  @num3 = n3
end

def self.MyClass(num1, num3)
...
end

def self.MyClass(num2,num3)
 ...
end

So in the first case what I want to do is to only give a value to num1 and num3, not to num2. And in the second case I want to give a value only to num2 and num3, but not to num1. How can I do that?

tukan
  • 17,050
  • 1
  • 20
  • 48
Kernel
  • 107
  • 1
  • 10
  • In your case you are setting values to all n1, n2, n3 to 0. I think it would be wise to tell us the bigger picture; what is your goal. – tukan Oct 13 '19 at 09:08
  • Can you be more precise as to what you want to achieve? There is no such thing as a "constructor" in Ruby, so it is really unclear what your actual goal is. Also, note that you have two methods with the same name in your code, so the latter will simply overwrite the former. (You should always turn on warnings, it will actually tell you that you are overwriting a method in this case!) – Jörg W Mittag Oct 13 '19 at 09:26
  • I think they're referring to the thing that constructs instances that he's posted in his question – Mark Oct 13 '19 at 09:27
  • Try following https://stackoverflow.com/questions/3958735/in-ruby-is-there-a-way-to-overload-the-initialize-constructor - should be what you're after – Mark Oct 13 '19 at 09:27
  • @Mark: The *closest* Ruby has to "a thing that constructs instances" is `Class#allocate`, which I don't see in the question. – Jörg W Mittag Oct 13 '19 at 09:27
  • It would honestly be quicker for you to just help out the questioner rather than pick up on a technicality. If you genuinely didn't know his intent in the question I'd be absolutely amazed. – Mark Oct 13 '19 at 09:31
  • Okay, what I want to achieve is to create different objects in which some of them I only want to give a value to some of the attributes,not to all of them. So, with my example I want to create an object with the first constructor. And the parameters that the constructors receives are 1 and 3, so my object's attributes would be num1 = 1, num2 = 0 and num3=3 – Kernel Oct 13 '19 at 09:49
  • @JörgWMittag Hey, I'm sorry, could you tell me why is the `#initialize` method is not considered a constructor and what is the correct term for it (just an initializer?)? How is `#allocate` closer to a constructor than `#initialize` is? If you don't mind answering, I can also open a new question regarding this. Thank you – Viktor Oct 13 '19 at 10:34
  • In most languages that have construct called "constructor", a constructor is a completely separate construct, a piece of executable code that is distinct from other pieces of executable code. For example, Java has instance methods, static methods, instance initializers, static initializers, and constructors, and they are 5 different kinds of executable code with 5 different sets of rules what kind of code is allowed inside of them, and so on. They are also all called in different ways. In particular, constructors are different from methods in almost every way: what is allowed inside of them, … – Jörg W Mittag Oct 13 '19 at 17:54
  • … how they are called, etc. Even in ECMAScript, where constructors are just normal functions, there is an important difference, in that a function only becomes a constructor when it is called in a special way (using the `new` operator), and when called in this special way and acting as a constructor, again, the code inside the constructor changes its meaning. (ECMAScript 5 then added constructors in classes as a distinct concept, even further cementing the difference.) `initialize`, however, is none of those things. It isn't called in a special way, the code inside has no special meaning, it … – Jörg W Mittag Oct 13 '19 at 17:57
  • … is *literally* just a method like any other method. In fact, there is no such thing in Ruby as Java's various "like a method but different" things. Ruby has instance methods, and that's it. (There *are* other kinds of executable code, though: script bodies, module / class definition bodies, and blocks / lambdas.) – Jörg W Mittag Oct 13 '19 at 17:59
  • We can also look at it simply from a naming point of view: `initialize` doesn't "construct" anything, so it is not a constructor. In fact, since `initialize` is an instance method, and instance methods get called on, you know, *instances*, it is clear that in some way, shape, or form, the object must have *already* been "constructed" *before* calling `initialize`, otherwise, there would be no object to call `initialize` on! – Jörg W Mittag Oct 13 '19 at 18:01
  • 1
    But now a pragmatic question: how do you distinguish between the two cases? I.e. if I call `MyClass(1, 2)`, what is the logic which decides whether `1` gets bound to `n1` or `n2`? – Jörg W Mittag Oct 13 '19 at 18:06
  • @JörgWMittag Since, as you say, there are only instance methods (because class methods are really just methods of `Class` instances), does it not follow that module / class definition bodies are actually blocks? And, if so, not really a different kind of executable code from a block? – BobRodes Oct 13 '19 at 18:31
  • A block is a *very specific* syntactic thing that can exist *only* in one place: as the last argument to a method call. That is not true for module / class definition bodies. Also, blocks have nested scope and are the *only* construct in Ruby where scope nests. Module / class definitions always create a new scope. Blocks not only have nested scope, but they can become *closures*, i.e. they can refer to their outside lexical environment even after the lifetime of that environment has ended. Module / class definitions can't do that because they don't nest and thus there *is no* outer environment – Jörg W Mittag Oct 13 '19 at 18:43
  • @JörgWMittag Ok, that helps. I've been pondering the alternative syntax `MyClass = Class.new do`, etc. It seemed that `Class.new` actually takes a block argument, which is the definition of all of the class's methods and so on, and that `class MyClass`, etc. is just syntax sugar. I see the definition as the last argument of the `Class.new` method call, and one might say that each method definition is a nested scope (right?). In any case it isn't a closure though, since it doesn't have access to its outside lexical environment. So, not the same indeed. Thanks for your explanation. – BobRodes Oct 14 '19 at 06:18
  • @BobRodes: Yes, `Class.new` takes a block as its argument that is executed in the context of the newly created class. Likewise, `define_method` also takes a block as its argument. But `class` and `def` are not method calls and they don't take arguments, so they are not blocks. "one might say that each method definition is a nested scope" – No. *Only blocks have nested scope*, and a method definition is not a block, therefore, it doesn't have nested scope. – Jörg W Mittag Oct 14 '19 at 06:45

3 Answers3

1

Couldn't you achieve what you want by using keyword arguments:

class MyClass
  attr_reader :num1, :num2, :num3
  def initialize(n1: nil, n2: nil, n3: nil)
    if n2.nil?
      puts 'num2 is nil'
    end
    if n3.nil?
      puts 'num3 is nil'
    end
    @num1 = n1 || 0
    @num2 = n2 || 0
    @num3 = n3 || 0
  end
end

MyClass.new(n1: 1, n2: 3)
# num3 is nil
# => <MyClass:0x0... @num1=1, @num2=3, @num3=0>

MyClass.new(n1: 4, n3: 1)
# num2 is nil
# => <MyClass:0x0... @num1=4, @num2=0, @num3=1>

Keyword arguments are available since ruby 2.0. Google or see for instance here for more information regarding keyword arguments.

If you want to stay close to the MyClass() syntax, you could set it up like this:

class MyClass
  attr_reader :num1, :num2, :num3
  def initialize(n1 = 0, n2 = 0, n3 = 0)
    @num1 = n1
    @num2 = n2
    @num3 = n3
  end
  def self.call(n1: nil, n2: nil, n3: nil)
    if n2.nil?
      puts 'num2 is nil'
    end
    if n3.nil?
      puts 'num3 is nil'
    end
    new n1, n2, n3
  end
end

MyClass.(n1: 1, n2: 3)
# num3 is nil
# => <MyClass:0x0... @num1=1, @num2=3, @num3=nil>
MyClass.(n1: 4, n3: 1)
# num2 is nil
# => <MyClass:0x0... @num1=4, @num2=nil, @num3=1>

Note the '.' after the class name.

Yet another alternative is to use def self.[](...) instead of def self.call(...) and make your calls use MyClass[...] (no dot after class).

  • 1
    Instead of doing `@num1 = n1 || 0` you could also simply set a default to `0` in the parameters instead of using `nil`. `def initialize(n1: 0, n2: 0, n3: 0)` – 3limin4t0r Oct 14 '19 at 11:20
  • @3limin4t0r You could, but that would break it. The initializer wouldn't know the difference between `MyClass.new(n1: 1, n2: 0, n3: 2)` and `MyClass.new(n1: 1, n3: 3)`. Try it and you will see both examples no longer trigger the `num2 is nil` or `num3 is nil`. – Kris Dekeyser Oct 15 '19 at 12:36
  • Yes, but OP might simply want some default options. Specific behaviour for specific combinations might not be in their requirements. – 3limin4t0r Oct 15 '19 at 14:15
0

Ruby semantics about constructors aside (and Jörg's comments about that are precisely correct, as usual), you have said that you want to create a class where you give a value to different attributes in different instances of the class, and not to all of them in any given instance.

You don't have to use "self methods" (Ruby calls them "class methods") to do that, and you certainly don't need "more than one constructor" to do that. Just give a default value of nil to each of them. Pretty similar to what you already have:

def initialize(n1 = nil, n2 = nil, n3 = nil)
  @num1 = n1
  @num2 = n2
  @num3 = n3
end

Now, if you don't set them when you initialize the instance, their value will be nil, and if you need to, you can test for that in your methods.

You haven't really explained the problem that you're trying to solve (you haven't said why you want to have some instances have some attributes and other instances have other ones), so I can't really tell you whether that's a solution to it. But that's the easiest way to do what you say you want to do. If you explain why you want to do it, maybe we can come up with something better.

BobRodes
  • 5,990
  • 2
  • 24
  • 26
0

To dynamic your class constructor, I think you need to dynamic attr_accessor of your instance variables too.
Because we need to know which instance variables are initialized then we should declare attr_accessor inside initialize block.
I already check this and it works well, you can give it a try here:

class MultipleConstructors
  def initialize(args={})
    args.each do |k,v|
      instance_variable_set("@#{k}", v)
      self.class.send(:attr_accessor, *args.keys)
    end
  end
end

And then you can get or set any instance variable after initialize constructor.

m = MultipleConstructors.new(a: 1, c: 4)
m2 = MultipleConstructors.new(d: 'blah', e: 100)
m3 = MultipleConstructors.new(f: true)

or you can initialize any constructor you want ...
ref : https://apidock.com/ruby/Module/attr_accessor

Dac Nguyen
  • 23
  • 1
  • 7