I love how enumeration can be customized in Ruby ❤️
Did you know you can write your own Enumerator helpers, like `.with_index` and `.with_object`?
I searched the web, and informations on this is relatively scarce.
So here's a few paragraphs to show how useful it can be! ➡️ https://kemenaran.winosx.com/posts/ruby-crafting-enumerator-helpers
Among all the powerful abilities of Ruby Enumerators, one of their most useful usage is to customize what gets enumerated. For instance, by default #each will yield the elements of the enumeration, one by one: array = ["apple", "banana", "grape"] array.each do |value| puts "${value}" end # "apple" # "banana" # "grape" In some cases, however, we may also need the index of the element being enumerated. For this, we can use Enumerator#with_index. It turns an existing enumerator into one that also yields the index: array.each.with_index do |value, index| puts "${index}: ${value}" end # "1: apple" # "2: banana" # "3: grape" The neat thing: this works for any enumerator! For instance, if you’re not enumerating using #each, but rather using #map or #filter, the usage is the same: array.map.with_index do |value, index| "${index}. ${value.uppercase}" end # ["1. APPLE", "2. BANANA", "3. GRAPE"] How to craft your own enumerator helpers Recently, I wanted to enumerate the pixels of an image. The pixels are represented a single-dimensional array of integers: image.pixels # [998367, 251482, 4426993, 777738, ... ] However, in my case, I want to perform different operations depending on the pixel coordinates. Of course, we can compute the coordinates in the loop itself: pixels.map.with_index do |pixel, i| x = i % image.width y = i / image.width pixel * ((x + y) / 100.0) # brighten from top-left to bottom-right end But there has to be a better way. What if we could substitute the enumerator’s .with_index by something like .with_coordinates? First, I needed a quick refresher on how to write a method that enumerates on values. AppSignal’s article on Enumerators is quite a good read there. So, our method just need to yield the values one-by-one, and that’s it? Let’s try this. We’re going to re-open the Enumerator class, and add a #with_coordinates(width, &block) method: class Enumerator def with_coordinates(width, &block) each.with_index do |value, i| x = i % width y = i / width yield value, x, y end end end When called, Enumerator#with_coordinates will invoke its block once for each of the enumerator values - passing the coordinates along. Let’s see how it is used: pixels.map.with_coordinates(image.width) do |pixel, x, y| pixel * ((x + y) / 100.0) # brighten from top-left to bottom-right end The coordinates computation are pushed away from the block, the code is nicer… Good work. Plus #with_coordinates works not only for #each, but for any enumerator – juste like #with_index! Method chaining on enumerators There’s only one caveat though: in Ruby, enumerators support method chaining. That is, instead of passing a block to the enumerator, we can instead call methods on it. Like this: pixels .each .with_index .with_object("filename.png") do |pixel, i, path| puts "Pixel at #{path}:#{i} => #{pixel}" if i = 5 end # "Pixel at filename.png:5 => 1962883" But if we try this with our current implementation of Enumerator#with_coordinates, we get: pixels .each .with_coordinates(width) .with_object("filename.png") do |pixel, x, y, path| puts "Pixel at #{path}:#{x}:#{y} => #{pixel}" if i = 5 end # in `block in with_coordinates': no block given (yield) # (LocalJumpError) Makes sense: our helper yields to a block, but Ruby complains that none was provided. To fix this, we’ll need to return an Enumerator instance when our #with_coordinates function is called without a block. Let’s modify our implementation of Enumerator#with_coordinates: class Enumerator def with_coordinates(width, &block) + if block_given? each.with_index do |value, i| x = i % width y = i / width yield value, x, y end + else + Enumerator.new do |y| + with_coordinates(width, &y) + end end end end And there we have it: using the block-less form will return a new Enumerator. pixels.each.with_coordinates(width) # <#Enumerator: ...> Which means we can properly chain #with_coordinates with further methods now: pixels .each .with_coordinates(width) .with_object("filename.png") do |pixel, x, y, path| puts "Pixel at #{path}:#{x}:#{y} => #{pixel}" if i = 5 end # "Pixel at filename.png:1:1 => 1962883" And that concludes our short side-quest on implementing Enumerator helpers in Ruby. It feels very expressive; and I like how we can make our custom helpers as powerful as the native ones. Happy enumerating!
I have a friend who wants to host a site which has links, embedded videos and an embedded Google form. He’s a guitar teacher so it’s for that.
What is the current meta for hosting, where should I suggest he do it?
In case any of you are interested, I started doing open mic music sessions last year and I put them on YouTube.
I have a small set of friends who subscribe, and my views are normally in the single digits. But apparently Tennessee Whiskey is popular, as I did it last week and it’s suddenly hopped up to 400 views.
I mostly do acoustic guitar and singing, occasionally with a loop pedal.
Have a listen on the channel if you like: https://youtube.com/@fearoffish
Somebody pointed out:
> ed balls
balls: No such file or directory
So instead the correct command is:
> touch balls && ed balls