skip to content

In Ruby, how do Proc#curry and Method#curry decide when to run, and why does currying a method with optional parameters need an explicit arity?

level: seniorimportance: should knowfreq 24%

answer

  1. collects arguments until arity
  2. default arity: required count
  3. curry(n) must fit a lambda
  4. Method#curry is to_proc.curry
  5. lambda-ness is preserved

basics

~20 s

A curried proc collects arguments until it has as many as its arity, then calls the original. The default arity is the required count, so for def cell(value, width = 10) method(:cell).curry runs after one argument; pass curry(2) to wait for width.

solid answer

~40 s

`curry` returns a proc that accumulates arguments across calls; once it holds at least its arity, it calls the original with all of them, otherwise it returns another curried proc. Without an argument the arity is the callable's **required** count, so optional and splat parameters are never waited for: for `def cell(value, width = 10)`, `method(:cell).curry["x"]` runs at once with `width` 10, while `method(:cell).curry(2)` waits for both. `Method#curry` converts the method to a lambda first, so lambda rules apply: `curry(n)` outside the accepted range raises `ArgumentError`, and giving too many arguments at the last step raises too. A curried lambda stays a lambda; currying a plain proc gives a lenient proc that accepts any `n`.

code

ruby · 13 lines
ruby
def cell(value, width = 10) = value.to_s.ljust(width)

method(:cell).curry["x"]          # => "x         ", ran with width 10
waiting = method(:cell).curry(2)  # waits for two arguments
waiting["x"]                      # => a curried lambda
waiting["x"][3]                   # => "x  "
method(:cell).curry(3)
# ArgumentError: wrong number of arguments (given 3, expected 1..2)

add = ->(a, b, c) { a + b + c }.curry
add[1][2]                         # => a curried lambda waiting for c
add[1, 2][3]                      # => 6
add[1][2].lambda?                 # => true

go deeper

for a junior

Recall that curry lets you supply a callable's arguments one call at a time and that it runs once it has enough.

for a middle

Explain that the default arity is the required count and that curry(n) is needed to wait for optional parameters.

for a senior

Know that methods curry as lambdas with range-checked arity, and use currying to build one-argument formatters with the fixed parameters first.

for a principal

Judge whether curried callables help readability in a codebase or whether explicit lambdas state the fixed values more clearly.

## What curry returns **Currying** turns a function of several arguments into a chain of calls that each supply some of them. In Ruby, `Proc#curry` and `Method#curry` return a **curried proc**: 1. Each call adds its arguments to the ones collected so far. 2. If the total is still below the **arity**, it returns another curried proc holding the collected arguments. 3. Once the total reaches the arity, it calls the original callable with all collected arguments and returns the result. Several arguments can be supplied at once: `add.curry[1, 2][3]` and `add.curry[1][2][3]` both call `add` with 1, 2 and 3. ## Where the arity comes from | Call | Arity used | |---|---| | `prc.curry` / `meth.curry` | the number of **required** arguments | | `prc.curry(n)` / `meth.curry(n)` | `n`, checked against the parameters for lambdas and methods | The default is the required count, so **optional and splat parameters are never waited for**. For a formatter such as `def cell(value, width = 10)`: - `method(:cell).curry["x"]` calls `cell("x")` immediately and uses the default width. - `method(:cell).curry(2)["x"]` returns a curried proc; supplying `12` next calls `cell("x", 12)`. The documentation says it directly: the optional arity argument should be supplied when currying methods with variable arguments to decide how many arguments are needed before the method is called. ## Lambdas and methods are strict `Method#curry` is `to_proc.curry`, and `Method#to_proc` returns a lambda, so methods follow lambda rules: - `curry(n)` must lie between the minimum and maximum argument count, otherwise `ArgumentError`: for `cell`, `curry(3)` raises `wrong number of arguments (given 3, expected 1..2)`. - Supplying more arguments than the method takes in the final step raises `ArgumentError` from the call itself. - The curried proc stays a lambda at every step, so `lambda?` is `true`. A **non-lambda proc** accepts any `n` and ignores extra arguments or fills missing ones with `nil`, following ordinary proc leniency. ## A report generator use Currying fixes leading arguments to produce a one-argument formatter: ```ruby def format_cell(width, align, value) align == :right ? value.to_s.rjust(width) : value.to_s.ljust(width) end right12 = method(:format_cell).curry[12][:right] right12.call(9.5) # => " 9.5" ``` The result is a lambda that a generator accepting any `call`-able can use directly. Put the parameters you want to fix **first**, because currying always fills from the left. ## Partial results are reusable Each step returns a new curried proc holding its own copy of the collected arguments, so an intermediate result can branch: - `cell12 = method(:format_cell).curry[12]` fixes the width once. - `cell12[:left]` and `cell12[:right]` are two independent formatters built from it. - Calling one never changes the arguments collected by the other. ## Curry versus a wrapper lambda | Aspect | `method(:format_cell).curry[12][:right]` | `->(v) { format_cell(12, :right, v) }` | |---|---|---| | Shows which parameters are fixed | Only by position | By name at the call | | Can fix a later parameter first | No, currying fills from the left | Yes | | Argument checking | Lambda rules at the final call | Lambda rules | ## Pitfalls - Forgetting that the default arity ignores optional parameters, which makes a curried call run earlier than expected. - Passing a block to a curried proc before it is complete: Ruby warns that the block is not used. - Reaching for `curry` where a plain lambda, such as `->(v) { format_cell(12, :right, v) }`, reads better; the lambda makes the fixed values obvious.

  • What does ->(a, b, c) { a + b + c }.curry[1, 2][3, 4] do?
    It raises `ArgumentError`. The curried lambda collects 1 and 2, then receives 3 and 4, which makes four arguments; it passes all of them to the original lambda, which takes exactly three. A plain proc curried the same way would ignore the extra argument.
  • Is a curried lambda still a lambda?
    Yes. Each intermediate proc copies the lambda flag of the original, so `lambda?` stays `true` and the final call checks arguments strictly. Currying a plain proc produces plain procs that stay lenient.

saying these in an interview costs you the question

  • curry waits for optional parameters by default
  • A curried call with a missing argument raises ArgumentError at once
  • Method#curry returns a Method, not a Proc
  • Any arity can be passed to curry on a method
  • Currying a lambda produces a lenient plain proc