Seven Languages in Seven Weeks — Ruby (Lessons 1 & 2)

I’ve been quite curious about a number of languages out there in the wild, and have recently found the motivation to explore a lot of them all at once. When I heard about the Seven Languages book, I felt like I had a responsibility to try it out. In fact, before purchasing the book I grabbed an install of Prolog on my Ubuntu machine and played around with it to get into the mood to explore. (Spoiler : Prolog is really weird coming from an OOP background, but utterly sensible form the perspective of a formal logic student). The first language in the book is Ruby, which I have in fact studied a little bit in the past… but that was many years ago. I have distinct memories of “Why’s” guide, and how it was simultaneously brilliant and insane. I think it was a bit too insane, because all the talk about water slides completely lost me at the time… but, I was a mere fraction of the coder then as I am today and now I wonder what the confusion was.

Progress Report

I’m most of the way through the chapter, actually. I have the suspicion that my Ruby is not particularly idiomatic, at least in my initial implementations. However, I quickly found a few places where I could get into the “natural” swing of the language. Here are some of my solutions, and what I was thinking…

If you’ve already read the book, or you’re already conversant in Ruby, there’s probably not much danger for you here. If you haven’t read the book, but you intend to, try to forget you read this entry, or merely skip over the code samples.

The first exercise that made me do a double-take was:

  • Print your name ten times

Was this hard? No. I wanted to be relatively idiomatic to Ruby so my first stab was something like this:

i = 0
while i < 10
  puts 'Horseman'
  i = i + 1
end

How silly of me though. I had forgotten from so many years ago that the really idiomatic way was more like this…

10.times { puts "horseman" }

The rumors you may have heard about Ruby and it’s fun and whimsical functions are well earned.

The real fun though, was in the challenge to

  • Run a Ruby program from a file.
  • Bonus Problem: If you’re feeling the need for a little more, write a program that picks a random number. Let a player guess the number, telling the player whether the guess is too low or too high.

Now that’s something that I could sink my teeth into.

#!/usr/bin/env ruby
 
# In a file called day1.rb...
 
class Randomizer
 
  # Initialize the instance with a randomly created number from 1 - 10
  def initialize(foo="foo")
    @number = rand(10) + 1
  end
 
  # Check the user's input and print out either
  # a helpful message if they're wrong
  # or a congratulation if they're correct.
  # If they are correct, pick a new random 
  # number.
  #
  # In either case, prompt again.
  def check(pNumber)
    puts "Too high" if pNumber > @number
    puts "Too low" if pNumber < @number
 
    if pNumber == @number
      puts "You got it! Let's try again!"
      @number = rand(10) + 1
    end
 
    prompt
  end
 
  # Function to prompt the user for input
  def prompt
    puts "Guess a number... between 1 and 10"
    num = gets().to_i
    check(num)
  end
 
end
 
if __FILE__ == $0
  r = Randomizer.new
  r.prompt
end

If you’re new to Ruby, you might be scratching your head about some of these syntactical choices. For example, you might wonder “What in the world is the ‘if’ conditional doing after the result? Or Why are you just writing ‘prompt’ at the end of the check function? Doesn’t that just reference the prompt function? Don’t you need parens to call it, like this: ‘prompt()’?” The answer simply is that Ruby’s philosophy behind these things is that it tries to be more “human-readable” and at the same time to have you type fewer characters such that you could theoretically use Notepad or gedit and not an IDE. It’s an exercise best left to the individual to judge the degree to which the language succeeds at these goals.

Where this got really interesting (and perhaps problematic) was on day 2. Under the “find” section at the end, the reader is asked

  • How would you translate a hash to an array?
  • Can you iterate through a hash?

Well the answer to the second is “sure”, and the answer to the first was…

 
# Converting an array to a hash...
array = ["foo", "bar", "goo", "moofy"]
hash = {}
puts "Array is #{array}"
puts "Hash is #{hash}"
array.each {|item| hash[array.index(item)] = item}
puts "Array is #{array}"
puts "Hash is #{hash}"
 
# Iteration through a hash...
hash.each {|k,v| puts "Hash item #{k} #{v}"} # k is the key , v is the value
 
# Iteration through an array...
array.each{|v| puts "Array value #{v} resides at index #{array.index(v)}"}
 
# Now hash back to array...
array = [];
# if we don't care about the order...
hash.each{|k,v| array.push(v)}
 
