Skip to content

Instantly share code, notes, and snippets.

@durrellchamorro
Last active October 12, 2017 16:18
Show Gist options
  • Save durrellchamorro/b1513a8eef993856e4c23a7997e6f246 to your computer and use it in GitHub Desktop.
Save durrellchamorro/b1513a8eef993856e4c23a7997e6f246 to your computer and use it in GitHub Desktop.
Study of Procs Best Used With the Seeing is Believing Gem
# Procs are just closures:
def my_method_1
counter = 0 # => 0
proc { counter += 1 } # => #<Proc:0x007fadc9244ec8@/Users/durrellchamorro/code/scratch.rb:4>
end # => :my_method_1
my_proc = my_method_1 # => #<Proc:0x007fadc9244ec8@/Users/durrellchamorro/code/scratch.rb:4>
my_proc.call # => 1
my_proc.call # => 2
my_proc.call # => 3
# counter # ~> NameError: undefined local variable or method `counter' for main:Object
# Procs don't enforce arity, but lambdas do:
# my_lambda = lambda { |n, k| n + k } # => #<Proc:0x007fadc92443b0@/Users/durrellchamorro/code/scratch.rb:15 (lambda)>
# my_lambda.call(1, 1, 3) # ~> ArgumentError: wrong number of arguments (given 3, expected 2)
# my_lambda.call(2, 2, 3) # ~> ArgumentError: wrong number of arguments (given 3, expected 2)
my_proc = proc { |n, k| n + k } # => #<Proc:0x007fadc92441a8@/Users/durrellchamorro/code/scratch.rb:20>
my_proc.call(1, 1, 3, 4, 5, 6) # => 2
# Practical examples:
# csv_string = File.read("path/to/file.csv")
#
# CSV.parse(
# csv_string,
# headers: true,
# header_converters: lambda { |f| f.strip },
# converters: lambda { |f| f ? f.strip : nil }
# ) do |row|
# # do something with the row
# end
# Different ways to *call methods in ruby:
1.to_s # => "1"
1.send(:to_s) # => "1"
"123".gsub('1', '0') # => "023"
"123".send(:gsub, '1', '0') # => "023"
# *Technically you send methods(messages) to objects and you call procs/blocks.
# def foo!(array, index, &block)
# block.call array, index
# end
# my_proc.call(1)
# Instead of:
[1].map { |n| n.to_s } # => ["1"]
# you can use this syntactic sugar:
[1].map(&:to_s) # => ["1"]
[1].map { |obj, *args| obj.send(:to_s, *args) } # => ["1"]
# The Symbol class defines #to_proc like so:
# class Symbol
# def to_proc
# Proc.new { |obj, *args| obj.send(self, *args) }
# end
# end
# This works because in the context of a method call, putting an ampersand in front of the last argument
# tells Ruby to convert whatever is immediately to the right of the ampersand
# to a proc if it isn't a proc already and then use that proc as the method’s block.
# *Technically speaking, Ruby does not consider the ampersand followed by a proc (or in this case a symbol being
# converted to a proc) to be an argument for the method. If it did, then I would get a wrong number of arguments (1 for 0)
# error since #map doesn't take any arguments.
a_proc = :to_s.to_proc # => #<Proc:0x007fadc923e0c8(&:to_s)>
# since a_proc is already a proc, the & doesn't need to call #to_proc on it.
# Instead it will just make sure the proc is used as the method's block:
[1].map(&a_proc) # => ["1"]
# Write your own methods that yield to blocks/procs
def foo!(array, index)
yield array, index # => 1, 1
end # => :foo!
my_array = [1, 2] # => [1, 2]
# Instead of using a proc as the method's block we use a plain old block:
foo!(my_array, 0) { |obj, index| obj.delete_at(index) } # => 1
my_array # => [2]
# Example of a custom method yielding to a proc/block (the & must be in the last argument!):
my_array = [1, 2] # => [1, 2]
foo!(my_array, 0, &:delete_at) # => 1
# The proc created above by the Symbol class:
# Proc.new { |my_array, index| my_array.send(:delete_at, index) }
# Under the hood the proc above is being used as #foo!'s block like this:
# foo!(my_array, 0) { |my_array, index| my_array.send(:delete_at, index) } # => 1
my_array # => [2]
# Using procs to Dry Up Ruby Code (More practical example in my Blog):
def generic_method(array_of_anything, some_proc)
array_of_anything.each(&some_proc) # => ["string is an animal", "cat is an animal", "dog is an animal"]
array_of_anything[0..1].each { |object| yield object } # => ["string is not an animal", "cat is an animal"]
end # => :generic_method
user_input_1 = ["string", "cat", "dog"] # => ["string", "cat", "dog"]
user_input_2 = [1, 2, 3] # => [1, 2, 3]
add_string = proc { |string| string << " is an animal" } # => #<Proc:0x007fadc92365a8@/Users/durrellchamorro/code/scratch.rb:112>
generic_method(user_input_1, add_string) do |string|
string.gsub!("is", "is not") if string[0..5] == "string" # => "string is not an animal", nil
end # => ["string is not an animal", "cat is an animal"]
user_input_1 # => ["string is not an animal", "cat is an animal", "dog is an animal"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment