Last active
October 12, 2017 16:18
-
-
Save durrellchamorro/b1513a8eef993856e4c23a7997e6f246 to your computer and use it in GitHub Desktop.
Study of Procs Best Used With the Seeing is Believing Gem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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