A quick Ruby Tutorial, Part 4
description
Transcript of A quick Ruby Tutorial, Part 4
A quick Ruby Tutorial, Part 4
COMP313
Source: Programming Ruby, The Pragmatic Programmers’ Guide by Dave Thomas, Chad
Fowler, and Andy Hunt
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"
2 forms of assignment
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, *a b == 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 expressionsleap = 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 eachclass Periods def each yield "Classical" yield "Jazz" yield "Rock" end endperiods = 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=nily=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) endrescue 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) # .. endend
require 'trig' y = Trig.sin(Trig::PI/4)
Module mixin
• Modules are not classes, cannot have instancesmodule 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 interactionclass 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, thenlocal mixins last one first, thensuperclass, it’s mixins, etc …
variable before method name:a = 1def a 2enda -> 1a() -> 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)