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 Protect instance variables from outside forces (i.e., other classes) Ruby is strictly encapsulated • always private • you must include setters/getter • as long as other classes can’t access data directly, it’s encapsulated Reflection and open classes (covered later) subvert this encapsulation package by default can declare public best practice: make instance variables private why? • provides data validity (can only update via setter) • if you want to change internal data representation later, easy to do (other code accesses via getter… ) 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. Why would this be called ad hoc? See also: http://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-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? http://www.java2s.com/Tutorial/Java/0060__Operators/TheinstanceofKeyword.htm 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) POPL goal: be aware of features that commonly exist in many/all languages BUT have subtle differences 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)