Archive for the ‘Ruby’ Category

Seven Languages in Seven Weeks – Ruby (Final Day, Lesson 3)

And so it is that I’ve finished the Ruby chapter of the Seven Languages book. The final challenge to your skills and sanity in the Ruby chapter of “Seven Languages in Seven Weeks” is as follows:

  • Modify the CSV application (demonstrated as an example earlier in the chapter) to support a each method to return a CsvRow object (not demonstrated earlier). Use the method_missing on that CsvRow to return the value for the column for a given heading. For example, for the file:

    one, two
    lions, tigers

    Allow an API that works like this:
    csv = RubyCsv.new
    csv.each{|row| puts row.one}

    This should print “lions”

Well…

It took me a lot of futzing around with this before I got it working, and the reason was strikingly similar to what was happening before, except that it was in the opposite direction. Whereas yesterday my problem was an unwanted implicit coercion from Hash to Array, today’s problem was that Ruby required an absolutely explicit coersion into String from values that should by rights have already been strings.

# Do :
#  Modify the CSV application to support an each
#  method to return a CsvRow object.  Use method_missing
#  on that CsvRow to return the value for the column
#  for a given heading.  For example, for the file:
#
#  one, two
#  lions, tigers
#
#  allow an API that works like this:
#  csv = RubyCsv.new
#  csv.each{|row| puts row.one}
#  this should print "lions"
 
class CsvRow
 
  def method_missing name, *args
 
    num = nil
    i = 0
    while i < @headers.length
      # puts "testing #{name.to_s} vs #{@headers[i].to_s} is #{name.to_s.eql?(@headers[i].to_s)}"
 
      # You mean to tell me that Ruby doesn't automagically coerce correctly to string here?
      # I have to do that myself?
      num = i if name.to_s == @headers[i].to_s
      i = i + 1
 
    end
 
 
    # This feels ugly.
    # I am sure there's a
    # prettier way to return 
    # the value, but I'm
    # drawing a blank.
 
    if num.nil?
      nil #Don't crash by accessing an array location with nil!
    else
      @row[num]
    end
 
  end
 
 
 
  attr_accessor :row
 
  def initialize(row_array, headers)
    # puts " row init #{row_array}"
    @row = row_array
    @headers = headers
  end
end
 
module ActsAsCsv
 
  def self.included(base)
    base.extend ClassMethods
  end
 
  module ClassMethods
    def acts_as_csv
      include InstanceMethods
    end
  end
 
  module InstanceMethods
    def read
      @csv_contents = []
      filename = self.class.to_s.downcase + '.txt'
      file = File.new(filename)
      @headers = file.gets.chomp.split(', ')
 
      file.each do |row|
        puts "initing with row #{row}"
        @csv_contents.push( CsvRow.new(row.chomp.split(', '), @headers) )
      end
    end
 
    def each(&block)
      @csv_contents.each do |row| 
        block.call row
      end
    end
 
    attr_accessor :headers, :csv_contents
 
    def initialize
      read
    end
  end
 
end
 
class RubyCsv #no inheritance.  mixing it in.
  include ActsAsCsv
  acts_as_csv
end
 
m = RubyCsv.new
m.each {|row| puts " col one #{row.one}"}
m.each {|row| puts " col two #{row.two}"}
m.each {|row| puts " col blah #{row.blah}"} 
# as a bonus, the program won't crash if you pass an invalid column header.

I feel conflicted still. Ruby seems to want to allow you to write everything as implicitly as possible. This is nice at times, right up until you run into a situation where the implicit action carried out by the machine is completely unexpected. I have to say it also feels incredibly weird and confining to have the “last statemet” executed in a method be its return value. That’s how you end up with really awkward constructs like the return statement at the end of the overridden method_missing method.

Those of you who don’t have the book probably don’t have a reference for the amount of modification I had to make to the ActsAsCsv module. The effort was very small. It really just consisted of writing that def each(&block) method and figuring out how to apply it to all the rows in the csv_contents, and chaging the read function to pass along the headers to the CsvRow instance.

I have to say that the idea of modules and mixins is very attractive. It’s certainly a more attractive solution to the problem of extending classes at runtime with kludges like Decorators, though I feel like the particular example listed might be something that you’d simply plan for with composition in ActionScript rather than try to work in as an inheritance-based solution. Still though, this is an area in which AS3 and Java are both very weak by comparison.

The author of the book makes no secret that Ruby is his favorite language, and that he came from a Java background. His feeling is that Ruby made it fun to program again after years of fighting against Java’s restrictive structure. This isn’t the first person I’ve heard this from. Indeed, I have a suspicion that the current prevailing trend towards completely dynamic and weakly typed languages is driven by a collective desire to stop speaking in Java’s courtly etiquette, and that because Java is onerous that all strong and static type systems must be.

Those of us from the Flash world know that this is not so, and that a balance can certainly be achieved, if only there were interest in finding it.

Tags:

4 Comments


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:

No Comments