Last updated on June 4th, 2024 at 06:07 pm
Table of Contents
- An example use case
- When to use Procs instead of Lambdas?
- Storing Methods in Variables
- When to use what….
An example use case
At first glance, Procs and Lambdas can appear to be of little use. What do I need a Proc or Lambda for when I can simply use methods? Basically, Procs and Lambdas are useful when you need executable code to be stored in a variable, which cannot be done with a normal block.
One instance where they can be helpful is where we have to read in a CSV file and validate each column. An example of this is shown below.
require 'csv'
def read_and_validate_csv(file_path)
validators = [
->(value) { value.to_s.match?(/\A[-+]?\d+(\.\d+)?\z/) },
->(value) { value.is_a?(String) && !value.strip.empty? }
]
CSV.foreach(file_path, headers: true) do |row|
valid_row = true
row.each_with_index do |(header, value), index|
valid_row &&= validators[index].call(value)
unless valid_row
puts "Invalid data in column #{index + 1} (#{header}) at row #{row.index + 1}: #{value}"
break
end
end
puts "Valid row: #{row.to_h}" if valid_row
end
end
# Example usage:
# read_and_validate_csv('example.csv')
First, the validators are set up
validators = [
->(value) { value.to_s.match?(/\A[-+]?\d+(\.\d+)?\z/) },
->(value) { value.is_a?(String) && !value.strip.empty? }
]
And then references on each row using the call method on the Lambda
valid_row &&= validators[index].call(value)
You could also accomplish the same task using Procs instead of Lambdas
validators = [
Proc.new { |value| value.to_s.match?(/\A[-+]?\d+(\.\d+)?\z/) }, # Validator for the first column (number)
Proc.new { |value| value.is_a?(String) && !value.strip.empty? } # Validator for the second column (string)
]
The execution of the validator would be the same.
valid_row &&= validators[index].call(value)
While Lambdas can be a little faster in execution than Procs, in most cases Lambda would be sufficient.
When to use Procs instead of Lambdas?
One of the key differences between Lambdas and Procs is that a Lambda will strictly enforce the number of parameters it is called with, referred to as Arity, whereas a Proc won’t. For example, the following code will execute without any errors for Procs.
my_proc = Proc.new { |a, b| puts "a: #{a}, b: #{b}" }
my_proc.call(1) # Output: "a: 1, b: "
my_proc.call(1, 2) # Output: "a: 1, b: 2"
my_proc.call(1, 2, 3) # Output: "a: 1, b: 2"
In addition, a Proc can force a non-local return, as in the following example.
def return_with_proc
my_proc = Proc.new { return "Returning from Proc" }
my_proc.call
"Returning from method"
end
puts return_with_proc # Output: "Returning from Proc"
This capability could be used, for example, to force an early return when validating a collection of data, as with the following example.
def check_items(items)
validator = Proc.new do |item|
return "Invalid item found" if item == "invalid"
"Item is valid"
end
items.each do |item|
result = validator.call(item)
puts result
end
"All items processed"
end
items = ["valid", "valid", "invalid", "valid"]
puts check_items(items) # Output: "Invalid item found"
In general, Lambdas are sufficient for the majority of operations and preferred because of their Arity checking and less risk of accidental non-local returns.
Storing Methods in Variables
It is possible, instead of using a Lambda or a Proc, to store a method in a variable and use that.
require 'csv'
class CSVValidator
def initialize(filename)
@filename = filename
end
def read_and_validate_csv
# Read CSV file
CSV.foreach(@filename, headers: true).each_with_index do |row, row_index|
valid_row = true
# Validate each column
row.each_with_index do |(header, value), index|
unless send("validate_column_#{index + 1}", value)
puts "Invalid data in column #{index + 1} (#{header}) at row #{row_index + 1}: #{value}"
valid_row = false
break
end
end
# If all validations pass, process the row (example output)
puts "Valid row: #{row.to_h}" if valid_row
end
end
# Validation method for the first column (number)
def validate_column_1(value)
value.to_s.match?(/\A[-+]?\d+(\.\d+)?\z/)
end
# Validation method for the second column (string)
def validate_column_2(value)
value.is_a?(String) && !value.strip.empty?
end
end
# Example usage:
# validator = CSVValidator.new('example.csv')
# validator.read_and_validate_csv
When to use what….
When would you use a method instead of a Proc or Lambda?
- You have existing methods that you want to reference and call dynamically
- You need to reflect on method metadata or preserve the method’s binding to its original context
- You want to keep the logic encapsulated within the object-oriented design
Use Lambdas and Procs when:
- You need to define anonymous functions
- You are working with higher-order functions and functional programming patterns (yes, Ruby can be used as a functional programming language!)
- You require custom argument handling behavior (strict for lambdas, lenient for Procs)