In Ruby 4.0, how do a proc and a lambda handle missing, extra and single-array arguments when called?
answer
- methods are strict, blocks forgive
- nil for missing, drop extras
- auto-splat needs several parameters
- Ruby 3.2: |a, **k| stops splatting
- Hash#each with a two-parameter lambda
basics
~10 sA lambda checks arguments like a method, raising ArgumentError on any count mismatch. A regular proc fills missing positional parameters with nil, discards extra ones, and splats a single array across several parameters.
solid answer
~40 sA lambda applies method rules: `->(a, b) {}` called with one argument, three arguments, or one two-element array raises `ArgumentError`. A regular proc applies block rules: `proc { |a, b| }` gets `nil` for a missing `b`, silently drops a third argument, and **auto-splats** a lone array so `call([1, 2])` binds `a = 1, b = 2`. Auto-splatting needs more than one parameter, since `proc { |pair| }` receives the array whole, and since Ruby 3.2 a proc with one positional parameter plus `**keywords` no longer splats either. The difference surfaces with hashes: `{email: "x"}.each(&->(k, v) {})` raises `ArgumentError`, because `Hash#each` hands a lambda one `[key, value]` array, while a two-parameter proc destructures it.
code
ruby · 12 linesloose = proc { |value, field| [value, field] }
strict = lambda { |value, field| [value, field] }
loose.call("x") # => ["x", nil]
loose.call("x", :email, 9) # => ["x", :email]
loose.call(["x", :email]) # => ["x", :email] auto-splat
proc { |pair| pair }.call(["x", :email]) # => ["x", :email], whole array
strict.call("x") # ArgumentError (given 1, expected 2)
strict.call(["x", :email]) # ArgumentError (given 1, expected 2)
proc { |a, **opts| a }.call([1, 2]) # => [1, 2] on Ruby 3.2+ (was 1)go deeper
Recall that a lambda raises ArgumentError on a wrong count, while a regular proc fills missing positional parameters with nil and ignores extras.
Explain auto-splatting precisely: several parameters and one array, the one-parameter exception, and the Ruby 3.2 change for a positional plus keywords.
Spot where leniency hides bugs, such as stored rules silently receiving nil, and fix lambda-based iteration over hashes with destructuring.
Decide which callables in an API should fail fast on arity, and document the calling convention so contributors choose lambdas where it matters.
## Two rule sets for one class Every callable block object in Ruby is a `Proc`, but each carries a flag, readable with `Proc#lambda?`, that picks one of two argument-binding rule sets: - **Lambdas** (`lambda { }`, `->() { }`) bind arguments the way a method does: the number of arguments must fit the declared parameters, or Ruby raises `ArgumentError` with a message such as `wrong number of arguments (given 1, expected 2)`. - **Regular procs** (`proc { }`, `Proc.new { }`, captured blocks) bind arguments the way a block does: generously, with no arity error for plain positional parameters. ## The three lenient rules of a regular proc | Call to a two-parameter callable | Regular proc `\|a, b\|` | Lambda `(a, b)` | |---|---|---| | `call(1)` | `a = 1, b = nil` | `ArgumentError` | | `call(1, 2, 3)` | `a = 1, b = 2`, the 3 is dropped | `ArgumentError` | | `call([1, 2])` | `a = 1, b = 2` (auto-splat) | `ArgumentError` | 1. **Missing arguments become `nil`.** Nothing raises; the parameter is simply `nil` inside the body. 2. **Extra arguments are discarded.** The proc never sees them. 3. **A single array is auto-splatted** across the parameters when the proc declares more than one. With only one parameter, `proc { |pair| pair }.call([1, 2])` receives the whole array `[1, 2]`. The leniency covers positional parameters only. A required keyword parameter is enforced in a regular proc as well: `proc { |a:| a }.call` raises `ArgumentError` naming the missing keyword. These rules exist for iteration. `map` or `each` over an array of pairs hands the block one packed array per element, and the block's parameter list names its parts. ## What changed in Ruby 3.0 and 3.2 Auto-splatting interacts with keyword parameters, and Ruby tightened it in two steps: - **Ruby 3.0**: a proc taking a single rest parameter plus keywords, `proc { |*a, **kw| }`, stopped auto-splatting, so `call([1])` gives `a = [[1]]`. - **Ruby 3.2**: a proc taking **one positional parameter plus keywords**, `proc { |a, **k| a }`, stopped auto-splatting too. `call([1, 2])` returns `[1, 2]` on 3.2 and later, where 3.1 returned `1`. Code written against older releases that relied on destructuring into `|row, **opts|` now receives the whole array in `row`. On Ruby 4.0 the 3.2 behaviour is the only one. ## Where the strictness of lambdas shows Lambda-ness stays with the object, including when it is passed with `&` as a block: - `[[1, 2], [3, 4]].map(&->(a, b) { a })` raises `ArgumentError`: `map` yields one array per element, and the lambda refuses to splat it. The proc version returns `[1, 3]`. - `{email: "x"}.each(&->(field, value) { })` raises `ArgumentError`. When the block is a lambda, `Hash#each` yields one `[key, value]` array. A two-parameter regular proc or a literal block destructures that pair instead. - A lambda written `->((field, value)) { }` declares one destructuring parameter and accepts the pair. ## Choosing for a form library Suppose a form library keeps its validation rules as callables and calls each one with `(value, field)`: 1. **Lambdas surface wiring mistakes.** If the library later calls `rule.call(value)` without the field, every lambda raises immediately instead of silently treating `field` as `nil`. 2. **Regular procs hide them.** A proc rule would run with `field = nil`, produce a confusing error message, and pass its tests. 3. **Iterating the rule table needs care.** `rules.each(&->(field, rule) { ... })` fails because of the pair; use a literal block, `each_pair { |field, rule| ... }`, or a lambda with a destructuring parameter. The practical rule: use lambdas where a wrong argument count is a bug you want to hear about, and regular procs or literal blocks where you want Ruby to unpack values for you.
- Why does proc { |pair| pair }.call([1, 2]) not auto-splat?Auto-splatting only happens when the proc declares more than one parameter; with a single parameter the array is itself the one argument, so `pair` is `[1, 2]`. A trailing comma, `proc { |a,| a }`, or a second parameter turns splatting back on, so `a` becomes `1`.
- What did Ruby 3.2 change about auto-splatting with keyword parameters?A proc taking one positional parameter plus `**keywords`, such as `proc { |a, **k| a }`, no longer auto-splats a single array. `call([1, 2])` returns `[1, 2]` since 3.2, where 3.1 and earlier returned `1`. Ruby 3.0 had already stopped splatting for `|*a, **kw|`.
saying these in an interview costs you the question
- A proc raises ArgumentError when a positional argument is missing
- A lambda auto-splats an array just like a block does
- A one-parameter proc splats an array argument into its elements
- Hash#each always splats the pair, even into a lambda
- Procs and lambdas bind arguments identically; only return differs