In Ruby, given `def log(level, prefix = "app", *messages, suffix)`, how does the call `log(:info, "a", "b")` bind its three arguments?
answer
- required slots fill first
- both ends before the middle
- optionals left to right
- rest takes whatever remains
- Style/OptionalArguments
basics
~20 slevel gets :info and the trailing required suffix gets "b"; the one argument left fills prefix with "a", and messages is []. Ruby fills required parameters at both ends first, then optionals left to right, then the rest.
solid answer
~40 sRuby binds positional arguments in a fixed order. First it reserves enough arguments for the **required parameters at both ends**: leading ones from the left (`level = :info`) and trailing ones from the right (`suffix = "b"`). Whatever remains fills **optional parameters left to right** (`prefix = "a"`), and only then does the **rest parameter** take what is still left, here nothing, so `messages` is `[]`. With `log(:info, "b")` the result is `prefix = "app"` and `suffix = "b"`, and `log(:info)` raises `ArgumentError` (given 1, expected 2+). Optional parameters must be contiguous and cannot follow the rest parameter; both are `SyntaxError`s. RuboCop's `Style/OptionalArguments` also flags optionals that are followed by required ones.
code
ruby · 9 linesdef log(level, prefix = "app", *messages, suffix)
[level, prefix, messages, suffix]
end
log(:info, "b") # => [:info, "app", [], "b"]
log(:info, "a", "b") # => [:info, "a", [], "b"]
log(:info, "p", "m1", "m2", "s") # => [:info, "p", ["m1", "m2"], "s"]
log(:info)
# ArgumentError: wrong number of arguments (given 1, expected 2+)go deeper
Recall the allowed order: required, optional, rest, trailing required, then keywords and the block.
Walk through the binding algorithm on a concrete call, showing that required parameters at both ends are served before optionals and the rest.
Flag trailing required and misplaced optional parameters in review, since adding one silently rebinds existing calls, and prefer keyword parameters for optional settings.
Decide whether signatures with post-required parameters belong in public APIs at all, weighing concise calls against readability and the cost of later signature changes.
## The order Ruby allows in a parameter list A Ruby method's parameters must appear in this order, each group optional: 1. **Leading required** parameters: `level`. 2. **Optional** parameters with defaults: `prefix = "app"`. 3. One **rest** parameter: `*messages`. 4. **Trailing required** parameters, also called post parameters: `suffix`. 5. Keyword parameters, a keyword rest `**opts`, and finally a block parameter `&block`. The parser enforces the shape: - Optional parameters must be **grouped together**; `def m(a = 1, b, c = 1)` is a `SyntaxError`. - An optional parameter cannot come **after** the rest parameter; `def m(*r, a = 1)` is a `SyntaxError`. - Only one rest parameter is allowed. ## The binding algorithm Given `n` positional arguments, Ruby binds them like this: 1. Count the required parameters, leading plus trailing. If `n` is smaller, raise `ArgumentError`. 2. Bind the **leading required** parameters from the front of the argument list. 3. Bind the **trailing required** parameters from the back. 4. Hand the arguments still in the middle to the **optional** parameters, left to right, until either runs out. Optionals that get nothing evaluate their defaults. 5. Anything still left goes into the **rest** parameter as an Array. With no rest parameter, leftovers mean `ArgumentError`. ## Worked calls For `def log(level, prefix = "app", *messages, suffix)`: | Call | `level` | `prefix` | `messages` | `suffix` | |---|---|---|---|---| | `log(:info, "b")` | `:info` | `"app"` | `[]` | `"b"` | | `log(:info, "a", "b")` | `:info` | `"a"` | `[]` | `"b"` | | `log(:info, "p", "m1", "m2", "s")` | `:info` | `"p"` | `["m1", "m2"]` | `"s"` | | `log(:info)` | `ArgumentError` (given 1, expected 2+) | | | | The second row is the interview trap: reading left to right suggests `prefix = "a"` and `messages = ["b"]`, but the trailing required `suffix` claims `"b"` before the rest parameter sees anything. Ruby's own documentation shows the same rule with `def add_values(a = 1, b = 2, c)`: a single argument goes to `c`, and the optionals keep their defaults. ## Why trailing required parameters are a maintenance risk Trailing required parameters are legal and occasionally natural, as in `def wrap(open, *parts, close)`, but they make call sites harder to read: - A reader cannot tell which argument lands where without recalling the algorithm. - **Adding** a trailing required parameter to an existing method silently changes how old calls bind: the last argument moves from an optional or the rest into the new parameter. - Optionals placed before required ones are only filled when the caller passes more arguments than the required count, which surprises people who expect left-to-right filling. RuboCop's `Style/OptionalArguments`, enabled by default, reports optional arguments that do not come at the end of the list. The cop is marked unsafe, because changing a method signature changes behaviour at every call site. ## Reading a signature quickly In an interview or a review, a short procedure answers any binding question: 1. Count the required parameters, leading and trailing; that is the minimum the method accepts. 2. Add the optional parameters; without a rest parameter, that sum is the maximum, and with one there is no maximum. 3. Take the call's argument count, subtract the required ones, and hand the difference to the optionals from the left. 4. Whatever is still left over goes to the rest parameter, or raises `ArgumentError` if there is none. The same minimum and maximum appear in the `ArgumentError` message, as `2`, `1..3` or `2+`, so this procedure also explains every arity error the method can raise. ## Practical guidance - Put required parameters first, optionals after them, and the rest parameter last among positionals. - When a method needs several optional settings, keyword parameters, a separate topic, name them at the call site and avoid positional guessing altogether. - When reviewing a signature with a trailing required parameter, write out the table above for two or three realistic calls before approving it.
- In Ruby, with `def add_values(a = 1, b = 2, c)`, how does `add_values(5)` bind?`c` gets `5`, and `a` and `b` keep their defaults `1` and `2`. Required parameters are satisfied first, even when they come last, so a single argument never reaches the optionals. `add_values(5, 6)` would bind `a = 5` and `c = 6`.
- What goes wrong when a Ruby method gains a new trailing required parameter after `*messages`?Every existing call is reinterpreted: the last argument that used to join `messages` now binds to the new parameter, and calls with too few arguments start raising `ArgumentError`. Nothing warns at the call sites, so the change needs a search of all callers.
Seating a group in a row with reserved end seats: the end seats are filled first, then the optional seats beside the left end in order, and only the people still standing go on the bench in the middle.
saying these in an interview costs you the question
- Positional arguments always bind strictly left to right.
- A rest parameter takes everything after the optionals, even the last argument.
- Optional parameters can be interleaved with required ones anywhere.
- An optional parameter may follow the rest parameter.
- Missing trailing required parameters are set to nil.