ruby.onl / dark-arts

Proc#curry: Build Specialized Functions from General Ones

2026-03-03

Proc#curry lets you pre-fill arguments to a lambda, creating a specialized version. Where Perl uses closures that capture variables, Ruby gives you curry for explicit, composable partial application. Define a generic function once, then stamp out specialized versions on demand.

Part 1: Basic Currying

# Generic function with two parameters multiply = ->(a, b) { a * b } # Pre-fill the first argument double = multiply.curry[2] # a is now always 2 triple = multiply.curry[3] # a is now always 3 double.call(5) # => 10 (2 * 5) triple.call(5) # => 15 (3 * 5)

Part 2: Day-of-Week Checkers

# One lambda, many specialized versions is_day_of_week = ->(day_num, date) { date.wday == day_num } is_sunday = is_day_of_week.curry[0] is_monday = is_day_of_week.curry[1] is_saturday = is_day_of_week.curry[6] is_saturday.call(Time.now) # => true/false

Part 3: Combining with Case Statements

Since Proc#=== calls the proc, curried procs work directly in case/when:
case Time.now when is_saturday then puts "Saturday!" when is_sunday then puts "Sunday!" else puts "Weekday" end
This is beautiful. You're using functions as pattern matchers. No dedicated matcher class, no DSL. Just procs and ===.

Part 4: Curried Grep

# Generic pattern matcher grep_pattern = ->(pattern, line) { line =~ pattern } # Specialized matchers grep_error = grep_pattern.curry[%r~ERROR~i] grep_warning = grep_pattern.curry[%r~WARN~i] grep_ip = grep_pattern.curry[%r~\d+\.\d+\.\d+\.\d+~] # Use with select error_lines = lines.select(&grep_error) warning_lines = lines.select(&grep_warning)
Define your patterns once, reuse them everywhere. The & operator converts the curried proc into a block, so it plugs right into select, reject, map, and friends.

Part 5: Curried Formatting

# Generic formatter format_field = ->(width, align, value) { align == :left ? "%-#{width}s" % value : "%#{width}s" % value } # Specialized formatters format_name = format_field.curry[20][:left] format_count = format_field.curry[8][:right] puts format_name.call("hostname") + format_count.call(42) # => "hostname 42"

Part 6: Perl Comparison

# Perl closure approach: sub make_multiplier { my $factor = shift; return sub { $factor * shift }; } my $double = make_multiplier(2); print $double->(5); # 10
# Ruby curry approach: multiply = ->(a, b) { a * b } double = multiply.curry[2] puts double.call(5) # 10
Ruby's curry is more composable because you can partially apply any number of arguments incrementally.

Part 7: How Curry Works

When you call .curry on a proc with N parameters:
add = ->(a, b, c) { a + b + c } curried = add.curry curried[1] # returns proc waiting for b and c curried[1][2] # returns proc waiting for c curried[1][2][3] # => 6 (all arguments provided, calls proc)
It's like filling in a form one field at a time. Each field you fill returns a new form with fewer blanks. When the last blank is filled, the form processes.

Created By: Wildcard Wizard. Copyright 2026