320 likes | 480 Views
A quick Ruby Tutorial, Part 4. COMP313 Source: Programming Ruby, The Pragmatic Programmers’ Guide by Dave Thomas, Chad Fowler, and Andy Hunt. 2 forms of assignment. hard coded to variables and constants: instrument = "piano" MIDDLE_A = 440
E N D
A quick Ruby Tutorial, Part 4 COMP313 Source: Programming Ruby, The Pragmatic Programmers’ Guide by Dave Thomas, Chad Fowler, and Andy Hunt
2 forms of assignment hard coded to variables and constants: instrument = "piano" MIDDLE_A = 440 user defined for object attributes and other element references: song.duration = 234 instrument["ano"] = "ccolo"
problems class BrokenAmplifier attr_accessor :left_channel, :right_channel def volume=(vol) left_channel = self.right_channel = vol end end ba = BrokenAmplifier.new ba.left_channel = ba.right_channel = 99 ba.volume = 5 ba.left_channel → 99 ba.right_channel → 5
Parallel assignment a = [1, 2, 3, 4] b, c = a → b == 1, c == 2 b, *c = a → b == 1, c == [2, 3, 4] b, c = 99, a → b == 99, c == [1, 2, 3, 4] b, *c = 99, a → b == 99, c == [[1, 2, 3, 4]] b, c = 99, *a → b == 99, c == 1 b, *c = 99, *ab == 99, c == [1, 2, 3, 4]
Nested assignment b, (c, d), e = 1,2,3,4 → b == 1, c == 2, d == nil, e == 3 b, (c, d), e = [1,2,3,4] → b == 1, c == 2, d == nil, e == 3 b, (c, d), e = 1,[2,3],4 → b == 1, c == 2, d == 3, e == 4 b, (c, d), e = 1,[2,3,4],5 → b == 1, c == 2, d == 3, e == 5 b, (c,*d), e = 1,[2,3,4],5 → b == 1, c == 2, d == [3, 4], e == 5
Overwriting operators like + class Bowdlerize def initialize(string) @value = string.gsub(/[aeiou]/, '*') end def +(other) Bowdlerize.new(self.to_s + other.to_s) end def to_s @value end end a = Bowdlerize.new("damn ") → d*mn a += "shame" → d*mn sh*m*
defined? operator defined? 1 → "expression" defined? dummy → nil defined? printf → "method" defined? String → "constant" defined? $_ → "global-variable" defined? Math::PI → "constant" defined? a = 1 → "assignment" defined? 42.abs → "method” defined? yield → ”yield” or nil
Case expressions leap = case when year % 400 == 0: true when year % 100 == 0: false else year % 4 == 0 end case input_line when "debug" dump_debug_info dump_symbols when /p\s+(\w+)/ dump_variable($1) when "quit", "exit" exit else print "Illegal command: #{input_line}" end
another case examples kind = case year when 1850..1889 then "Blues" when 1890..1909 then "Ragtime" when 1910..1929 then "New Orleans Jazz" when 1930..1939 then "Swing" when 1940..1950 then "Bebop" else "Jazz" end
case as instanceof test case shape when Square, Rectangle # ... when Circle # ... when Triangle # ... else # ... end
For .. in .. looping based on each => works for all classes defining each class Periods def each yield "Classical" yield "Jazz" yield "Rock" end end periods = Periods.new for genre in periods print genre, " " end produces:Classical Jazz Rock
break, redo, next, retry while line = gets next if line =~ /^\s*#/ # skip comments break if line =~ /^END/ # stop at end # substitute stuff in backticks and try again redo if line.gsub!(/`(.*?)`/) { eval($1) } # process line ... end for i in 1..100 print "Now at #{i}. Restart? " retry if gets =~ /^y/ end
Loops, blocks, and scope of variables while/until/for do NOT create a new scope (!= java), but explicit code blocks can: [ 1, 2, 3 ].each { |x| y = x + 1 } [ x, y ] --> error, but x=nil y=nil [ 1, 2, 3 ].each { |x| y = x + 1 } [ x, y ] --> [3,4]
Exceptions op_file = File.open(opfile_name, "w") begin # Exceptions raised by this code will # be caught by the following rescue clause while data = socket.read(512) op_file.write(data) end rescue SystemCallError $stderr.print "IO failed: " + $! op_file.close File.delete(opfile_name) raise end
more on Exceptions begin eval string rescue SyntaxError, NameError => boom print "String doesn't compile: " + boom rescue StandardError => bang print "Error running script: " + bang end empty rescue catches StandardError instances, may also use expression returning an Exception class
ensure (cf. Java’s finally) f = File.open("testfile") begin # .. process rescue # .. handle error ensure f.close unless f.nil? end
retry @esmtp = true begin # First try an extended login. If it fails because the # server doesn't support it, fall back to a normal login if @esmtp then @command.ehlo(helodom) else @command.helo(helodom) end rescue ProtocolError if @esmtp then @esmtp = false retry else raise end end
Raising exceptions raise raise "bad mp3 encoding" raise InterfaceException, "Keyboard failure", caller raise "Missing name" if name.nil? if i >= names.size raise IndexError, "#{i} >= size (#{names.size})" end raise ArgumentError, "Name too big", caller[1..-1]
Retry example class RetryException < RuntimeError attr :ok_to_retry def initialize(ok_to_retry) @ok_to_retry = ok_to_retry end end def read_data(socket) data = socket.read(512) raise RetryException.new(true), "transient read error” if data.nil? # .. normal processing end
Retry example cont. begin stuff = read_data(socket) # .. process stuff rescue RetryException => detail retry if detail.ok_to_retry raise end
Modules • provide namespace against clashes • implement mixin facility module Trig PI = 3.141592654 def Trig.sin(x) # .. end end require 'trig' y = Trig.sin(Trig::PI/4)
Module mixin • Modules are not classes, cannot have instances module Debug def who_am_i? "#{self.class.name} (\##{self.object_id}): #{self.to_s}" end end class Phonograph include Debug # ... end ph = Phonograph.new("West End Blues") ph.who_am_i? → "Phonograph (#937328): West End Blues"
Mixin interaction class Song include Comparable def initialize(duration) @duration = duration end def <=>(other) self.duration <=> other.duration end end song1, song2 = Song.new(225), Song.new(260) song1 <=> song2 → -1 song1 < song2 → true song1 == song1 → true song1 > song2 → false
Resolving name clashes methods: immediate class, then local mixins last one first, then superclass, it’s mixins, etc … variable before method name: a = 1 def a 2 end a -> 1 a() -> 2
2 ways of IO utility methods: gets, open, print, printf, puts, putc, readline, readlines, test proper classes: IO, File, BasicSocket, … endl = ”\n" STDOUT << 99 << " red balloons" << endl
File IO examples File.open("testfile") do |file| file.each_byte {|ch| putc ch; print "." } end IO.foreach("testfile") {|line| puts line } # read whole file into one string str = IO.read("testfile") str.length → 66 str[0, 30] → "This is line one\nThis is line " # read all lines into an array arr = IO.readlines("testfile") arr.length → 4 arr[0] → "This is line one\n"
StringIO require 'stringio' ip = StringIO.new("now is\nthe time\nto learn\nRuby!") op = StringIO.new("", "w") ip.each_line do |line| op.puts line.reverse end op.string →”\nsi won\n\nemit eht\n\nnrael ot\n!ybuR\n"
Profiler require 'profile' count = 0 words = File.open("/usr/share/dict/words") while word = words.gets word = word.chomp! if word.length == 12 count += 1 end end puts "#{count} twelve-character words”
Profiler output % cumulative self self total time seconds seconds calls ms/call ms/call name 7.70 8.60 8.60 234938 0.04 0.04 IO#gets 7.65 17.14 8.54 234937 0.04 0.04 Fixnum#== 7.43 25.43 8.29 234937 0.04 0.04 String#length 7.18 33.45 8.02 234937 0.03 0.03 String#chomp! 0.70 34.23 0.78 20460 0.04 0.04 Fixnum#+ 0.00 34.23 0.00 1 0.00 0.00 Fixnum#to_s 0.00 34.23 0.00 1 0.00 0.00 Kernel.puts 0.00 34.23 0.00 1 0.00 0.00 File#initialize 0.00 34.23 0.00 2 0.00 0.00 Kernel.respond_to? 0.00 34.23 0.00 1 0.00 0.00 File#open 0.00 34.23 0.00 2 0.00 0.00 IO#write 0.00 34.23 0.00 1 0.00 0.00 Profiler__.start_profile 0.00 34.23 0.00 1 0.00 111640.00 #toplevel
Faster solution require 'profile' words = File.read("/usr/share/dict/words") count = words.scan(/^.........…\n/).size puts "#{count} twelve-character words" 20460 twelve-character words % cumulative self self total time seconds seconds calls ms/call ms/call name 95.24 0.20 0.20 1 200.00 200.00 String#scan 4.76 0.21 0.01 1 10.00 10.00 File#read 0.00 0.21 0.00 2 0.00 0.00 IO#write
Duck typing If it walks and talks like a duck, treat it like one (laissez-faire) def append_song(result, song) result << song.title << " (" << song.artist << ")" end ###### when checking, check for capability instead of class: def append_song(result, song) unless result.respond_to?(:<<) fail TypeError.new("'result' needs `<<' capability") end unless song.respond_to?(:artist) && song.respond_to?(:title) fail TypeError.new("'song' needs 'artist' and 'title'") end result << song.title << " (" << song.artist << ")” end
other ruby stuff • threads and processes • GUI: Tk library • JavaDoc like Rdoc • Package management: RubyGems • Web stuff (including Ruby on Rails) • JRuby • Extending using host language (C/Java)