# if we know all the hash keys are Fixnums and want them 
# in numeric order...
hash.each{|k,v| array[k]=v}

Again, that might not be very idiomatic to Ruby (especially not my method of using array.index to find the index of the value v!). If it is, feel free to congratulate me.

Next was this:

  • Print the contents of an array of sixteen numbers, four numbers at a time, using just each.

This was harder (or at least more awkward) than I’d originally given it credit for. I think though, that the author intentionally chose an example that was going to be inelegant to contrast it against the next problem which was

  • Now, do the same with each_slice in Enumerable.
#   Print the contents of an array of sixteen numbers, four numbers at a time, using just each.
 
# Creating the array of numbers...
16.times {|num| array[num] = num} 
 
# Printing, four at a time...
array.each do |item|
  index = array.index(item)
  s = ''
  if(index % 4 == 0) 
   puts array.slice((index..index+3)).to_s # not very neat. I also feel this isn't very idiomatic to ruby.
  end
end
 
############
#   Now, do the same with each_slice in Enumerable.
############
 
puts "using the enumerable each_slice function... "
 
array.each_slice(4) {|i| puts "#{i}"} 
#Wow... that was a lot easier.  Probably more idiomatic to ruby.

After that was the modified Tree class. I’ll admit it, I blew a lot of time on this one and over one very stupid mistake… and indeed it’s a mistake that reminds me again why it is that I generally don’t like weakly typed languages.

#   Modified version of Tree that accepts hashes of hashes...
class Tree2
  attr_accessor :children, :node_name
 
  def initialize(name, children = {})
 
    # is it really this awkward?  It feels like I'm missing something...
    @children = children.clone
    @children.each {|k,v| @children[k] = Tree2.new(k,v)}
    @node_name = name
 
 
  end
 
  def visit_all(&block)
    visit &block
 
    # From the original sample using arrays....
    # children.each {|c| c.visit_all &block}
    # even though this is a hash, |c| forces ruby to treat it as array!!!!!
    # It **COERCES THE HASH TO AN ARRAY EVEN THOUGH THE TWO DO NOT SHARE A 
    # COMMON ANCESTOR**  ARGH!!!!
 
    children.each{|k,v| v.visit_all &block}
  end
 
  def visit(&block)
    block.call self
  end
end
 
tree = Tree2.new("grampy", {"pops"=>{"me"=>{},"bro"=>{}}, "uncle"=>{'cousin1'=>{},'cousin2'=>{}}});

That was maddening. I’m still not at all sure why the value of c inside the code block of visit_all is an Array. It’s verifiable that I passed a hash of hashes. This boggles my mind! When I trace the class.superclass… structure of Hash and Array they don’t inherit from each other and in theory shouldn’t be mutually convertable. Should they? And how is any reasonable person supposed to intuit that a code block attached to what we know for a fact is a hash of hashes will treat the inner hashes as non-hashes depending entirely on whether your code block pipes have one variable or two? Where does one discover this bit of lore aside from banging one’s head against a wall? This does not feel like an “optimization of programmer productivity” that the book claims is a founding principle of the language. I grant you that code blocks are pretty neat, but this particular implementation is a total headache.

But on a happier note, the final challenge

  • Write a simple grep that will print the lines of a file having any occurrences of a phrase anywhere in the line. You will need to do a simple regular expression match and read lines from a file. (This is surprisingly simple in Ruby) If you want, include line numbers.

Sure enough, this really was pretty simple. I just had to do a little digging to find the right class. I was looking for something like Java’s InputStream family and I found it.

# Impmlementing simple grep program that prints lines of a file
# and displays line numbers as well.
 
 
class SimpleGrep
 
  def initialize(filename)
    @io_stream = IO.readlines(filename)    
  end
 
  # Holy cow, that's it?  
  def grep_for(phrase)
    @io_stream.each do |line|
      puts "line #{@io_stream.index(line)} : #{line}" if line.match(phrase)
    end
    # That's virtually a one liner!
  end
 
end
 
puts "begin grep"
grep = SimpleGrep.new("day2.rb") # That should be *THIS* file
grep.grep_for("ARGH")
puts "end grep"

And that brought things right back around to a happy place. And that is where I left things off over the weekend.

So… I’m open for comments and criticisms of my posted code. Do remember that I’m still quite the neophyte with Ruby (and indeed all the languages in the 7 Languages book!) so please try to be constructive.

Tags:

  1. No comments yet.