When Stuck, Reverse It for Clarity

Recently I tried to create a ruby method that formatted a number to a string but comma separating large numbers.

For example: 100000 should result in 100,000 and 352456745 in 352,456,745

My first monstrous attempt was:

def number_to_string(number)
  num_str = number.to_s
  array_str_num = num_str.to_s.split(//)
  result = []
  array_str_num.each_with_index do |value, index|
    result << value
    result << ',' if index % 3 == 0
  end
  p result.join
end

But this returned “1,000,00” for 100000 which is NOT the end result I envisioned.

Then after twenty minutes of frustration, I left my room to quench my thirst. While I was drinking a nice cold beer my subconscious kicked in and I pictured the solution right away. I actually had to reverse the array of stringed numbers and add one to index to place the comma in the position that I wanted.

def number_to_string(number)
  result = []
  # reverse the string and then split it
  number.to_s.reverse.split(//).each_with_index do |value, index|
    result << value
   #add 1 to index to get the comma in the correct position
    result << ',' if (index + 1) % 3 == 0
  end
  result.join('').reverse
end

number_to_string(100000)
#=> 100,000

number_to_string(352456745)
#=> 352,456,745

Yes! It worked now. The code is ugly as fuck but it gave me the expected result. A friend once told me that if I was ever totally stuck at a problem, then I should reverse the data. When I was studying for the SAT for example, one of strategies for vocabulary retention I used was to say a word backwards out loud a few times. I have no idea why this tactic works, but apparently it helps programmers too.

So if you happen to feel hopelessly lost because of a hard problem, try reversing how you are viewing, approaching, or even reading it. Don’t forget that a cold beer on your side might also do the trick.

Thanks to @caffo for referring me to the problem. Hope we can have a cold beer someday.

Creating Your Own attr_reader and attr-writer

Have you ever wondered how you would create your own Ruby attr_reader and attr_writer.

Just so we are on the same page.

class Cat
 attr_reader :name
 attr_writer :name

 def initialize(<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="">name)
  @name</span></span></span></span> = name
 end
end

cat = Cat.new("Tobias")
cat.name #=> "Tobias"

cat.name #= "New Tobias"
cat.name #=> "New Tobias" 

attr_reader in the Cat class above is the same as if we create a method inside of it that the object’s the attribute name.

and attr_writer, as you have guessed but now allows the attribute to be written or assigned

class Cat

 def initialize(name)
  @name = name
 end

 def name
  @name
 end

 def name=(value)
  @name = value
 end
end

Build Your Own

Because attr_reader and attr_writer are pieces of code that creates code, we’ll have to enter the realm of metaprogramming.

For attr_reader, we will create an instance method that return its corresponding instance variable. And for attr_writer, we will code an instance method that assigns a value to an instance variable and returns it.

module Attrs
 
 def my_attr_reader(attr_name)
  define_method(attr_name) do 
   instance_variable_get("@"+attr_name.to_s)
  end
 end

 def my_attr_writer(attr_name)
  define_method("#{attr_name}=") do |value|
   instance_variable_set(“@“+attr_name.to_s, value)
  end
 end
end

In the module above I am using Ruby methods that let me define a method(define_method), get an instance variable(instance_variable_get) and set it(instance_variable_set). Don’t you love Ruby?

class Cat
 extend Attrs

 my_attr_reader :name
 my_attr_writer :name

 def initialize(name)
  @name = name
 end
end

cat = Cat.new("Tobias")
cat.name #=> "Tobias"

cat.name = "New Tobias"
cat.name #=> "New Tobias"

Voilá

Notice that I am using extend rather than include with the module Attrs. This is so my_attr_reader and my_attr_writer can become class methods.

Also, these are defining my_attr_reader and my_attr_writer once. In order to define multiple readers and writers at once like attr_reader and attr_writer you can do

module Attrs
 
def my_attr_reader(*attr_names)
 attr_names.each do |attr_name|
  define_method(attr_name) do
   instance_variable_get("@"+attr_name.to_s)
  end
 end
end

 def my_attr_writer(*attr_names)
  attr_names.each do |attr_name|
   define_method("#{attr_name}=") do |value|
    instance_variable_set("@"+attr_name.to_s, value)
   end
  end
 end
end

*attr_names lets you pass in zero or more attribute names. For each of those, a corresponding method is defined.

In case you are curious, the actual attr_reader and attr_writer in Ruby are implemented with functions written in C. When we simulate this behavior with our own Ruby methods we don’t get the advantage of these C functions.

And that is how you can replicate Ruby attr_reader and attr_writer with metaprogramming.
We used define_method, instance_variable_get, and instance_variable_set to define a method on a caller, get and set an instance variable.

This is just the tip of the metaprogramming iceberg.

Happy metaprogramming!