DTSCode
DTSCode

Reputation: 1100

Instance methods with default and variable length arguments

I am trying to create a method that can be called in the following manner:

instance.respond('some command', 'command arg 1', 'command arg 2', 'command arg 3')
instance.respond(true, 'some command', 'command arg 1', 'command arg 2', 'command arg 3')
instance.respond(false, 'some command', 'command arg 1', 'command arg 2', 'command arg 3')

The true/false values indicate if the command should be sent immediately or buffered, respectively.

I tried to define the method as in the following example:

class Foo
    def bar1(arg1=true, arg2)
        puts 'this works'
    end

    def bar2(arg1, *args)
        puts 'this works as well'
    end

    def bar3(arg1=true, arg2, *args)
        puts 'but this fails'
    end
end

Running this gives the error:

/opt/dl/testcase.rb:10: syntax error, unexpected *
    def bar3(arg1=true, arg2, *args)
                               ^
/opt/dl/testcase.rb:13: syntax error, unexpected keyword_end, expecting end-of-input

Is there a way to resolve this, or to work around it?

Upvotes: 2

Views: 67

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110755

@simplaY has shown how named arguments can be used here. One has a few options if versions of Ruby prior to 2.0 (when named arguments made their debut) must be supported.

#1

def respond(*args)
  flag = true # default
  flag = args.shift if [true, false].include? args.first
  puts "flag = #{flag}"
  puts "args = #{args}"
  if flag
    # code
  else
    # code
  end
end

respond "dog", "cat"
flag = true
args = ["dog", "cat"]

respond false, "dog", "cat"
flag = false
args = ["dog", "cat"]

This of course requires that the permitted value of the first argument is true or false if and only if it is the value of flag.

#2

def respond(flag=true, args)
  if flag
    # code
  else
    # code
  end
end

called

respond ["dog", "cat"]
respond true, ["dog", "cat"]
respond true, ["dog", "cat"]

Whenever a method has a variable number of arguments the rule is that it will be unambiguous to Ruby if and only if it is unambiguous to you.

#3

def respond(options)
  flag = options.fetch(:flag, true)
  if flag
    # code
  else
    # code
  end
end

called

respond(arg1: 'dog', arg2: 'cat')
respond(flag: false, arg1: 'dog', arg2: 'cat')

This last approach, which was widely used prior to Ruby 2.0, has been largely supplanted by the use named arguments, in part because named arguments display the keys whose values are being passed, whereas in this last approach only the hash is identified among the method's arguments.

Upvotes: 1

simplaY
simplaY

Reputation: 161

Since you haven't specified which ruby version you are using, I'm going to assume that a version >= 2 is fine. If this question is just about fixing the syntax error, then you could write something like

class A
  def respond(flag = true, b = '', *args)
    "flag=#{flag}, b=#{b} args=#{args.join(',')}"
  end
end

However, there are some issues you'd encounter:

A.new.respond('some command', 'command arg 1', 'command arg 2', 'command arg 3')
#=> flag=some command, b=command arg 1 args=command arg 2,command arg 3

A.new.respond(true, 'some command', 'command arg 1', 'command arg 2', 'command arg 3')
#=> flag=true, b=some command args=command arg 1,command arg 2,command arg 3

A.new.respond(false, 'some command', 'command arg 1', 'command arg 2', 'command arg 3')
#=> flag=false, b=some command args=command arg 1,command arg 2,command arg 3

An approach that would make use of named arguments would be safer. You could for example do something like

class B
  def respond(flag: true, b:, **args)
    "flag=#{flag}, b=#{b} args=#{args.values.join(',')}"
  end
end

args = {
  c1: "command arg 1",
  c2: "command arg 2",
  c3: "command arg 3"
}

B.new.respond(b: 'some command', **args)
#=> flag=true, b=some command args=command arg 1,command arg 2,command arg 3

B.new.respond(flag: true, b: 'some command', **args)
#=> flag=true, b=some command args=command arg 1,command arg 2,command arg 3

B.new.respond(flag: false, b: 'some command', **args)
#=> flag=false, b=some command args=command arg 1,command arg 2,command arg 3

Upvotes: 3

Related Questions