1

This is possible in Ruby:

class SomeClass
  def initialize(a, *b, c)
  end
end

but this is not:

class SomeClass
  def initialize(a, *b, c='anything here')
  end
end

Why?

Edit: This question does NOT have an answer. In the answer linked, the first answer is:

The splat means "use up all of the remaining arguments" but then you provide an optional argument, so how could the interpreter know if the last argument is part of the "numbers" splat or the optional "options"?

Which is NOT a valid answer because according to this logic, Ruby would also not allow mandatory parameters after a splat.

daremkd
  • 8,244
  • 6
  • 40
  • 66
  • It's an interesting question. I would assume it's because the splat could already contain all your optional variables. Plus you can pass the optional parameter before. So I don't really see how this could be an issue. Still interested in official explanation if such a thing exists :P – LukeS Nov 21 '14 at 00:27
  • In the latter case: `SomeClass.new(1, 2)` => `b == [2], c == 'anything else'` or `b == [], c == 2`? – Victor Moroz Nov 21 '14 at 02:33
  • If you put default parameter after a splat, there will be no way to know when you want to override the default and when you don't because the last argument can always belong to the splat – Vu Minh Tan Nov 21 '14 at 03:12
  • http://stackoverflow.com/questions/17173034/optional-argument-after-splat-argument – Jin Nov 21 '14 at 11:12
  • The question linked DOES NOT have the answer needed there, see my edit. – daremkd Nov 21 '14 at 12:56
  • @daremkd This question has been answered both in http://stackoverflow.com/q/17173034/123527 and http://stackoverflow.com/q/13960678/123527, either in the accepted answer or in one of the other answers. The short answer is **you can't**, the long answer is explained in the links I just posted. One of the links I posted uses as an example `options = {}`, a parameter with a default value, which is the same case you mention. – Simone Carletti Nov 21 '14 at 14:20

1 Answers1

1

Ruby's argument binding semantics are already pretty complex. Consider this method:

def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4, 
          ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
  local_variables.map {|var| [var, eval(var.to_s)] }.to_h
end

method(:foo).arity
# => -5

method(:foo).parameters
# => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat], 
#     [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2], 
#     [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]

Can you tell at first glance what the result of the following invocations will be?

foo(1, 2, 3, 4)

foo(1, 2, 3, mk1: 4, mk2: 5)

foo(1, 2, 3, 4, mk1: 5, mk2: 6)

foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)

foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)

foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)

foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)

foo(1, 2, 3, 4, 5, 6, 7, 8, 
      ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end

Now, imagine adding optional parameters with default arguments after the splat parameter to that list. It's not impossible to find sane semantics for that, but it may lead to some non-obvious results.

Can you come up with simple, sane, backwards-compatible, and non-surprising semantics?

BTW: here's the cheatsheet for the method at the top:

foo(1, 2, 3, 4)
# ArgumentError: missing keywords: mk1, mk2

foo(1, 2, 3, mk1: 4, mk2: 5)
# ArgumentError: wrong number of arguments (3 for 4+)

foo(1, 2, 3, 4, mk1: 5, mk2: 6)
# => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4, 
#      ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
# => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5, 
#      ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6, 
#      ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7, 
#      ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
#      blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, 
      ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8, 
#      ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14}, 
#      blk: #<Proc:0xdeadbeefc00l42@(irb):15> }
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653