Ruby: September 2006 Archives

I stole the code posted on reddit earlier today and ported it to ruby. Now I can do the following:

layout = BoxLayout.html <<-END
----------
|        |
----------
| |    | |
| |    | |
| |    | |
| |    | |
----------
|        |
----------
END

puts layout % [header, left_sidebar,
               body,
               right_sidebar, footer]

and get layout code that looks much much cleaner than the equivalent HTML/rhtml/whatever. I can instantly look at this code and know what it is gonna look like in a browser. I'll release this soon, but I have some enhancements for codeforpeople's rubyforge and seattle.rb's hoe first.

Talk at NYC.rb went well

| | Comments (0)

Finished my talk and am winding down. Not bad at all for an unrehearsed talk. I'll be giving it again tomorrow at limewire so I should have plenty of practice for ajaxworld next week. YAY!

ok. dying now... thud

Hoe 1.0.4 released

| | Comments (0)

Hoe 1.0.4 has been released!

sudo gem install hoe

DESCRIPTION:

Hoe is a simple rake/rubygems helper for project Rakefiles. It generates all the usual tasks for projects including rdoc generation, testing, packaging, and deployment.

Tasks Provided:

  • audit - Run ZenTest against the package
  • clean - Clean up all the extras
  • debug_gem - Show information about the gem
  • default - Run the default tasks
  • docs - Build the docs HTML Files
  • install - Install the package. Uses PREFIX and RUBYLIB
  • multi - Run the test suite using multiruby
  • package - Build all the packages
  • publish_docs - Publish RDoc to RubyForge
  • release - Package and upload the release to RubyForge
  • test - Run the test suite. Use FILTER to add to the command line.
  • uninstall - Uninstall the package.
  • upload - Upload RDoc to RubyForge

See class rdoc for help. Hint: ri Hoe

Changes

= 1.0.4 2006-09-23

  • Damnit... I messed up. There is no rubygems gem to be dependent upon. Duh.

= 1.0.3 2006-09-23

  • Added debug_gem rule.
  • Added lots of doco.
  • Added proper deps to hoe for other's gems, and rake/rubyforge/rubygems for hoe.
  • Added ridocs to generate ri locally for testing.
  • Added support for multiple authors.
  • Fixed include paths.
  • Rdoc now includes any top level .txt files.
  • Renamed deploy to release.
  • Renamed upload to publish_docs.
  • publish_docs is now smart about subprojects and missing subdirectories.

  • http://rubyforge.org/projects/seattlerb/

  • http://seattlerb.rubyforge.org/hoe/

ParseTree 1.5.0 Released

| | Comments (0)

ParseTree version 1.5.0 has been released!

sudo gem install ParseTree

DESCRIPTION:

ParseTree is a C extension (using RubyInline) that extracts the parse tree for an entire class or a specific method and returns it as a s-expression (aka sexp) using ruby's arrays, strings, symbols, and integers.

As an example:

def conditional1(arg1)
  if arg1 == 0 then
    return 1
  end
  return 0
end

becomes:

[:defn,
  :conditional1,
  [:scope,
   [:block,
    [:args, :arg1],
    [:if,
     [:call, [:lvar, :arg1], :==, [:array, [:lit, 0]]],
     [:return, [:lit, 1]],
     nil],
    [:return, [:lit, 0]]]]]

FEATURES/PROBLEMS:

  • Uses RubyInline, so it just drops in.
  • Includes SexpProcessor and CompositeSexpProcessor.
    • Allows you to write very clean filters.
  • Includes parsetreeshow, which lets you quickly snoop code.
    • echo "1+1" | parsetreeshow -f for quick snippet output.
  • Includes parsetreeabc, which lets you get abc metrics on code.
    • abc metrics = numbers of assignments, branches, and calls.
    • whitespace independent metric for method complexity.
  • Includes parsetreedeps, which shows you basic class level dependencies.
  • Only works on methods in classes/modules, not arbitrary code.
  • Does not work on the core classes, as they are not ruby (yet).

    http://rubyforge.org/projects/parsetree/ http://www.zenspider.com/ZSS/Products/ParseTree/

