#### duck typing ####
# (first, see lec20.rb for how double1 and double2 make different
# assumptions about their arguments)
# silly example
def foo(x,y)
x.m(y) + y.n(3,"x")
end
# good that callee does not require particular classes -- more reuse
# but makes it much harder to find equivalent bodies
#### block, iterators, Procs ####
def play
# many library methods "take a block", which is essentially a closure
# avoids many uses of explicit loops
# (more concise, separates traversal from processing)
3.times { puts "hi" }
[4,6,8].each { puts "hi" } # can "ignore" argument
y = 7
[4,6,8].each { |x|
y = y + x
puts y
}
arr = [4,6,8,10]
arr2 = arr.map { |x| x + 1 }
puts "map result "
puts arr2
sum = arr.inject { |acc,elt| acc + elt }
puts "inject result "
puts sum
puts (arr.any? { |elt| elt < 0 })
# if only the immediate callee needs the block,
# convenient to use the "yield" language feature (how the methods
# above are implemented)
puts (foo { |x| x + x })
# lambda is a built-in method that returns a Proc that is
# _exactly_ a closure, you call it with the call method
cl = lambda {|z| z * y}
q = cl.call(9)
puts q
# since Proc values can be passed around, often
# what you need (e.g., on homework) (see foo, bar below)
puts (foo2 (lambda { |x| x + x }))
# learn on your own only if you want: using "& argument"
# so caller uses a block (not a Proc) but callee gets a Proc
# (how lambda is implemented -- just a method of Object)
end
def foo
eight = yield 4
twelve = yield 6
eight + twelve
end
def foo2 fun
eight = fun.call 4
twelve = bar fun
eight + twelve
end
def bar f
f.call 6
end
#### inheritance and overrding ####
class Point
attr_reader :x, :y
attr_writer :x, :y
def initialize(x,y)
@x = x
@y = y
end
def distFromOrigin
Math.sqrt(@x * @x + @y * @y) # why a module method? Less OO :-(
end
def distFromOrigin2
Math.sqrt(x * x + y * y) # uses getter methods
end
end
#hmm, without overrding "why not" just add a method to Point instead?
# design choice, really (but adding would affect ThreeDPoint too!)
class ColorPoint < Point
attr_reader :color
attr_writer :color # just our choice to make mutable like this
# (say p.color = "green" rather than needing
# p.myColorSetter("green") which does @color="green" in its body)
def initialize(x,y,c="clear") # or could skip this and color starts unset
super(x,y)
@color = c
end
end
# design question: "Is a 3D-point a 2D-point?"
class ThreeDPoint < Point
attr_reader :z
attr_writer :z
def initialize(x,y,z)
super(x,y)
@z = z
end
def distFromOrigin
d = super
Math.sqrt(d * d + @z * @z)
end
def distFromOrigin2
d = super
Math.sqrt(d * d + z * z)
end
end
class PolarPoint < Point
# interesting: by not calling super constructor, no x and y field
# (in Java or Smalltalk would just have unused x and y fields)
def initialize(r,theta)
@r = r
@theta = theta
end
def x
@r * Math.cos(@theta)
end
def y
@r * Math.sin(@theta)
end
def x= a
b = y # avoids multiple calls to y method
@theta = Math.atan(b / a)
@r = Math.sqrt(a*a + b*b)
self
end
def y= b
a = y # avoid multiple calls to x method
@theta = Math.atan(b / a)
@r = Math.sqrt(a*a + b*b)
self
end
def distFromOrigin
@r
end
# distFromOrigin2 already works!!!
end