Gray Soft

The programming blog of James Edward Gray II (JEG2).
  • 31

    JAN
    2006

    Iterators (Chapters 4 and 5)

    Due to a printing error, these two chapters actually came out longer than intended. Originally their contents were: "Use Ruby."

    All jokes aside, there's really not a whole lot for me to talk about from these chapters, since iterators are so internal to Ruby. Readers from our camp should run into a lot less surprises here that the intended audience. Just translate MDJ's anonymous subroutines to blocks, replace his returns with yields, and you are 90% of the way there.

    Here are translations for some of the examples in these chapters. I think these all come out cleaner and more natural in Ruby, but you be the judge:

    Permutations

    #!/usr/local/bin/ruby -w
    
    def permute(items)
      0.upto(1.0/0.0) do |count|
        pattern = count_to_pattern(count, items.size) or break
        puts "Pattern #{pattern.join(' ')}:" if $DEBUG
        yield(pattern_to_permutation(pattern, items.dup))
      end
    end
    
    def pattern_to_permutation(pattern, items)
      pattern.inject(Array.new) { |results, i| results + items.slice!(i, 1) }
    end
    
    def count_to_pattern(count, item_count)
      pattern = (1..item_count).inject(Array.new) do |pat, i|
        pat.unshift(count % i)
        count /= i
        pat
      end
      count.zero? ? pattern : nil
    end
    
    if ARGV.empty?
      abort "Usage:  #{File.basename($PROGRAM_NAME)} LIST_OF_ITEMS"
    end
    
    permute(ARGV) { |perm| puts(($DEBUG ? "  " : "") + perm.join(" ")) }
    

    Read more…

  • 20

    JAN
    2006

    Caching and Memoization

    I felt this chapter had a lot going for it, in places, but occasionally got lost in the details. All in all though, it's good stuff.

    Caching

    Obviously a powerful technique here and all of it translates to Ruby with little effort. Here's a direct translation of the RGB_to_CMYK() subroutine:

    #!/usr/local/bin/ruby -w
    
    $cache = Hash.new
    
    def rgb_to_cmyk(*rgb)
      return $cache[rgb] if $cache.include?(rgb)
      c, m, y     = rgb.map { |color| 255 - color }
      k           = [c, m, y].min
      $cache[rgb] = [c, m, y].map { |color| color - k } + [k]
    end
    
    unless ARGV.size == 3 && ARGV.all? { |n| n =~ /\A\d+\Z/ }
      abort "Usage:  #{File.basename($PROGRAM_NAME)} RED GREEN BLUE"
    end
    
    puts rgb_to_cmyk(*ARGV.map { |num| num.to_i }).join(", ")
    

    There are several interesting syntax differences in there. For example, I had to use a global variable for the $cache because Ruby methods don't have access to local variables. Another option would be to use a lambda(). These are probably good indicators that we would wrap this in an object and use instance variables normally.

    Read more…

  • 17

    JAN
    2006

    Dispatch Tables

    I think in Ruby we tend to do a lot of this kind of work with method_missing(). I told you, Functional OO Programming.

    Here's my attempt at something close to a direct translation of the RPN calculator example:

    #!/usr/local/bin/ruby -w
    
    $stack = Array.new
    
    def rpn(expression, operations_table)
      tokens = expression.split(" ")
      tokens.each do |token|
        type = token =~ /\A\d+\Z/ ? :number : nil
    
        operations_table[type || token][token]
      end
    
      $stack.pop
    end
    
    if ARGV.size == 2 && ARGV.first == "-i" && ARGV.last =~ /\A[-+*\/0-9 ]+\Z/
      require "pp"
    
      def ast_to_infix(ast)
        if ast.is_a?(Array)
          op, left, right = ast
          "(#{ast_to_infix(left)} #{op} #{ast_to_infix(right)})"
        else
          ast.to_s
        end
      end
    
      ast_table = Hash.new do |table, token|
        lambda { |op| s = $stack.pop; $stack << [op, $stack.pop, s] }
      end.merge(:number => lambda { |num| $stack << num.to_i })
    
      puts "AST:"
      pp(ast = rpn(ARGV.last, ast_table))
      puts "Infix:"
      pp ast_to_infix(ast)
    elsif ARGV.size == 1 && ARGV.first =~ /\A[-+*\/0-9 ]+\Z/
      calculation_table = Hash.new do |table, token|
        raise "Unknown token:  #{token}."
        end.merge(
          :number => lambda { |num| $stack << num.to_i },
          "+"     => lambda { $stack << $stack.pop + $stack.pop },
          "-"     => lambda { s = $stack.pop; $stack << $stack.pop - s },
          "*"     => lambda { $stack << $stack.pop * $stack.pop },
          "/"     => lambda { d = $stack.pop; $stack << $stack.pop / d }
        )
    
        puts rpn(ARGV.first, calculation_table)
    else
      puts "Usage:  #{File.basename($PROGRAM_NAME)} [-i] RPN_EXPRESSION"
    end
    

    Read more…

  • 17

    JAN
    2006

    Recursion and Callbacks

    I'm currently reading through Higher-Order Perl, by Mark Jason Dominus. (Yes, I read books about things other than Ruby.)

    So far, I'm enjoying the title quite a bit. It certainly has me thinking and the Perl in it is very clean and easy to understand. That helps me translate the concepts to my language of interest.

    I'll post some of my Ruby translations of the books example code here as I go along. Others familiar with the book might enjoy looking over them. Be warned, my comments might not make much sense to those who haven't read the book.

    Recursion

    The book starts with some very simple recursion examples trivially translated. Here's one for manually translating Integers to binary Strings (a long way to say str.to_i.to_s(2) in Ruby):

    #!/usr/local/bin/ruby -w
    
    def binary(number)
      return number.to_s if [0, 1].include?(number)
    
      k, b = number.divmod(2)
      binary(k) + b.to_s
    end
    
    unless !ARGV.empty? && ARGV.all? { |n| n =~ /\A\d+\Z/ }
      abort "Usage:  #{File.basename($PROGRAM_NAME)} DECIMAL_NUMBERS"
    end
    
    puts ARGV.map { |num| binary(num.to_i) }.join(" ")
    

    Read more…

  • 15

    JAN
    2006

    What Not to Test

    I've now seen multiple claims amounting to something like, "I built this Rails application with just testing." I think it's great that people can do that. They are obviously smarter than me. I need a browser to build a Web application.

    I do test my Rails projects, of course. I'm a huge fan of testing and I'm always telling people how much it could help with their work. However, I believe there's a time to test and a time not to, if you can imagine that. Yes, you heard me right, there are things I don't test and I'm sure this next revelation will shock you even more:

    I don't test Rails views.

    Now before everyone fires up their mail client and pours on the hate mail, let's make sure we are very clear about what I just said. I do write thorough unit tests for all of my model classes, of course. I also test the controllers as much as possible. I make sure the data I expect ends up in the right instance variables with the magic assigns(). I also use the session Hash to validate what I am remembering about the user. If a controller action is a complex logic branch that can end up in several different places, I will also make sure I add some assertions to verify that the right template handled the response.

    Read more…

  • 5

    JAN
    2006

    Code as a Data Type

    Introduction

    This is the first of a series of articles where I will try to demystify some Ruby idioms for the people who come to Ruby through Rails and find themselves wanting to learn a little more about the language under the hood.

    Strings, Arrays, ... and Code?

    You don't have to code for long in any language before you get intimately familiar with some standard data types. We all have a fair grasp of Ruby's String and Array, because every language has something similar. Ruby has an unusual data type though, which can trip up newcomers. That type is Ruby code itself.

    Allow me to explain what I mean, through an example. First, let's create a little in-memory database to work with:

    class ClientDB
      Record = Struct.new(:client_name, :location, :projects)
    
      def initialize
        @records = [ Record.new( "Gray Soft", "Oklahoma",
                                 ["Ruby Quiz", "Rails Extensions"] ),
                     Record.new( "Serenity Crew", "Deep Space",
                                 ["Ship Enhancements"] ),
                     Record.new( "Neo", "Hollywood", 
                                 ["Rails interface for the Matrix"] ) ]
      end
    end
    

    Read more…

  • 4

    JAN
    2006

    Pathname and Enumerator

    I'm always digging around in Ruby's standard library looking for new toys. It's one-stop-shopping for geeks. I love it.

    There really are some great tools in there that can help you do your work easier. Let me tell you a little about two I am using on a current application.

    Pathname

    Pop Quiz: Which would you rather work with?

    if File.exist? some_dir
      data = File.read( File.join( File.dirname(some_dir),
                                   "another_dir",
                                   File.basename(some_file) ) )
      # ...
    end
    

    Or:

    if some_dir.exist?
      data = (some_dir.dirname + "another_dir" + some_file.basename).read
      # ...
    end
    

    If you prefer the second version, the Pathname library is for you.

    Ruby's file manipulations are usually fine for normal work, but anytime I start messing with directories I really start feeling the pain. For that kind of work, I find Pathname to be a superior interface. My current project makes great use of the methods in the above example as well as Pathname#entries, Pathname#relative_path_from, and more.

    Read more…

  • 4

    JAN
    2006

    Add to Subversion

    I love Rails generators and I love Subversion, but I'm not so sure they love each other.

    Little is worse than generating some scaffold files or a login system, tediously typing svn add ... a bunch of times, and later learning that you missed a file. Oops.

    Given that, here's my hack to get the Rails generators and Subversion to kiss and make up:

    desc "Add generated files to Subversion"
    task :add_to_svn do
      sh %Q{svn status | ruby -nae 'system "svn add \\#{$F[1]}" if $F[0] == "?"'}
    end
    

    I put that in "lib/tasks/add_to_svn.rake" for all my Rails projects. You can then access it whenever you need with rake add_to_svn.

    I've seen similar tasks posted, but they've always been Unix specific and I work with some Windows programmers. This is my attempt to help both on platforms.

    How does it work?

    You're probably not use to seeing ugly Ruby code like that, but Ruby supports a lot of command-line shortcuts that it inherited from Perl. Here's a breakdown of the options used:

    -e The code to execute directly follows this option.
    -n Wrap my code in a standard Unix filter style processing loop.
    -a Auto-split each line of input.
    

    Read more…