Changes

  • 5 minor enhancements:
    • Added parsetreeaudit.
    • Added reporting of unsupported nodes that have processors.
    • YAY! class method support! generated as :"self.blah"
    • Add parsetreefor_string.
    • Converted Rakefile+gemspec to Hoe-based Rakefile.
  • 6 bug fixes:

    • Did some preliminary work on 1.9 compatibility.
    • Fixed tests for some changes/clarifications.
    • Fixed resbody: should have nil exceptions list when no exception rescued.
    • Fixed opasgn1 and opasgn2.
    • Fixed incompatibility with new inline changes.
    • Fixed VALUE decl in parse_tree.rb
  • http://rubyforge.org/projects/parsetree/

  • http://www.zenspider.com/ZSS/Products/ParseTree/

Recursive Functions in RubyInline

| | Comments (2)

harrisj filed a bug against inline today regarding recursive functions failing. The simplest example is fibonacci. The problem is simple, and its solution (workaround really) is simple too. Let's dig in, shall we?

Pure Ruby

class Fib
  def rubyfib(n)
    return 1 if n <= 1
    rubyfib(n-1) + nfib(n-2)
  end
end

Simple enough example, but because of its recursive nature it takes a long time to run (less to do with its recursiveness than the simple fact of the matter that it is doing a ton of method dispatches in general.

Inline C

So, let's say that we have this code coming up at the top of our profile and we want to speed it up. We grab our trusty inline and pop in the equivalent in C:

require 'inline'
class Fib
  inline do |builder|
    builder.c <<-END
      long badfib(long n) {
        if (n <= 1) {
          return 1;
        } else {
          return badfib(n-1) + badfib(n-2);
        }
      }
    END
  end
end

The Recursion Problem

The problem is that this doesn't compile properly. Why? It looks right. Indeed it does, but you need to dig deeper to understand what inline is doing for you to make this stuff look so easy. What it is really generating is this:

static VALUE badfib(VALUE self, VALUE _n) {
  long n = NUM2INT(_n);

  if (n == 0 || n == 1) {
    return INT2NUM(1);
  } else {
    return INT2NUM(badfib(n-1) + badfib(n-2));
  }
}

The problem is twofold. badfib has two arguments, not one, and both of them are VALUE, not long. The "simple" solution is to pass in self, but you still have to convert the second arg as well and the return value. The last line starts to look gross:

  return FIX2INT(badfib(self, INT2FIX(n-1))) + FIX2INT(badfib(self, INT2FIX(n-2)));

That is icky. It works, is damn fast compared to the pure ruby example (14.5s vs 0.3s in my benchmarks), but it is still icky. I'll even take a speed hit to reduce icky. That said, there is a much simpler solution.

Pure C Recursion, Ruby Wrapper

Instead of mixing the two languages at that level (and it really is an oil and water problem at this stage--maybe if we shake it enough we'll get a vinaigrette), strictly separate them:

builder.prefix <<-END
  long realfib(long n) {
    if (n <= 1) {
      return 1;
    } else {
      return realfib(n-1) + realfib(n-2);
    }
  }
END

builder.c <<-END
  long cfib(long n) {
    return realfib(n);
  }
END

Here we've popped the real work into builder.prefix instead of builder.c which just injects the code raw. As a result, we have one layer doing ruby-to-c-to-ruby conversions and another doing the work. It is even faster because you get rid of all those icky conversions at every level. Even the resulting code looks much better:

long realfib(long n) {
  if (n <= 1) {
    return 1;
  } else {
    return realfib(n-1) + realfib(n-2);
  }
}

static VALUE cfib(VALUE self, VALUE _n) {
  long n = NUM2INT(_n);

  return INT2NUM(realfib(n));
}

Just one problem

There is one problem I want to point out here. Granted, the problem itself is slightly contrived, but I see examples like this coming up over and over in IRC and the mailing list. The problem is this: people get so myopically focused on using C to make things faster that they don't bother looking at their algorithms or data-structures. It is sad. Ruby may be slow for method dispatch, but bad code can be slow in ANY language. Wanna see a simple and readable pure ruby solution to the above that kicks the crap out of my fastest C solution?

##
# Classic fibonacci is boring, cache the hell out of it.
@@fib = {}
def fasterfib(n)
  return 1 if n <= 1
  unless @@fib.has_key? n then
    @@fib[n] = fasterfib(n-1) + fasterfib(n-2)
  end
  @@fib[n]
end

C doesn't make ruby fast. Avoiding method dispatch makes ruby fast. You can do that using pure ruby quite a bit of the time by applying your noodle. Check out the times:

% ./fib.rb 10000 15
# of iterations = 10000
                          user     system      total        real
null_time             0.000000   0.000000   0.000000 (  0.001779)
fib-ruby             14.450000   0.060000  14.510000 ( 14.628854)
badfib                0.310000   0.000000   0.310000 (  0.318015)
cfib                  0.150000   0.000000   0.150000 (  0.147117)
fib-cached            0.010000   0.000000   0.010000 (  0.012523)

And Zed, before you get started, I picked 10k because that was all I had the patience to sit through. This isn't a scientific writeup. So nyah!

The latest version has been released and makes deployment easier by having add_release return the release_id to be used in add_file. YAY!

Now deployment looks more like:

task :deploy => [:clean, :package] do
  rf = RubyForge.new
  rf.login
  release_id = rf.add_release 'group', 'project', '1.2.3', 'pkg/blah-1.2.3.tgz'
  rf.add_release 'group', 'project', release_id, 'pkg/blah-1.2.3.gem'
end

(without all those hard coded versions and paths and stuff... esp if you use Hoe!)

Farmer Ted came to me the other day with a problem. He has about 10 different packages he wants to work on and periodically release, but sometimes it seems to him that maintaining a threshing machine is easier than it is maintaining all his Rakefiles and gemspecs. Most of his rakefiles are duplicated in every project yet differ slightly everywhere. His deployment rules are enhanced in one place and go stale in another. So I showed him another tool to help: Hoe!

Hoe is a tool that covers all the usual stuff you have in a project: documentation, testing, version compatibility (through multiruby), packaging, deployment, cleanup, and more. In fact, this:

require 'rubygems'
require 'hoe'

Hoe.new("thingy", '1.0.0') do |p|
  p.rubyforge_name = "myproject"
  p.summary = "I'm so happy that I don't have to write this stuff anymore."
end

gets you all of this for free:

% rake -T
rake audit            # Run ZenTest against the package
rake clean            # Clean up all the extras
rake clobber_docs     # Remove rdoc products
rake clobber_package  # Remove package products
rake default          # Run the default tasks
rake deploy           # Deploy the package to rubyforge.
rake docs             # Build the docs HTML Files
rake install          # Install the package. Uses PREFIX and RUBYLIB
rake multi            # Run the test suite using multiruby
rake package          # Build all the packages
rake redocs           # Force a rebuild of the RDOC files
rake repackage        # Force a rebuild of the package files
rake test             # Run the test suite. Use FILTER to add to the command line.
rake uninstall        # Uninstall the package.
rake upload           # Upload RDoc to RubyForge

Farmer Ted tried it out and he's much much happier now.

Hoe is real young right now. Rough around the edges. Undocumented. Etc. You've been warned. That said, please try it out and let me know what you think.

sudo gem install hoe

This improves the rdoc and fixes a deployment issue with 0.2.0. Sorry for not blogging it earlier.

RubyInline version 3.6.0 has been released! Only 10 month late! (ugh I can be so spacy sometimes!)

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

Description:

Ruby Inline is an analog to Perl's Inline::C. Out of the box, it
allows you to embed C/++ external module code in your ruby script
directly. By writing simple builder classes, you can teach how to cope
with new languages (fortran, perl, whatever). The code is compiled and
run on the fly when needed.

Using the package_inline tool Inline now allows you to package up
your inlined object code for distribution to systems without a
compiler (read: windows)!

Features/Problems:

+ Quick and easy inlining of your C or C++ code embedded in your ruby script.
+ Extendable to work with other languages.
+ Automatic conversion between ruby and C basic types
+ char, unsigned, unsigned int, char *, int, long, unsigned long
+ inline_c_raw exists for when the automatic conversion isn't sufficient.
+ Only recompiles if the inlined code has changed.
+ Pretends to be secure.
+ Only requires standard ruby libraries, nothing extra to download.
+ Can generate a basic Rakefile and package up built extensions for distribution.

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

Changes:

+ 6 minor enhancements
+ C builder can now be used directly for other foreign language glue.
+ Pretty much all (c) functions are plain argument style, not argc/argv.
+ Added Nathaniel and Dan's patches for windows support.
+ Added VALUE as a default known type.
+ Improved testing under $DEBUG.
+ Deprecated $INLINE_FLAGS and $INLINE_LIBS are dead.
+ 3 bug fixes
+ Fixed a number of issues wrt testing.
+ Cleaned up and cached certain calculations.
+ Some windows compiler fixes went in, but MS compiler is a PITA still.

http://www.zenspider.com/ZSS/Products/RubyInline/
http://rubyforge.org/projects/rubyinline/

In NYC for the 26th

| | Comments (1)

I'll be in NYC for the 26th! I'm flying out to meet the fabulous Ms. Amy Hoy to interview at Limewire. I'll be missing the Seattle.rb meeting, but going to my first NYC.rb meeting!

While I'm in the city I hope to hang out with some of the NYC.rb crew, have some arepas, and generally have a good time. What else should I do? I get in way too early.

I've said this before but I was lax in enforcing it for rubyforge 0.2.0:

ALWAYS deploy from a manifest file!

Specifically Dir.glob with ** is evil. Even * is evil.

That said, rubyforge now packages from a hand-created manifest file and the rdoc is a bit cleaner.

sudo gem update rubyforge

I'm proud to announce that codeforpeople's rubyforge 0.2.0 has been released! This release marks a massive revamp to rubyforge splitting the previous command-line script into a script and library with unit tests. Makeing the system now able to be used directly from Rakefiles and other deployment scripts!

We've extended the command set and added semi-automatic configuration of groups, projects, and releases. We've also added the ability to add files to a release.

We have a lot of plans for this library. Next on the list is to be able to specify multiple files to release all at once, error checking, and a much more powerful configuration system.


Editorial:

This is sooo going to make my deployment life easier! I'm planning on doing a release per day for this week (with this release counting as Wednesday's release).

Eventually I'd like you to be able to do this:

% echo "group: seattlerb
project: ringy_dingy" > .rubyforge.yml
% rake test clean package
% rubyforge release 1.2.3 pkg/*

For now I'm quite happy with:

% rake test clean package
% rubyforge add_release seattlerb ringy_dingy 1.2.3 pkg/*.gem
% rubyforge add_file seattlerb ringy_dingy 6929 pkg/*.tgz

The only real PITA is getting that release ID by hand. That is next on my list.

Tomorrow should be a release of RubyInline. Long overdue.

ZenTest 3.4.0 Released

| | Comments (0)

ZenTest version 3.4.0 has been released! Lots of autotest plugin'y goodness!

sudo gem update ZenTest

Changes:

13 minor enhancements

* Broke out example_dot_autotest into multiple files in lib.
* Enhanced hook system so it can return true if event handled.
* Sleep is now 1 second by default because life is too short.
* Hooked interrupt with new hook system. First handler wins.
* Hooked test results before output
* Accurate test counts for Test::Rails.
* Added snarl autotest plugin, thanks to Patrick Hurley.
* Added timestamp autotest plugin, thanks to Joe Goldberg.
* Added redgreen, thanks to Pat Eyler, Sean Carley, and Rob Sanheim.
* Added kdenotify autotest plugin, thanks to Geir Freysson.
* Added markaby support for Test::Rails.
* Added hack to display a tree of render calls.
* Added hook to perform extra setup for

5 bug fixes

- Extended zentest to deal with rails a bit better... ugh.
- Fixed @libs for windoze.
- Fixed inner class/test identification in autotest.
- Namespaced all plugins... eric is anal.
- No longer freak out if rubygems tarball not in multiruby/versions.

Coming Up for Air

| | Comments (0)

I spent too many of the last 36 hours working on my presentation coming up in October. It is done, and I'd like to take a breather now. Oh, and by the way, I'll be giving a talk at AJAXWorld in Santa Clara on October 3, 2006! :P

What? The title? You already know it:

"Electric Kool-aid Acid Testing"

So, if you wanted to see that talk, here is your chance.

ERB made faster (by about 40%?)

| | Comments (1)
require 'benchmark'
require 'erb'

max = (ARGV.shift || 1_000_000).to_i

def validate(src)
  expect = "blah blah 2 " * 50
  result = eval(src)
  unless expect == result then
    raise "expected\n<#{expect}>\nbut got\n<#{result}>\n"
  end
end

def run
  iters = [10, 100, 1_000, 10_000, 100_000]
  Hash[*iters.map { |max|
         real = Benchmark::measure do
           src = []
           50.times { src << "blah blah <%= 1 + 1 %> " }
           src = src.join

           src = ERB.new(src).src

           validate src

           for i in 0..max do
             eval src
           end
         end.real
         [max, real]
       }.flatten]
end

before = run
require 'make_erb_fast' # patches 3 methods
after = run

before.keys.sort.each do |iter|
  puts "%6d\t%6.4f\t%6.4f" % [iter, before[iter], after[iter]]
end

begets:

erb.png

I found Shortest Python Quine? and thought I'd check it out. I wasn't aware of python's %r, but apparently it is equivalent to our %p (which I always forget about). Anyhow, here is our version:

% ruby quine.rb 
_="_=%p;puts _%%_";puts _%_
% ruby quine.rb | ruby
_="_=%p;puts _%%_";puts _%_

or eric's morph:

% echo '_="_=%p;puts _%%_";puts _%_' | ruby | ruby
_="_=%p;puts _%%_";puts _%_

Comes in at 28 characters, and ties the shortest on rubygarden.

RubyInline Myths

| | Comments (4)

I just read the RubyInline section (22.4) of the Ruby Cookbook by Lucas Carlson and Leonard Richardson. I'm a bit ticked off so calibrate accordingly. In the discussion there are blatant errors. A lot of them... like, of the 8 paragraphs involved, 2 of them are basic high-level description, 1 of them is a warning against using C code to begin with (sound advice--although a bit off the mark since Inline does any language it is taught to do, not just C/++). Then there is the rest: 3 of the 8 paragraphs are flat out wrong and 2 more don't have the limitations indicated and contradict themselves.

First in says, RubyInline only understands a limited subset of C and C++. The functions you embed can only accept and return arguments of the types char, unsigned, unsigned int, char *, int, long, and unsigned long. If you need to use other types, RubyInline won't be able to automatically generate the wrapper functions. [...]. This is false. Completely and totally false. It isn't even the full list of registered automatically converted types, and since 3.0.0 (released 2003-12-23) there has been public API for registering conversions of your own. Before then it was possible but less easy. Example:

inline(:C) do |builder|
  builder.add_type_converter("VALUE", '', '') # register, but doesn't require conversion
  # ...
end

Second, it goes on to mention that another limitation is that you need to have a complier environment but then says that RubyInline provides inline_package to deal with that... well... is it a limitation or not? Seems they're saying not, except that it is in the list of limitations. Way to put on some negative spin and backpedal one paragraph later. *shrug*

To their credit, Neither Lucas nor Leonard wrote this section. Instead, Garrett Rooney has been given credit for this section, and all other sections for externalized code (except jruby). After doing some googling it looks like Garrett has a stake SWIG, so I have to assume that there is some bias involved.

None of the three sent me the section for review or notified me in any way that this was happening. I'm a bit disappointed, but I guess not that surprised given the publisher. I just wish they'd sent me a draft to review. It would have been so easy to deal with proactively.

About this Archive

This page is a archive of entries in the Ruby category from September 2006.

Ruby: August 2006 is the previous archive.

Ruby: October 2006 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Pages

Powered by Movable Type 4.1