Ruby - Colorado School of Mines
Download
Report
Transcript Ruby - Colorado School of Mines
types in Ruby and other
languages…
Classes
and objects (vs prototypes)
Instance variables/encapsulation
Object creation
Object equality/comparison
Object type
• type-safety
• type conversions
Class
methods
Class variables
Inheritance – another lecture
Widening Conversions: can include at least approximations to all of the
values of the original type. Examples (Java/C++)
• byte to short, int, long, float or double
• short to int, long, float or double
• char to int, long, float or double
• int to long, float or double
• long to float or double
• float to double
Narrowing conversions: cannot include all of the values of the original type
• short to byte or char
• char to byte or short
• int to byte, short, or char
• long to byte, short, char or int
• float to byte, short, char, int or long
• double to byte, short, char, int, long or float
Even
widening conversions may lose
accuracy. Example: integers stored in 32
bits, 9 digits of precision. Floating point
values also stored in 32 bits, only about 7
digits of precision (because of space
used for exponent).
Conversions should be used with care! Warnings should
not just be ignored…
Strongly typed language minimizes type conversions
Every
value in Ruby is (or behaves like)
an object
• 2.times { puts “hey”}
Objects
are instances of classes
BasicObject is superclass
Objects are strictly encapsulated
What does that mean?
•Not covered: frozen or tainted objects
Protect
instance variables from outside
forces (i.e., other classes)
Ruby is strictly encapsulated – you must
include setters/getter
Java provides encapsulation – but is it
strictly encapsulated? (hint: what is the
purpose of access modifiers)
Every
class inherits method new
• calls allocate to get space (can’t override)
• calls initialize to create instance variables
Often
convenient to provide default
parameters for initialize
def initialize(x, y, z=nil)
@x, @y, @z = x,y,z
end
class Cat
def initialize(name, age)
@name, @age = name, age
end
def to_s
"(#@name, #@age)"
end
def name
@name
end
c = Cat.new("Fluffy", 2)
puts c
puts c.name
c.age=5
puts c.age
puts c.to_s
new calls initialize
def age
@age
end
to_s like toString
getters and setters, called attributes
last expression in function is return
def name=(value)
@name=value
end
@ indicates instance variable
always refer to self
def age=(value)
@age=value
end
end
DO NOT declare outside of method
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def add(other)
Bottle.new(@label, @ounces+other.ounces)
end
def add!(other)
@ounces += other.ounces
other.ounces = 0
end
def split(scalar)
newOunces = @ounces / scalar
@ounces -= newOunces
Bottle.new(@label, newOunces)
end
def +(amount)
Bottle.new(@label, @ounces + amount)
end
def to_s
# with an accessor, don't need @ - why?
"(#{label}, #{ounces})"
end
end
attr is example of metaprogramming
b = Bottle.new("Tab", 16)
puts b.label
b.ounces = 20
puts "b is #{b}“
puts "result of b2 = b.split 4"
b2 = b.split 4
puts "b is #{b}"
puts "b2 is #{b2}“
puts "result of b3 = b.add(b2)"
b3 = b.add(b2)
puts "b3 is #{b3}“
puts "result of b.add!(b2)"
b.add!(b2)
puts "b is #{b}"
puts "b2 is #{b2}“
puts "result of b4 = b3 + 10"
b4 = b3 + 10
puts "b4 is #{b4}"
From wikipedia:
Polymorphism (from the Greek many forms) is the
provision of a single interface to entities of different
types
If a function denotes different and potentially
heterogeneous implementations depending on a
limited range of individually specified types and
combinations, it is called ad hoc polymorphism. Ad hoc
polymorphism is supported in many languages using
function overloading.
If the code is written without mention of any specific
type and thus can be used transparently with any
number of new types, it is called parametric
polymorphism. In the object-oriented programming
community, this is often known as generics or generic
programming.
See also: http://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-overloading
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def to_s
"(#@label, #@ounces)"
end
end
b = Bottle.new("Tab", 16)
c = Can.new(“Coke", 12)
b2 = b.add(c)
puts "result of b2 = b.add(c)"
puts "b2 is #{b2}“
cat = Cat.new
b3 = b.add(cat)
If it walks like a duck and
quacks like a duck, it
must be a duck
any object with needed
method will work
undefined method
‘ounces’ for <Cat>
http://beust.com/weblog/2005/04/15/the-perils-of-duck-typing/
x=1
x.instance_of?
x.instance_of?
Fixnum => true
Numeric => false (even
though subclass)
x.is_a?
x.is_a?
x.is_a?
x.is_a?
Fixnum => true
Numeric => true
Comparable => true
Object => true
x.class
x.class
== Fixnum => true
== Numeric => false
Quick Ex: how does Java handle?
Class
of an object doesn’t change
Type is more fluid… includes set of
behaviors (methods it responds to)
• i.e, Class != Type….whoaaa
respond_to?
equal?
checks addresses (like == in Java)
for Object, == is synonym for equal?
Most classes override == (like equal in Java)
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def ==(other)
return false if ! other.instance_of? Bottle
return @ounces == other.ounces && @label ==
other.label
end
alias eql? ==
end
# Can also has ounces
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
end
b = Bottle.new("Tab", 16)
b2 = Bottle.new("Tab", 16)
b3 = Bottle.new("Tab", 12)
b4 = Bottle.new("Coke", 16)
puts "b.equal?(b2) #{b.equal?(b2)}"
puts "b == b2 #{b == b2}"
puts "b == b3 #{b == b3}"
puts "b == b4 #{b == b4}“
puts "b.eql?(b2) #{b.eql?(b2)}"
puts "b.eql?(b3) #{b.eql?(b3)}"
c = Can.new("Tab", 16)
puts "b == c #{b == c}"
Numeric
classes will do type conversions when
used with==
eql? is like ==, but no type conversion
• 1.eql? 1.0
can
alias eql? with ==
===
used with case statement
• (1..10) === 5 # true, 5 is in range
• /\d+/ === “123” # true, matches regex
• String === “s” # true if s is instance of String
Other language with eql?/=== feature?
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def add(other)
raise TypeError, "Bottle argument expected " unless other.is_a? Bottle
Bottle.new(@label, @ounces+other.ounces)
end
def add2(other)
raise TypeError, "Bottle argument expected " unless other.respond_to?
:ounces
Bottle.new(@label, @ounces+other.ounces)
end
def add3(other)
Bottle.new(@label, @ounces+other.ounces)
rescue
raise TypeError, "Cannot add with an argument that doesn't have
ounces"
end
def to_s
"(#@label, #@ounces)"
end
end
class Can
attr_accessor :ounces
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def to_s
"(#@label, #@ounces)"
end
end
class Cat
end
b = Bottle.new("Tab", 16)
c = Can.new("Coke", 12)
puts "result of b2 = b.add(c)"
b2 = b.add(c)
puts "result of b2 = b.add2(c)"
b2 = b.add2(c)
puts "b2 is #{b2}"
puts "result of b2 = b.add3(c)"
b2 = b.add3(c)
puts "b2 is #{b2}"
cat = Cat.new
puts "result of b2 = b.add3(cat)"
b2 = b.add3(cat)
class Bottle
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def each
yield @label
yield @ounces
end
def [](index)
case index
when 0, -2 then @label
when 1, -1 then @ounces
when :label, "label" then @label
when :ounces, "ounces" then @ounces
end
end
end
b = Bottle.new("Tab", 16)
puts "using each"
b.each {|x| puts x }
puts "[ix]"
puts b[0]
puts b[-2]
puts b[1]
puts b[-1]
puts "[field name]"
puts b["label"]
puts b[:label]
puts b["ounces"]
puts b[:ounces]
Implement
<=> operator (spaceship)
Like compareTo (-1, 0, 1 and nil if not
comparable)
Typically include Comparable module as
a mixin
Provides <, <=, ==, >=, >(defined in
terms of <=>)
Sometimes write == anyway (e.g., one
may be case-sensitive, may be more
efficient). Best to be consistent.
class Bottle
include Comparable
attr_accessor :ounces
attr_reader :label
def initialize(label, ounces)
@label = label
@ounces = ounces
end
def hash
#based on Effective Java by Bloch
code = 17
code = 37 * code + @label.hash
code = 37 * code + @ounces.hash
code
end
def <=>(other)
return nil unless other.instance_of? Bottle
@ounces <=> other.ounces
end
end
b = Bottle.new("Tab", 16)
b2 = Bottle.new("Tab", 12)
b3 = Bottle.new("Coke", 16)
b4 = Bottle.new("Tab", 16)
puts "b == b2 #{b == b2}"
puts "b < b2 #{b < b2}"
puts "b > b2 #{b > b2}"
puts "b == b3 #{b == b3}"
puts "b == b4 #{b == b4}"
class Bottle
include Comparable
attr_accessor :ounces
attr_reader :label
MAX_OUNCES = 64
@@numBottles = 0
def initialize(label, ounces)
@label = label
@@numBottles += 1
if ounces > MAX_OUNCES
@ounces = MAX_OUNCES
else
@ounces = ounces
end
end
bottles = Array.new
bottles[0] = Bottle.new("Tab", 16)
bottles[1] = Bottle.new("Tab", 16)
bottles[2] = Bottle.new("Coke", 16)
bottles[3] = Bottle.new("Tab", 20)
puts "Total ounces: #{Bottle.sum bottles}"
b = Bottle.new("Sprite", 72)
puts b
Bottle.report
def Bottle.sum(bottles)
total = 0
bottles.each {|b| total += b.ounces }
total
end
def to_s
"(#@label, #@ounces)"
end
def self.report #class method
puts "Number of bottles created: #@@numBottles"
end
end
Not covered: Class Instance Variables
Language Concepts
Classes
Instance variables
Object creation
Determine object type
Object equality
Object comparisons
Type conversions
Type safety
Class methods – next
lecture
• Class variables
•
•
•
•
•
•
•
•
•
Ruby
• new
• to_s
• duck typing
Not covered, but may be useful
dup
clone
initialize_copy
marshalling (create from serialized data)