Came across an interesting code challenge on Code Wars a while back. It involved defining methods in a way to be able to type mathematical operations verbally, i.e.
one plus one which would return
I pondered over this for a while, and on a flight back from San Francisco came up with a neat solution and even learned a couple interesting things about metaprogramming.
I started with defining
one. I know that I’ll want to potentially pass an argument to
one. If I don’t have an argument, I’ll just want to return the integer
1. Simple enough. I can just give my method definition argument a default value of
nil, check whether I have an operator, and if I don’t, simply return the integer
1 2 3 4 5 6 7
Ok, so what should happen when I get an operator? I decided to find a way to implement
This method is a bit tricky – for it to work, it needs to take an argument that is one of my number methods. And it needs to return a value in such a way that the method taking this operator can do something with the return value.
This brought me back to the if statement in
one. I could write what I needed the number method to do with the expected result of the operator method.
I knew I wouldn’t be able to finagle the
+ method in there. Instead, I decided to play around with
.send(). You can call
.send() on any object, passing it a method to call on the receiver, and an argument to supply to the method. For example
1.send(:+, 1) returns
2. Ruby knows to convert a symbol to a method, so this is basically equivalent to writing
This gave me the answer to what I needed each of my word operator methods to return: Their appropriate operators, and then the return value of the number method passed to the word operator method.
1 2 3
[:+, 1] if I were to call
plus(one). Now I have my operator and number that I can assign to variables using Ruby’s multiple assignment feature:
1 2 3 4 5 6 7 8
So that’s cool. I began defining the same methods for the rest of the numbers to nine. I was duplicating a lot of code – the only thing different about each method was either the operator or the specific number. Wouldn’t it be great if I could define the methods on the fly?
Well that’s easy with Ruby’s metaprogramming capability. To begin, I created two constant hash values,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
I wanted to use Ruby’s
define_method() method, but I was a little confused about a couple things. Specifically, I didn’t know how to define a method that accepted an argument or an argument with a default value. However, with some tinkering, I managed to get it working. Here’s what the solution looked like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
It seems the placeholder variable passed to the code block for
define_method() becomes the argument that gets passed to the method being defined. And then, I didn’t realize you could do this, I can even give the placeholder a default value, as you see in the
|word_operator = nil|. This successfully defines the methods needed, so now I can add, subtract and divide to my heart’s content.
1 2 3 4 5 6 7 8
One thing I noticed is that this solution doesn’t work if you want to chain operations, like
two times three plus six. The order of operations is ignored because the innermost methods are evaluated first. So
two times three plus six returns
18 instead of
12 as would be expected if you followed the order of operations. It does work for chaining addition operations, because it doesn’t matter in which order the numbers are added. It’d be interesting to know if there’s a way to accomplish making the methods aware of order of operations.