|
![]() |
![]() |
![]() |
![]() |
This document is evolving; check back!
As I warned, I'm no Ruby expert, so please email me and/or the class list if you see errors below, or have tips for better ways to do things. Thanks!
Ruby Reading: Online Resources as needed. I suggest starting with Ruby in 20 minutes, then the slides I used in lecture (short, long ). Next, I'd recommend Programming Ruby. Start with the chapter called Ruby.new, a nice overview, then skim the chapter called The Ruby Language, which is more bottom-up and detail-oriented, plus perhaps the Classes and Objects chapter. After you're familiar with the basics, Ruby QuickRef is a good, but terse, summary of key syntax, etc. Overall, I'd estimate this is 2-6 hours reading.
Also note that if you've installed Ruby, the built in doc system 'ri' can give details on the standard library, etc. E.g.
ri String#upcasewill tell you how to convert a character string to uppercase. Syntax here is classname#methodname. It will give you options if your request is ambiguous. E.g., try "ri upcase".
HW 5 Afterthoughts: (A few notes that I forgot to include on the assignment page.) First, I'm not too concerned with error cases. One that occurs is what to do about placing a widget that's wider that the box containing it. I think the simplest solution is that it will be too wide for the "current" line, so unconditionally put it at the start of the next line. I.e., don't check to see whether the 1st widget on a line is too wide. This avoids a potential infinite loop where you keep pushing it to the next line because the current line is full...
Another issue is what to do with clicks that don't land in check/radio widgets---just ignore them. Otherwise, I can't think of any "error" cases that I care about; just do whatever is easy.
I'm also not especially concerned with public/private/protected methods. In "real" code you should be, of course, but you can skip it here.
One issue I didn't get to in lecture was "mixins". It impacts the current assignment in one place. You provide an "each" method for widgets. What if you want to do map or detect or select (filter) or inject (fold) or sort? These are all implemented based on each and can be used with any class that includes an each method, but not by default. Instead, you have to explicitly say you want to include these methods, by adding
include Enumerableto your HWidget class definition. Enumerable is a "module" and the net effect is that its methods are "mixed in" to your class definition---sort of like multiple inheritance, but simpler (tho less flexible). Java's "interfaces" have somewhat the same purpose. It's not crucial, but you might find it convenient.
Ruby programming environment: I'm sure there are fancier tools (like a Ruby plugin for Eclipse, I think), but probably using Emacs plus irb (the interactive Ruby shell) will be adequate for this assignment. Edit code in Emacs as usual, then switch to another window and run irb. This can be a separate shell window, or an "inferior proces" window ("buffer" in emacs lingo) within Emacs. For the later, you do Escape-x-shell-Return to start a shell process in a new buffer, then type irb there. Switch back to your code buffer using the buffer menu. Control-x-2 will split your window and you can have your code buffer visible in the top half and shell/irb buffer in the bottom half, say. Control-x-1 hids the second buffer.
In irb, you can read in your source code via
source 'hw5.rb'(or whatever file name you used). This will process your file line by line, just as if you had typed it in to irb, class definitions and test code both. "Load 'hw5.rb'" is somewhat similar, but better for just class definitions since you seem to get different access to any variables you define in your test cases. Being lazy and a slow typist, I included "def xxx; source 'hw5.rb'; end" in my file, then (after the first time) just typing xxx caused my code to be re-read and my test cases re-executed.
But note that re-loading/re-sourcing your program does NOT restart you in a pristine environment. Variable and method definitions that are not replaced are still there, and this can get very confusing. I wasted a fair bit of time thinking my code "fixes" weren't working because I was looking at stale data. So, if you make extensive changes, or things start looking funny, or just because, it's a good idea to quit/restart irb every so often. Also, be sure to scan backwards in the buffer for compiler error messages; missing them can also be confusing.
I believe attu and the Lab linux machines have a "Ruby mode" installed in Emacs, which should be triggered whenever you visit a file with .rb suffix. I haven't played with it, but this likely has keyboard shortcuts, auto indentation and other features to "make your Ruby experience a gem" (yes, I know; But please note the amazing self-restraint that got me all the way to here before I cracked and made a gem pun). Please report useful stuff you find here.
Misc. Ruby Tips:
# # Binary Search Tree Iterator Example # Borrowed (& modified) from S. Reges' CSE341 Winter 2007 web # class Node def initialize(data = nil, left = nil, right = nil) @data = data @left = left @right = right end attr_accessor :data, :left, :right end class Tree def initialize() @overallRoot = nil end def insert(v) @overallRoot = insert_helper(v, @overallRoot) end def insert_helper(v, root) if root == nil root = Node.new(v) elsif v < root.data then root.left = insert_helper(v, root.left) else root.right = insert_helper(v, root.right) end return root end # straightforward recursive inorder traversal, # but it only prints def print() print_helper(@overallRoot) end def print_helper(root) if root != nil then print_helper root.left puts root.data print_helper root.right end end # a more flexible idea: an inorder iterator. # the funny-looking "{|n| yield n}" is needed # so that a block is passed on each recursive call def inorder inorder_helper(@overallRoot) {|n| yield n} end def inorder_helper(root) if root then inorder_helper(root.left) {|n| yield n} yield root.data inorder_helper(root.right) {|n| yield n} end end # it can be used to print, among other things. def print2() inorder {|n| puts n} end #### # # Here's another way to do the iterator. # Two changes. # 1. Use of a default value for root # allows us to eliminate the helper function # (but does slightly compromise modularity, # since it allows external callers to specify # a non-default starting place). # 2. More interestingly, a last parameter starting # with an ampersand is bound to the block, if any, # specified with the method call, so that we can # explicitly pass it around (again with the # ampersand), rather than implicitly # as in the "inorder" example above. This avoids # the "{|n|yield n}" wrapper we needed above (and # so is perhaps a tiny bit more efficient). # # Although the approaches are slightly different, # the net effect is the same: an in-order iterator. # def each(root=@overallRoot, &block) if root then each(root.left, &block) yield root.data # block.call(root.data) <== you could also do this in place of yield each(root.right, &block) end end def print3 each {|n| puts n} end end t = Tree.new 5.times{t.insert(rand(100))} t.print t.print2 t.print3 sum=0 t.inorder do |n| puts n sum += n break sum if sum > 100 end
![]() |
Computer Science & Engineering University of Washington Box 352350 Seattle, WA 98195-2350 (206) 543-1695 voice, (206) 543-2969 FAX |