Mr. Kennedy Mr. Kennedy - 1 month ago 5
Ruby Question

Manipulating Ruby class default values

When there is more than one default value, how could I change only the second initialization variable without also calling the first?

For example, a Ruby class is created to return the value akin to the roll of a single die with default values for a six sided die ranging from 1 to 6:

class Die

def initialize(min=1, max=6)
@min = min
@max = max
end

def roll
rand(@min..@max)
end
end


If I wanted instead to use this code to simulate the return from rolling a 20 sided die, I could write the following:

p Die.new(min=1, max=20).roll


...but is there a way to argue only the second (max) value?

Of note - and this is where I am confused (I don't fully understand Ruby class attributes and variable scopes) - if I invoke:

p Die.new(max=20).roll


... I get
nil
printed. ?. (I understand that this is because
rand(20..6)
returns
nil
, but I thought that
max=
would retain the default min value for the first argument - instead
max=20
gets ingested as the integer
20
binding to the
min=
... This seems weird to me.)

I suppose I could re-work the Die class to take a default value of the number of sides and also set the min (or max) value relative to the number of sides, but this is beside the point of my main question: How to override only the second default value without explicitly writing the first as well...

Presuming that most dice would normally have a minimum value of 1, I realize that I could reverse the order of min and max like so:

class Die2

def initialize(max=6, min=1)
@max = max
@min = min
end

def roll
rand(@min..@max)
end
end


...and then invoke whatever maximum number of sides like so:

p Die2.new(20).roll


...but given the syntax of
class Die
(and my inclination to write the minimum before the maximum) is there a way to only enter an argument for the second value? Or, perhaps I am approaching Ruby classes poorly? Any help or guidance is appreciated - thanks!

Answer

If you write

class Die
  def initialize(min=1, max=6)
    @min, @max = min, max
  end
end

and create a new instance by passing a single argument, such as:

die = Die.new(3)
  #=> #<Die:0x007fcc6902a700 @min=3, @max=6>

we can see from the return value that the argument 3 has been assigned to @min and @max gets its default value. In short, to pass a value to @max you must also pass one to @min (unless, of course, you reverse the order of the arguments).

You can do what you want by using named arguments (or named parameters), introduced in Ruby v2.0.

class Die
  def initialize(min: 1, max: 6)
    @min, @max = min, max
  end
end

die = Die.new(max: 3)
  #=> #<Die:0x007fcc698ccc00 @min=1, @max=3>

(or die = Die.new(:max=>3). As you see, @min equals its default value and @max equals the argument that is passed, 3.

Default values were required for keyword arguments in Ruby v2.0, but v2.1 extended their functionality to permit required named arguments as well. See, for example, this article.

Lastly, consider the following two cases (the second being the more interesting).

class Die
  def initialize(min=1, max: 6)
    @min, @max = min, max
  end
end
die = Die.new(max: 3)
  #=> #<Die:0x007fcc69954448 @min=1, @max=3>

class Die
  def initialize(min, max: 6)
    @min, @max = min, max
  end
end
die = Die.new(max: 3)
  #=> #<Die:0x007fa01b900930 @min={:max=>3}, @max=6> 
Comments