Com S 541 Lecture -*- Outline -*- * Means of Abstraction in Scala based on: scala.epfl.ch ** functional abstractions *** function closures ------------------------------------------ ANONYMOUS FUNCTIONS x => x x: Int => x + 1 (y: Int) => { return y - 1 } (x: Int, y: Int) => (x + y /2) ------------------------------------------ Q: What's the meaning of these? parentheses around arguments are only optional if have exactly 1 argument can use a block for the body can use return if desired, but not needed *** method closures ------------------------------------------ METHOD CLOSURES Sugars: .head ==> (x => x.head) .drop(i) ==> (x => x.drop(i)) E.g., def column[T](xss: List[List[T]], n: int) : List[T] = xss.map(.drop(i)).map(.head) ------------------------------------------ Q: Does this take methods out of an object and give up dynamic binding? No, it's just a convenient sugars, not pointers to individual methods! ** Declarations and Definitions Q: What's the difference between a declaration and a definition? *** value ------------------------------------------ VALUES Declarations (val x:T) val half: Double; val quarter, eighth: Double; Value definitions (val x:T = E): val half = 1.0/2.0; val quarter: Double = 1.0/4.0; val one, uno = 1; With pattern matching: val x :: xs = mylist; ------------------------------------------ The value of the expression is computed once and stored. It can't be changed (like Java final). Q: What's the value of "one" after the definition above? 1 Type is inferred for definition if not given. Type must be present in declaration. *** variable ------------------------------------------ VARIABLES variable declarations (var x:T): var frac: Fraction; variable definitions (var x:T = E): var f = new Fraction(1,2); var i: Int = _; // default init. Client's view: var x: T; looks like: def x: T; // getter def x_= (y: T): Unit; ------------------------------------------ The value of a definition is computed and stored, but can be changed by assignment. Q: What's the advantage of having the client's view be two functions? *** functions or methods ------------------------------------------ FUNCTIONS (METHODS) declarations: def ip2: Int; def inc(x: Int): Int; def update(i: Int, e: T): Unit; def append(elems: T*): List[T]; def ifFalse(tst: Boolean, body: => Unit): Unit; definitions def ip2 = i + 2; def inc(x: Int): Int = x + 1; def ifFalse(tst: Boolean, body: => Unit): Unit = { if (!tst) body } def sum(args: int*) = { var result = 0; for (val arg <- args.elements) result = result + arg; result } ------------------------------------------ a method is a function inside an object. methods that aren't final or private are dynamically dispatched Can omit result type, must give it if the function is recursive ------------------------------------------ CURRYING Curried functions can be written: def cadd(x: Int)(y: Int): Int = x + y You can partially parameterize functions, even if not curried: (0 ==) But can't uncurry implicitly: cadd(3, 4) // error! ------------------------------------------ To explain what happens with zero argument functions, and the use of (), consider the following. Note that ip2 is run as ip2 but ip2() is an error And that ip3 is run as ip3() and ip3 simply produces a closure. package misc; /** Testing of zero argument functions. */ object ZeroArgFunTest { var i: Int = 0; def ip2 = { Console.println("ip2 called"); i = i+2; } def ip3() = { Console.println("ip3 called"); i = i+3; } def main(args : Array[String]) : Unit = { ip2; Console.println("'Call' to ip3, no args..."); ip3; Console.println("Call to ip3() ..."); ip3(); Console.println("i = " + i); } } *** type declarations and aliases ------------------------------------------ TYPE DECLARATIONS AND ALIAS DEFINTIONS declarations: type Elems; type IntList; type T <: Comparable[T]; type Two[a] = Tuple2[a,a]; type alias definitions: type void = Unit; type IntList = List[Int]; type Two[a] = Tuple2[a,a]; class and trait defintions class Counter(v: Int) { /* ... */ } trait Countable; ------------------------------------------ can also have lower bounds, with >: can't have recursive aliases, but can have bounds that are recursive can't use variance annotations in aliases ** class, trait, and object definitions classes and traits define types traits are abstract classes with some restrictions objects are instances of classes, not types (just values) *** class definitions these are also essentially function definitions! ------------------------------------------ CLASS DEFINITIONS package misc; /** Simple counters. */ class Counter(v: Int) { /** Increment above the initial value.*/ private var incr: Int = 0; /** Return this counter's value. */ def value: Int = v + incr; /** Increment the counter. */ def inc: Unit = { incr = incr+1 } } ------------------------------------------ add an override of toString base class is Scala.AnyRef (= java.lang.Object) by default **** constructors Explain the constructor, generates a value, not a variable, hence the need for incr. If you add "val" to the names in a constructor, then these names are defined as implicit accessors. ------------------------------------------ CONSTRUCTORS Additional ones named "this": this() = { this(0) } ------------------------------------------ **** members Methods defined/declared using "def" Can also provide values with "val", or variables (var) Q: Which members are abstract? Members are public by default (public is not a keyword!) can make protected or private (no package visibility notion) Overrides of defs need to use "override" Q: How would you add a toString override? override def toString() = { "Counter(" + value + ")" } Can also nest classes (and objects, packages, etc.) **** Classes are types Classes are types = sets of values with operations Q: What declaration would you give to say that you want a nested class to be defined in a subclass, but aren't going to define it? a type declaration **** abstract members and classes abstract = can't be instantiated If the class has declarations that aren't definitions it must be declared to be abstract *** object definitions singletons that define the equivalent of static methods in Java the main method must be in an object. ------------------------------------------ OBJECT DEFINITIONS I.e., singletons package misc; object CounterMain { def main(args: Array[String]): Unit = { val c = new Counter(3); Console.println(c); c.inc; c.inc; Console.println("c.value = " + c.value); } } ------------------------------------------ objects are values, not types **** initialization ------------------------------------------ CLASS AND OBJECT INITIALIZATION Objects are initialized lazily, when the object is first dereferenced When a constructor called: 0. all val, var members set to _ (default) 1. constructor for superclass is executed, 2. statements in the class body executed, in order ------------------------------------------ Example: package misc; /** Example illustrating order of evaluation in superclass bodies. */ class ClassBodySuper(i: Int) { Console.println("Statement at start of ClassBodySuper(" + i + ")"); val superx = { Console.println("superx init"); i } Console.println("superx = " + superx); val supery = { Console.println("supery init " + i); i } var superz = { Console.println("superz init " + i); i } Console.println("Statement at end of ClassBodySuper"); } /** Example illustrating order of evaluation in class bodies. */ object ClassBody extends ClassBodySuper(3) { Console.println("Statement at start of ClassBody"); val x = Console.println("x init"); override val superx = { Console.println("override of superx init"); 4 } Console.println("superx = " + superx); def main(args: Array[String]): Unit = { Console.println("Running main in ClassBody"); x; y; z; f; Console.println("making a new ClassBodySuper object"); g; } val y = Console.println("y init"); var z = Console.println("z init"); def f = Console.println("f run"); def g = new ClassBodySuper(1); Console.println("Statement at end of ClassBody"); } Output: Statement at start of ClassBodySuper(3) superx init superx = 0 supery init 3 superz init 3 Statement at end of ClassBodySuper Statement at start of ClassBody x init override of superx init superx = 4 y init z init Statement at end of ClassBody Running main in ClassBody f run making a new ClassBodySuper object Statement at start of ClassBodySuper(1) superx init superx = 1 supery init 1 superz init 1 Statement at end of ClassBodySuper *** More examples (skip if not needed) Work the Rational example from Scala by Example (ch 6) or do Interval or Person