4
def method(a, b='a', c, *d)
  [a,b,c, d]
end

p method(1,2,3,4)

doesn't work, I don't understand why, if we remove the b parameter all works well. The syntax rules say that you can put params with default values before the splat parameter.

daremkd
  • 8,244
  • 6
  • 40
  • 66

3 Answers3

4

Variables with defaults and a splat variable can exist and coexist as long as variables with defaults can be interpreted as the initial elements of the (one and only) splat.

As a result, these will all work:

def good(a = :a, b);      [a,b];    end
def good(a = :a, *b);     [a,b];    end
def good(a, b = :b, *c);  [a,b,c];  end

But these won't:

def bad(*a, b = :b);          [a,b];    end  # default after the splat
def bad(a = :a, b, c = :c);   [a,b,c];  end  # parsing would need two splats
def bad(a = :a, b, *c);       [a,b,c];  end  # parsing would need two splats
def bad(*a, b = :b, c);       [a,b,c];  end  # default after the splat

(Tbh, I have no idea what implementation details prevent Ruby from accepting defaults immediately after a splat provided there is no ambiguity. But I'm guessing it's to avoid looping twice over the splat instead of a single time, i.e. performance, and that there might be additional reasons that may have something to do with the way Ruby computes a method's arity.)

Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
  • Denis, re your parenthetical comment, can there not be ambiguity when a default follows a splat? – Cary Swoveland Dec 27 '13 at 18:53
  • I'm pretty sure it would work fine in theory, with a different implementation. The problem, I think, is that Ruby's internals treat variables with defaults the same way as it treats the beginning of a splat. Meaning that if it runs into `*a, b = :b`, it's processing that internally calling some C function which basically amounts to `scan_splat_args()` upon encountering `*a`; and again upon encountering `b = :b`. This in terms raises an error because you cannot have two splats. And the underlying rational, I suspect, amounts to performance reasons, since doing so would avoid look-aheads. – Denis de Bernardy Dec 27 '13 at 18:56
  • Denis, can you give an example where a call to `method(*a, b = :b)` is unambiguous? I thought the reason is as simple as that. – Cary Swoveland Dec 27 '13 at 19:13
  • `method(1)`, `method(1,2)` and `method(1,2,3)` with `method(a = :a, *b, c = :c)` would be unambiguous if we populate all defaults before the splat, and do so from left to right. But it would be harder and slower to parse. (I wouldn't be surprised more than that if Ruby ever supports it, though.) – Denis de Bernardy Dec 27 '13 at 19:18
  • I finally get your point, but I could see how that behavior could lead to confusion and resilient bugs. In any event, in a few minutes I will remove my comments as non-constructive clutter. – Cary Swoveland Dec 27 '13 at 19:37
3

Default parameters (b) should come after positional parameters (a, c):

def method(a, c, b='a', *d)
  [a,b,c, d]
end
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • 3
    Then why does this code work: def method(a='a', b) [a, b] end p method(1,2) – daremkd Dec 27 '13 at 13:18
  • @daremarkovic, That was not allowed in *Ruby 1.8*. In Ruby 1.9.2(?)+, it is equivalent to `def method(b, a='a') [a, b] end`. You can confirm this by calling `p method(1)`. – falsetru Dec 27 '13 at 13:30
  • 1
    I see, not sure about the equivalency, although it makes total sense, do you have some link to read more about it, can't find any where someone says this. – daremkd Dec 27 '13 at 13:38
  • @daremarkovic, See http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#Default_Values – falsetru Dec 27 '13 at 13:40
0

There is something going on under the hood:

def method(a=7, b, *c); end
SyntaxError: (irb):25: syntax error, unexpected *
 def method(a=7, b, *c); end
                     ^
def method(*b, *c); end
SyntaxError: (irb):26: syntax error, unexpected *
 def method(*b, *c); end
                 ^  

Notice that is the same error. When you place a param with a default value anywhere other than the end of the fixed parameter list, I expect that internally Ruby is using the same structure to represent that as it uses for the splat (the Ruby C API method for this is rb_scan_args) . Therefore, you cannot have mid-position default values due to the same limitations placed on multiple splats.

AFAICS, this is not a technical limitation of the language, but an implementation detail - clever handling of defaults in non-ending positions in Ruby is re-using the data structures and logic used for splats. The splats have to be limited in that way (because two splats are ambiguous), but it is in theory possible to have a scheme that supports the defaults in your question. Probably though it is an unusual enough requirement that you won't see it done in near future. I would suggest simply moving your named optional params to just before the splat param.

Neil Slater
  • 26,512
  • 6
  • 76
  • 94
  • 1
    To show your point more clearly, you don't need the method definition body, and you also don't need to call the methods. – sawa Dec 27 '13 at 13:48
  • @sawa: I don't understand "is grammatical"? It is logically consistent to support both `method(a=7, b, *c)` and `method(a, b=7, *c)`, the same expansion and allocation of params to fixed, optional, and splat operators could take place. However, Ruby only supports the second syntax. – Neil Slater Dec 27 '13 at 13:48
  • In other words, "valid". But I overlooked your conditioning `anywhere other than the end of the fixed parameter list`, so I removed the previous comment. – sawa Dec 27 '13 at 13:48