#### 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