We did an exercise I found on this blog post about exercises to learn a language (yeah it was too perfect not to check out). The exercise we did was the one to write a function to calculate the Haar wavelet of an array in numbers. We had to sit and discuss the problem at hand for bit to make sure we understand but after that we were off and stumbling through using RSpec.
Our first impressions of RSpec are that it seems to allow pretty expressive concise tests. We noticed very quickly how RSpec extends a lot of the core language to offer you the ability to do things like this:
it "an odd length array" do lambda { @haar.calculate([8,5,1]) }.should raise_exception(ArgumentError) end
I think you can get the gist of the test just from reading it (which is great) and I'm not going to go into Ruby syntax here. I probably could have came up with between description that flowed better but whatever. I should mention that at first we just tried this:
it "an odd length array" do @haar.calculate([8,5,1]).should raise_exception(ArgumentError) end
This test failed and it took a little bit before we realized what we were doing. Without the lambda wrapping the code to be exercised, we were having the exception thrown before RSpec knew what was suppose to happen. The calculate() method would be executed and throw the exception we were expecting but since the should() method never got executed to setup everything the test would fail. We had to wrap the call to calculate() in a lambda so that the execution was delayed. I'm not very familiar with RSpec, but I'm going to guess that should() call has an interesting implementation.
As for our actual implementation code, we noticed that Ruby has some pretty powerful built in functions and we were able to avoid looping structures and arrowhead code after some googling. We came across some interesting tidbits about the language. There is a function to split an array into several equal sized slices. Since the calculation required us to split up the numbers into pairs this was a huge savings for us. The method was call each_slice() and out code looked like this:
pairs = [] current.each_slice(2) { |a| pairs.push a }
The first attempted left out the parenthesizes around that 2 and we kept getting syntax errors. So it seems that Ruby requires parenthesizes sometimes but in this case (from our understanding) the block (an anonymous function defined in the { }) to be executed for each slice is being run against the result of the each_slice() method call. Leaving out the parenthesizes results in Ruby trying to run that block of code against the 2 which you can't do. I'm not completely sure of this explanation and I haven't had a time to research this, but it's worth mentioning.
The last quirk that I'll mention tonight is the result of a block and using a return statement inside one. We were doing a map of an array to produce an array of pairs but in the block to be executed we first tried a return statement at the end. This resulted in the entire function to be returned not just the block so we ended up having to do this:
avg_co = pairs.map { |pair| wavelet_pair = [] wavelet_pair[0] = (pair[0] + pair[1]) / 2.0 wavelet_pair[1] = pair[0] - wavelet_pair[0] wavelet_pair }.transpose
The last line is just the variable (which is scoped how you would expect, existing only inside the block) we want the value of. We have a line with only it and the return value is what we wanted. That last line was originally 'return wavelet_pair' but like I said it returned out of the whole function which was quite unexpected.
I think that pretty much wraps up all the interesting things we picked up while we stumbled through this code. It felt like a great learning experience for us and I look for to more. All the code for our Code Dojo should make its way up to the github organization page. We also have a Google Group page now. Feel free to take a look at our code on github and discuss any ideas or details of Ruby on our group page.
Looks like the formatting b0rked on that post. We'll probably have to look into moving the blog to another location at some point. Blogger's editor just doesn't seem up to the task of displaying code.
ReplyDeleteMore to the point of the post, I definitely think we had a good first session. Even though it was just the two of us (I wonder if Tony is still running those errands...) we accomplished exactly what we both set out to do. Got environments set up, got comfortable with coding and presenting, jumped right into Ruby and TDD and wrote something that does something.
Keeping small, definable goals like that will be good. It's easy for us to start thinking big about potential projects, but it's those simple goals that are easier to hit in a single session and define when we're "done." We can do complex things over time, made up of many simple things.
Yeah I really enjoyed the session and I hope future meetings follow they same mold. We knew what we wanted, figured out how to say what we wanted in RSpec, figured out how to implement what we wanted in Ruby. When something didn't quite work, we both did some research and shared/discussed what we found. Then we just repeated that until the exercise was done.
ReplyDeleteI look forward to tackling another exercise and I want each meeting to involve at least one exercise that we finish together in that meeting. It should be a great way to get in the habit and mindset of TDD and to hopefully get a deep understanding of Ruby.
When we come up with a project we want to work on I hope that leads us to doing online collaboration together with the meetings still revolving around trying to accomplish something small completely.
I just found some stuff that helps explain the snag we hit with the syntax around that 'each_slice()' call. You have to go to page 8 to get to where they start discussing blocks and iterators but it appears that the 'each_slice()' method we were using is an iterator and iterators accept a block (which has to happen after the parameter list so you have to use parenthesis to denote the end of the list). A block looks like it's basically the lambdas you are probably used to and the iterator method uses a 'yield' keyword to give control to the block. When the block is done it gives control back to the iterator right after the yield statement.
ReplyDeleteChapter: http://media.pragprog.com/titles/ruby/tut_containers.pdf