types in Ruby and other languages… Classes and Objects types in Ruby and other languages…
Language Design - classes 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
Encapsulation 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
Compare to Java 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… ) Based on above, Java is not strictly encapsulated
Object creation 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
Simple Class new calls initialize to_s like toString class Cat def initialize(name, age) @name, @age = name, age end def to_s "(#@name, #@age)" def name @name def age @age def name=(value) @name=value def age=(value) @age=value c = Cat.new("Fluffy", 2) puts c puts c.name c.age=5 puts c.age puts c.to_s new calls initialize to_s like toString getters and setters, called attributes last expression in function is return @ indicates instance variable always refer to self DO NOT declare outside of method
Operator Overloading, etc. 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) def add!(other) @ounces += other.ounces other.ounces = 0 def split(scalar) newOunces = @ounces / scalar @ounces -= newOunces Bottle.new(@label, newOunces) def +(amount) Bottle.new(@label, @ounces + amount) def to_s # with an accessor, don't need @ - why? "(#{label}, #{ounces})" 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 "result of b4 = b3 + 10" b4 = b3 + 10 puts "b4 is #{b4}" attr is example of metaprogramming
Polymorphism From wikipedia: Polymorphism (from the Greek many forms) is the provision of a single interface to entities of different types. A polymorphic type is one whose operations can also be applied to values of some other type, or types. We’ll see 3 types of polymorphism: Ad hoc (e.g., operator overloading) Parametric (e.g., generics in Java, duck typing in Ruby) Subtyping (e.g., parent-child relationships where child overrides parent methods) https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
Ad hoc Polymorphism 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. i.e., same function name, but behavior is different depending on number and/or types of parameters Why would this be called ad hoc? See also: http://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-overloading
Parametric Polymorphism 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. without any specific type, but potentially with a type parameter e.g., LinkedList<T> we explored this briefly in prior lecture 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 http://stackoverflow.com/questions/10179449/what-is-parametric-polymorphism-in-java-with-example
Duck Typing class Can attr_accessor :ounces def initialize(label, ounces) @label = label @ounces = ounces end def to_s "(#@label, #@ounces)" 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/
Object Class x=1 x.instance_of? Fixnum => true x.instance_of? Numeric => false (even though subclass) x.is_a? Fixnum => true x.is_a? Numeric => true x.is_a? Comparable => true x.is_a? Object => true x.class == Fixnum => true x.class == Numeric => false Quick Ex: how does Java handle? http://www.java2s.com/Tutorial/Java/0060__Operators/TheinstanceofKeyword.htm
Object Type 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?
Object equality 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
Equality Example 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 alias eql? == # Can also has ounces class Can 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}"
More object equality Numeric classes will do type conversions when used with == eql? is like ==, but no type conversion 1.eql? 1.0 can alias eql? with ==
More object comparison === 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 Interesting option that provides a context-sensitive comparison, but we won’t study/make use of http://stackoverflow.com/questions/4467538/what-does-the-operator-do-in-ruby
Type-safe Methods 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) def add2(other) raise TypeError, "Bottle argument expected " unless other.respond_to? :ounces def add3(other) rescue raise TypeError, "Cannot add with an argument that doesn't have ounces" def to_s "(#@label, #@ounces)" class Can attr_accessor :ounces def initialize(label, ounces) @label = label @ounces = ounces end def to_s "(#@label, #@ounces)" class Cat 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) cat = Cat.new puts "result of b2 = b.add3(cat)" b2 = b.add3(cat)
More overloading class Bottle attr_accessor :ounces attr_reader :label def initialize(label, ounces) @label = label @ounces = ounces end def each yield @label yield @ounces def [](index) case index when 0, -2 then @label when 1, -1 then @ounces when :label, "label" then @label when :ounces, "ounces" then @ounces 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]
Object Comparisons 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.
Comparison example 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 def <=>(other) return nil unless other.instance_of? Bottle @ounces <=> other.ounces 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 Methods and variables 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 def Bottle.sum(bottles) total = 0 bottles.each {|b| total += b.ounces } total def to_s "(#@label, #@ounces)" def self.report #class method puts "Number of bottles created: #@@numBottles" 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 Not covered: Class Instance Variables
Topic Summary Language Concepts Ruby 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
Additional Topics Not covered, but may be useful dup clone initialize_copy marshalling (create from serialized data)