Comment by chao-
The "aesthetically pleasing" aspect of blocks is not mutually exclusive with real, first-class functions! Ruby is really more functional than that. Ruby has both lambas and method objects (pulled from instances). For example, you can write:
let isLarge = a => a>100;
as a lambda and call via #call or the shorthand syntax .(): is_large = ->(a) { a > 100 }
is_large.call(1000)
# => true
is_large.(1000)
# => true
I find the .() syntax a bit odd, so I prefer #call, but that's a personal choice. Either way, it mixes-and-matches nicely with any class that has a #call method, and so it allows nice polymorphic mixtures of lambdas and of objects/instances that have a method named 'call'. Also very useful for injecting behavior (and mocking behavior in tests).Additionally, you can even take a reference to a method off of an object, and pass them around as though they are a callable lambda/block:
class Foo
def bar = 'baz'
end
foo_instance = Foo.new
callable_bar = foo_instance.method(:bar)
callable_bar.call
# => 'baz'
This ability to pull a method off is useful because any method which receives block can also take a "method object" and be passed to any block-receiving method via the "block operator" of '&' (example here is passing an object's method to Array#map as a block): class UpcaseCertainLetters
def initialize(letters_to_upcase)
@letters_to_upcase = letters_to_upcase
end
def format(str)
str.chars.map do |char|
@letters_to_upcase.include?(char) ? char.upcase : char
end.join
end
end
upcase_vowels = UpcaseCertainLetters.new("aeiuo").method(:format)
['foo', 'bar', 'baz'].map(&upcase_vowels)
# => ['fOO', 'bAr', 'bAz']
This '&' operator is the same as the one that lets you call instance methods by converting a symbol of a method name into a block for an instance method on an object: (0..10).map(&:even?)
# => [true, false, true, false, true, false, true, false, true, false, true]
And doing similar, but with a lambda: is_div_five = ->(num) { num % 5 == 0 }
(0..10).map(&is_div_five)
# => [true, false, false, false, false, true, false, false, false, false, true]
That is interesting! I haven't explored Procs much, since I use ruby for a shared codebase at work and I was originally a bit afraid of trying to push unidiomatic ideas in the codebase.
In your experience, is it ok to use Procs for example for extraction of block methods for cleanliness in refactors? or would I hit any major roadblocks if I treated them too much like first-class functions?
Also, is there any particular Rails convention to place collections of useful procs? Or does that go a bit against the general model?