/* CS:4420 Artificial Intelligence Spring 2017 The University of Iowa Instructor: Cesare Tinelli */ /* Scala examples seen in class */ /*-------------*/ /* Basic types */ /*-------------*/ 3 3 + 5 4 - 2 3.0 // some symbols are overloaded 1.4 + 3.0 // implicit conversion 1.4 + 3 // syntactic sugar for 1.4 + 3.toDouble "hi" "hi" ++ " there" // ill-typed "hi" ++ 3 "hi" + " there" // implicit conversion "high " + 5 // abbreviates "high " + 5.toString // functionally equivalent to "high " ++ "5" // implicit conversion and left associativity of + "hi" + 3.4 + 1 // implicitly converted to ("hi" + 3.4.toString) + 1.toString 3.4 + 1 + "hi" // implicitly converted to (3.4 + 1.toDouble).toString + "hi" true ! true true && false true || false 3 == 5 - 2 3 == 3.0 // implictly converted to 3.toDouble == 3.0 3 == "3" /** no implicity conversion here! **/ /* tuple types */ (1,3) (3,4,5) (3,"pp",4.3) ((1,2),3) == (1,(2,3)) // tuples of different type ((1,2),3) == (1,2,3) // tuples of different type /* Unit type */ () val a = () /* values and variables */ val a = 3 // declares symbolic value a as a synonym for 3 a a = 4 // error, symbolic values are immutable var b = 4 // declares a variable b b = 0 // variables are mutable /* Type ascription */ val a:Int = 4 // prescribes right hand-side expression to have type Int val a:Int = 4.7 // will generate a type error val a:Float = 4 // will convert 4 to 4.0 val a:String = "abc" val a:String = 3 // type error 3:Int (3:Int) + 1 3:Long // implicitly converted to (3.toLong):Long (3:Long) + 1 // implicitly converted to ((3.toLong):Long) + 1.toLong 3:String // failed implict conversion, type error /* methods */ // methods with automatically inferred return type def f1 (n:Int):Int = n + 1 val a = 3 f1(a) def f2 (n:Int) = n + 1.5 f2(3) // method with return type Unit def g (n:Int) = println("Input value was " + n) // alternative syntax for Unit returning method def g (n:Int) { println("Input value was " + n) } // g is interesting for its side effect (printing something), // not for its returned value val x = g(4) def max (x:Int, y:Int) = if (x > y) x else y // parsed incorrectly // 'if' statement must be in a single line or // enclosed in a block (see later) def max (x:Int, y:Int) = if (x > y) x else y // correct syntax def max (x:Int, y:Int) = { if (x > y) x else y } var c = 3 /*------------------*/ /* Basic Constructs */ /*------------------*/ // The if construct is an expression. // It can be seen as a mixfix operator with three arguments: // // if (_) _ else _ // // The first argument must be of type Boolean. // The second and third argument must be of the same type. // The type of an if expression is determined by the type of its // second and third arguments // val k = if (c > 0) 10 else 20 // if expressions can go everywhere an expression of can go (if (c > 0) 1 else 0) + 7 // if the left and right branches are not of the same type // they are implicitly converted the their closest common supertype // (roughly speaking) if (true) 1 else 2.0 (if (true) 1 else "f") * 2 /* expressions with side effects */ // assignments are also expressions ... c = 4 // ... of type Unit :type (c = 4) val w = (c = 0) // this looks a lot like Jave code but it is an expression // (of type Unit) if (c > 0) c = c + 1 else c = c - 1 /* sequential composition of expressions */ // expressions can be composed and made into a block {3; 4} // block evaluates to and has the type of its last expression // in the sequence // a new line separator can be used instead of ; { 3 4 } // zero or more espressions can be composed {} // of type unit {3} {3; 4; "a"} // blocks are expressions and so can be nested {{3; 4}; 5} {3; {4; 5}} // composition is most useful with expressions with side effect, // such as assignments {c = 40; c} // changes c and returns its value val x1 = {c = 42; c - 2} // x1 is an Int c val x2 = {2; c = 41} // x2 is a Unit c // since blocks are expressions, they can be used as arguments // in method calls (not recommended for clarity though) f1({c = 3; 2}) c // Scala has while "statements" too. // As in the case of if, they are actually expressions of type Unit. // While can be seen a mixfix operator with two arguments: // // while (_) _ // // the first argument takes an expression of type bool, // the second takes any expression var j = 10 while (j > 10) 5 val u = while (j > 0) {j = j - 1; 5} // the second argument of while can be a block j = 6 while (j > 0) { println(j) j = j - 1 } // a block of two expressions, both of type Unit { j = 8 while (j > 0) { println(j) j = j - 1 } } // The body of a method can be a block def print_range(m:Int, n:Int) = { var i = m println() while (i <= n) { print(i + "\n" ) i = i + 1 } println() } print_range(4,9) // imperative-style definition of factorial function def fact (n:Int) = { var i = n var f = 1 while (i > 0) { f = f * i i = i - 1 } f } // functional style, recursive definition of factorial def rfact (n:Int):Int = if (n <= 1) 1 else n * rfact(n-1) // declaring the return type of recursive methods is mandatory def rfact (n:Int):Int = { if (n <= 1) 1 else n * rfact(n-1) } /*---------*/ /* Scoping */ /*---------*/ /* lexical scoping rules */ var a = 10 def f (x:Int) = a + x f(4) // *new* immutable variable a, distinct from the previous one val a = 100 // the new a shadows the old one a // however, the old one is unchanged f(4) // blocks can contain both expression *and* (variable/method) definitions // variable k1 is visible only in the block in which it is declared {val k1 = 4 k1*k1 } k1 // error // within {val x = 4 val x = 5 x } // local declarations shadow less local ones val k = 1 {val k = 2; {val k = 3 println(k) } println(k) } println(k) // there is no shadowing within the same block though {val x = 4 val x = 5 // will generate an error x } // method definitions can have local scope too {def h(x:Int) = x + 1 h(9) } h(9) // error // this implies that methods can be defined locally // within other methods def f (x:Int) = { def square (x:Int) = x * x def double (x:Int) = x + x square(x) - double(x) } /* -----------------*/ /* Pattern matching */ /* -----------------*/ val t = (3,5,2) // declare three new variables x1,x2,x3; // the value of xi is the value of t's i-th component; // the pattern (x1,x2,x3) is matched against (3,5,2) val (x1,x2) = t // _ is for "don't care" positions val (y1,y2,_) = t // pattern matching can be used with nested patterns val t = ((2,3),5,(6,2)) val (_, x, _) = t val (_, _, (x,_)) = t // this will give an error: patterns should have no repeated variables val (x,x) = (24,24) // patterns are extremely useful with the match construct: // // (_ match {case _ => _ ... case _ => _}) // // the first argument of match is match agains the pattern given // between case and => ; // patterns are checked one at a time, top-down; // the value of the whole match construct is the value of the expression // on the right of the matching pattern var n = 11 n match { case 0 => "zero" case 1 => "one" case 3 => "other" } // All right-hand sides of a 'case' clause must be of the same type // if all cases fail an exception is raised n match { case 0 => "zero" } // match and patterns allow one to write cleaner and more compact code def convert (n:Int) = n match { case 0 => "zero" case 1 => "one" case _ => "other" } // the bodies of the case statements can be any expression (in particular a block) // but, like in the the two branches of the if construct, they all have to be of // same type def p (n: Int) = n match { case 0 => { print("You entered ") println("zero") } // type of this is Unit case _ => { print("You did not enter ") println("zero") } // type of this is Unit } // patterns can be complex and nested var v = (1, 2) v match { case (0, 0) => "a" case (0, _) => "b" case (_, 0) => "c" } // bothZero returns "OK" if both of its arguments are 0 // Otherwise it returns "NotOK" // common (bad) coding style def bothZero (x1:Int, x2:Int) = { if (x1 == 0) { if (x2 == 0) "OK" else "NotOK" } else "NotOK" } // same method but using match def bothZero (x1:Int, x2:Int) = (x1, x2) match { case (0, 0) => "OK" case _ => "NotOK" } // the input here is already a pair that can be pattern matched // directly def bz (p:(Int, Int)) = p match { case (0, 0) => true case _ => false } def and (p:(Boolean, Boolean)) = p match { case (true, y) => y case (false, _) => false } // the scope of a pattern variable in a case is the body of that case def and (p:(Boolean, Boolean)) = p match { case (true, y) => y case (false, _) => y // y is undefined here } // pattern variables shadows variables in outer scopes with the same name def f1 (x:Int, y:Int) = x match { case 0 => y // input y case y => x + y // new y! Gets the value of x } // pattern matching against the **value** of a variable can be done // by back-quoting the variable def f2 (x:Int, y:Int) = x match { case `y` => x + 1 // will match if x has the same value as y case _ => 0 } // factorial with match def fact (n:Int):Int = n match { case 0 => 1 case m => m * fact(m - 1) } // you can match several values at the same time by putting them into a tuple (0 < 1, 1 == 1) match { case (true, y) => y case (false, _) => false } // patterns can be augmented with conditional guards def f2 (x:Int, y:Int) = (x, 2*x, 3*y) match { // will match if x is 3 *and* v1 and v2 have the same value case (3, v1, v2) if v1 == v2 => "same v1, v2" case (3, v1, v2) => "different v1, v2" case _ => "x not 3" } // alternative patterns can be given for the same case, separated by | // however, they cannot contain variables other than _ def and (p:(Int, Int)) = p match { // first case matches if the first component of p is either o or 1 case (0, _) | (1, _) => 1 case (_, y) => y } // the match construct too builds expressions val r = (0 < 1, 1 == 1) match { case (true, y) => y case (false, _) => false } /*--------------------*/ /* Collections: Lists */ /*--------------------*/ // lists are finite sequences of elements val l = List(1, 2, 3) val l = List(1, true) // Main list methods l.head l.tail l.length // individual elements can be accessed with this syntax // with indices starting at zero (like in arrays) l(0) // first element of l l(3) // fourth element of l // a list can be built with elements of *any* type, // but all elements must be of the *same* type List('a', 'b') List("hi", "there") List((1, 2), (1, 4)) List(List(1, 2), List(1, 4, 3)) List(1, "a") // types as a list of Any val l1 = List(1, 2, 3) val l2 = List(1, 2, 3) // list identity l1 eq l2 val l3 = l1 l1 eq l3 // structural equality l1 == l2 // order and number of occurrences of elements matters for equality List(1,2,3) == List(2,1,3) List(1,2) == List(1,2,2) // Empty list, prints as List() Nil // Evaluates to Nil List() Nil eq List() // bad practice, gives you a list of elements of type Nothing. val l = List() // recommended, specify the element type of an empty list explicitly val il = List[Int]() var x = List("a", "b") // Causes an implicit conversion of 1 and 2 to Long. var y = List[Long](1, 2) // if T1 and T2 are different types, // List[T1] is a different type from List[T2] // no implict conversions are defined for them x = il y = il // Lists are built by increasingly adding elements in front // of Nil with the "cons" operator :: val x = 6 :: Nil val y = 5 :: x // note that x is unchanged x y.tail eq x // :: is right associative. 3 :: 4 :: Nil // same as 3 :: (4 :: Nil) // List(3, 4) can be understood as syntactic sugar for 3 :: (4 :: Nil) 3 :: 4 // error, right-hand argument not a list // can mix and match notation 3 :: List(4) // note type here List(7) :: Nil val l = List(1, 2, 3) // invariant defining head and tail in terms of :: // for any non-empty list // (l.head :: l.tail) == l // pattern matching also works with :: val h :: t = l // why it works: l is really 1 :: (2 :: (3 :: Nil)) val h :: t = 1 :: (2 :: (3 :: Nil)) // deeper patters for lists with at least two elements val x1 :: (x2 :: t) = l val x1 :: x2 :: x3 :: t = l val x1 :: x2 :: x3 :: x4 :: t = l // fails because l has < 4 elements // 'List' syntax can also be used for pattern matching val List(x1, x2, x3) = l // fails because l has length 3 val List(x1) = l // also fails val List(x1, x2) = List("a") // recursive definition of length function for lists // based on navigating the list structure. // If l is empty its length is 0. If l is non-empty, // and so has a tail t, its lenght is 1 + the length of t // def len (l: List[Int]): Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } len(List(1, 1, 1)) // min returns the minimun element of an non-empty integer list, // it raises an exception with empty lists // // if l has only one element n, the minimum is n // if l has at least two elements its miminum is the smaller // between l's head and the minimun of l's tail. // def min (l:List[Int]):Int = l match { case Nil => throw new IllegalArgumentException("empty list") case n :: Nil => n case n :: t => { val m = min(t) if (n < m) n else m } } // concat contatenates two lists def concat(l:List[Int], m:List[Int]):List[Int] = l match { // when l is empty, its concatenation with m is just m case Nil => m // when l has a head h and a tail t, its contatenation with m // is obtained by inserting h in front of the contatenation // of t with m case h :: t => h :: concat(t, m) } concat(List(1, 2), List(3, 4, 5)) // the effect of concat is achieved with the predefined overloaded operator ++ List(1,2) ++ List(3,4,5) // reverse reverses a list def reverse (l:List[Int]):List[Int] = l match { // when l is empty it is its own reverse case Nil => l // when l has a head h and a tail t, its reverse is the contatenation // of the reverse of t with a list containing just h case h :: t => { val rt = reverse(t) val lh = List(h) concat(rt, lh) } } reverse(List(3, 4, 5)) // more compact version of the implementation above // both version are inefficient because each reverse(t) is // scanned linearly by concat each time def reverse (l:List[Int]):List[Int] = l match { case Nil => l case h :: t => concat(reverse(t), List(h)) } // this reverse uses a local auxiliary function rev // rev is "tail-recursive", it builds the reverse of l as it scans it // by incrementally moving its elements into an initially empty list rl. // l is scanned only once def reverse (x:List[Int]):List[Int] = { def rev (l:List[Int], rl:List[Int]):List[Int] = l match { // when l has a head h and a tail t, put h in front of rl // and continue with t and h::rl case h :: t => rev(t, h :: rl) // when l is Nil, rl contains by construction the reverse // of the original list case Nil => rl } rev(x, Nil) } /*---------------------*/ /* Collections: Arrays */ /*---------------------*/ // creates an array with 5 elements and calls it a val a = new Array(5) // more typically, one specifies the element type as well; // the array elements start with a type-dependent default value val a = new Array[Double](5) a[1] // standard notation *not* used in Scala a(1) // Scala's notation for array access a(0) = 2.5 // array update a(5) // generates out of bound error // generates an Array[Int] of length 3 and elements 10,11, and 12; // element type Int is inferred Array(10, 11, 12) val a = Array(10, 11, 12) // 3 different ways to generate an Array[Long] // from Int initial values val a = Array[Long](10, 11, 12) // preferred one val a:Array[Long] = Array(10, 11, 12) val a = Array(10:Long, 11, 12) // note the tricky difference val a = Array[Long](10) // a is an Array[Long] with one element: 10 val b = new Array[Long](10) // n is an Array[Long] with 10 elements // array length a.length // multidimentional arrays are arrays of arrays val m = new Array[Array[Int]](10) val m = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9), Array(10, 11, 12)) m(2)(1) // no special syntax for matrix access /*-------------------------*/ /* More control structures */ /*-------------------------*/ /* for loops */ // The for loop is an expression of type Unit // It can be seen as a mixfix operator with two arguments: // // for (_) _ // // The second argument (the "body") is any expression. // The first argument is a sequence of one or more *generators* // separated by ';' // // A generator is an expression of the form _ <- _ where the first // argument is a pattern and the second is an expression of a collection // type (Range, List, Array, ...) // below, the pattern 3 is matched against each element of the list. // each time there is a match, the body of for is evaluated for (3 <- List(1,3,4,3,5,3,6,3)) println("Hi") // as in the match and val constructs, patterns can contain variables for (i <- List(1,2,14,52,7,10)) println("high five") // any variables in the pattern have local scope within the for for (i <- List(1,2,14,52,7,10)) println("i = " + i) i // gives an error // patterns in a generator can be arbitrarily complex, depending // on the type of the collection val l = List((1,2), (1,4), (6,7), (3,6)) for ( (i, j) <- l ) println("i = " + i + ", j = " + j) for ( (i, _) <- l ) println(i) // ignores the second component of each pair for ( (1, j) <- l ) println(j) // only matches pairs starting with 1 // *any collection* type can be used in a generator for (i <- Array(1.2, 2.5, 4.5, 7.1)) println("i = " + i) for (i <- Set(1,2,4,5,7,10,4)) println("i = " + i) // even strings, which are seen as the collection of their characters for (i <- "abcd") println("i = " + i) // ranges are sequences of consecutive integers val r = Range(1,5) // contains values 1, 2, 3, 4 for (i <- r) println("i = " + i) // Expression 1 to 4 is the same as Range(1,5) for (i <- 1 to 4) println("i = " + i) // Putting more than one generator has the effect of nested for loops for (i <- 1 to 2; j <- 1 to 3) println("i = " + i + ", j = " + j) // the above for has the same effect as for (i <- 1 to 2) { for (j <- 1 to 3) println("i = " + i + ", j = " + j) } // and it can also be written as for { i <- 1 to 2 j <- 1 to 3 } println("i =" + i + ", j = " + j) // a generator can be optionally followed by a *filter* of the form: if b // where b is a Boolean expression. // the matched value is used if and only if b evaluates to true for (i <- List(1,9,2,4,5,7,10) if i > 4) println("i = " + i) val l = List((1,2), (1,4), (6,7), (3,6)) for ( (i,j) <- l if j == 2*i ) println((i,j)) for { c1 <- "hi" c2 <- "fifi" if c1 == c2 } println("character match found for letter " + c1) // BTW, matching filters can be used with the match construct too val l = List(2, 1, 3) l match { case x :: y :: t if (x > y) => y :: x :: t case _ => l } // the first case applies only if l has at least two elements *and* // its first element is greater than the second // this version of reverse scans the input list x using // a for loop instead of a recursive call, // each element of x is inserted into an initialy empty list; // a mutable variable is necessary to store the growing reversed list // def reverse(x:List[Int]):List[Int] = { var l = List[Int]() for (h <- x) l = h :: l l } /*-----------------------------*/ /* Classes and class instances */ /*-----------------------------*/ /* User-defined classes */ // One can define new classes with the construct of the form // // class Name(a1,...,an) { ... } // // where Name is an identifier for the class, // a1, ..., an for n >= 0 are any values // { ... } is the "body" of the class definition class A() {} // equivalently: class A {} // or even class A // Every class C defines a type C // Each *instance* of a class C, which we call an object, // is a value of type C // objects are created with the construct (new _) new A val a1 = new A val a2 = new A a1 == a2 a1 eq a2 // The body of a class declaration is a block and // can contain anything allowed in a block // Any declarations within the body have only local scope in there class B { var x = 1 val s = "abc" def m(n: Int) = x + n } // variables in a class are also called "fields" // The fields of an instance of class B constitute's // the object's *internal state*. // Fields and methods can be accessed using the common // dot notation val b = new B b.x b.s b.m(10) // the value stored in a mutable field can be modified // with assignments b.x = 7 b.m(10) // Whatever expressions are in a class' body they are // evaluated at object creation time // The effect of this evaluation is a new object class B { println("Starting creating new object of class B") var x = 1 val s = "abc" def m(n: Int) = x + n def g = 33 // parameterless method println("Done creating new object of class B") } new B /* Operational difference between fields and parameterless methods */ // The value of an object's field is determined at object // creation time. In contrast, a parameterless method is // still a method, so its returned value is computed // anew each time the method is called. class A { var x = 0 val y = x + 1 // y will be initialized to 1 and stay so def f = x + 1 // the value returned by f each time depends // on the current value of x } val a = new A a.x a.y // evaluates to 1 a.f // evaluates to 1 a.x = 4 a.y // still evaluates to 1 a.f // evaluates to 5 // A class declaration can have input parameters class Point(x0:Double, y0:Double) { val x = x0 val y = y0 } val p = new Point(4.5, 3.9) p.x p.y // input parameters are used only at object creation time p.x0 // error // but they can turned into fields as in the example below class Point(val x:Double, val y:Double) {} // x, y are both input parameters and (immutable) fields val p = new Point(6.5, 3.1) p.x p.y // "class Point(val x:Double, val y:Double) {}" is just // an abbreviation for a declaration of the form class Point(x0:Double, y0:Double) { val x = x0 val y = y0 } // Fields and methods are exposed by default: they are "public" // They can be hidden inside by declaring them as "private" // private fields and methods are visible only by objects of the same class class Point(x0:Double, y0:Double) { private val x = x0 private val y = y0 def coincidesWith (other: Point) = (x == other.x) && (y == other.y) // other.x and other.y are visible by current point } val p1 = new Point(6.5, 3.1) p1.x // error val p2 = new Point(6.5, 3.1) p1.coincidesWith(p2) p1 == p2 // note less verbose alternative notation p1 coincidesWith p2 // more abbreviations: class Point(private val x:Double, private val y:Double) { } // abbreviates something like class Point(x0:Double, y0:Double) { private val x = x0 private val y = y0 } // inside a class method definition the object that receives the calls // can be referred to by 'this' class Point(private val x:Double, private val y:Double) { def coincidesWith (other:Point) = (this.x == other.x) && (y == other.y) // 'this.x' is the same as just x def sameAs (other:Point) = this eq other } val p1 = new Point(6.5, 3.1) val p2 = new Point(6.5, 3.1) p1 sameAs p2 p1 sameAs p1 val p3 = p1 p1 sameAs p3 // 'this' can be used also to define alternative object constructors class Point(val x:Double, val y:Double) { // new object constructor, taking only one parameter def this(x: Double) = this(x, x) // call to basic constructor, implicitly defined // by parameters (x0:Double, y0:Double) above // another constructor, taking no parameters def this() = this(0.0) // call to unary constructor above } val p1 = new Point(4.1, 5.9) val p2 = new Point(1.7) val p3 = new Point p2.x p2.y p3.x p3.y // methods called 'apply' can be called with shorthand syntax class Point(private val x:Double, private val y:Double) { def apply(i:Int) = i match { case 0 => x case 1 => y case _ => throw new IllegalArgumentException("index out of bound") } } val p = new Point(4.6, 9.1) p.apply(0) // can be abbreviated as p(0) // operator symbols like +, -, ++, *, **, etc. can be used // as method names in user-defined classes class Point(private val x:Double, private val y:Double) { def +(other: Point) = new Point(x + other.x, y + other.y) def -(other: Point) = new Point(x - other.x, y - other.y) def apply(i:Int) = i match { case 0 => x case 1 => y case _ => throw new IllegalArgumentException("Index out of bound") } } val p1 = new Point(4.0, 9.0) val p2 = new Point(3.0, 1.0) val p = p1.+(p2) p(0) p(1) // leaner syntax p1 + p2 // user-defined binary operators are left-associative by default p1 - p2 - p2 // same as (p1 - p2) - p2 // Scala is pervasively object-oriented // *every value* is an object of some class // All basic types (Boolean, Int, Double, Unit, ...) are classes // Their elements are objects with predefined methods // Basic operators are actually all methods // E.g. val x = 4 // as with user defined classes x + 2 is syntactic sugar for x.+(2) // i.e. + is a method of class Int that takes an integer // (the second operand of +) and returns its addition with // the receiving object (the first operand) // 3.2 - 1.7 is syntactic sugar for 3.2.-(1.7) // true && false is syntactic sugar for true.&&(false) // x == 3 is syntactic sugar for x.==(3) // Tuples, Lists, Arrays types are also all classes val l = List(10, 11, 12) // l is an object // l ++ l is syntactic sugar for l.++(l) // 9 :: l is syntactic sugar for l.::(9) // note the opposite order of the arguments // :: is a List method, not an Int method // any predefined or user-defined binary operator starting with ':' // is treated as a method of its *second* argument as above // l(1) is syntactic sugar for l.apply(1) // head, tail, length are all parameterless methods l.head l.tail l.length // All tuple types (e.g., (Int, String) ) are classes val p = (100, "abc") // _1 and _2 are methods of the (Int, String) class p._1 p._2 /*-------------*/ /* Inheritance */ /*-------------*/ // require // // The predefined method 'require' takes as input a Boolean value b // It throws the exception IllegalArgumentException if b is false, and // just returns () otherwise val a = 4 require(a > 8) // throws IllegalArgumentException require(a > 2) // evaluates to () // 'require' is useful to check (at runtime) that // a method's input values satisfy certain restrictions // It is best put at the beginning of a method's or class' body // With classes, it is executed at object creation time /* Subclassing and inheritance */ import scala.math // importing the math package // Class Point models points on a Cartesian plane // Fields x and y store the Cartesian coordinates of the point class Point(val x:Double, val y:Double) { // returns straight-line distance from other point def distanceFrom(other:Point) = { val a = x - other.x val b = y - other.y math.sqrt(a*a + b*b) } // returns true iff this and the other point // have the same coordinates def coincidesWith(other:Point) = x == other.x && y == other.y } val p1 = new Point(0, 0) // note implicit conversion of integer arguments to Double val p2 = new Point(2, 1) val p3 = new Point(3, 0) // Class Segment models segment on a plane // Fields start and end represent the segment's endpoints class Segment(val start: Point, val end: Point) { // Initialization requirements: // - start and end should not coincide require(!(start coincidesWith end)) // Field length stores the length of the segment // Since the segment is immutable, the length can be computed // once and for all at object creation time val length = start distanceFrom end // Field slope stores a Double pair (n,d) where n/d is // the slope of this Segmentment private val slope = (end.y - start.y, end.x - start.x) // returns the distance of input point p to this segment def distanceFrom(p:Point) = { val x0 = p.x; val y0 = p.y val x1 = start.x; val y1 = start.y val x2 = end.x; val y2 = end.y val n = math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) val d = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)) n / d } // returns true iff this segment's end point coincides // with the other segment's start point def consecutiveWith (other:Segment) = end coincidesWith other.start // returns true iff this and the other segment are parallel def parallelWith (other:Segment) = { val (n1, _) = slope val (n2, d2) = other.slope n1 * d2 == n2 * d1 // evaluates to true iff this and other have the same slope } // returns true iff this and the other segment are perpendicular def perpendicularWith (other:Segment) = { val (n1, d1) = slope val (n2, d2) = other.slope n1 * n2 == -d2 * d1 // evaluates to true iff this and other have orthogonal slopes } } // Abstract class ClosedShape models polygons // Field sides stores a list of segments that are // the sides of the ClosedShape. abstract class ClosedShape(val sides:List[Segment]) { // Initialization requirements: // - The sides list must contain at least three segments require( sides.length > 2 ) // - the segments in sides must be consecutive require( consecutive(sides) ) // - the end point of the last segment must coincide // - with the start point of the first require( sides.last.end == sides.head.start ) protected def consecutive(l:List[Segment]):Boolean = l match { case s1 :: s2 :: t => s1.end == s2.start && consecutive(l.tail) case _ => true } protected def len (l: List[Segment], n:Double):Double = l match { case Nil => n case s :: t => len(t, s.length + n) } // returns the perimeter of this ClosedShape val perimeter = len(sides, 0.0) } val seg1 = new Segment(p1,p2) val seg2 = new Segment(p2,p3) val seg3 = new Segment(p3,p1) val pol = new ClosedShape(List(seg1,seg2,seg3)) // error, can't create instances of abstract classes // Class Triangle models triangles as closed shapes with three sides class Triangle (s1:Segment, s2:Segment, s3:Segment) extends ClosedShape(List(s1,s2,s3)) {} val seg1 = new Segment(p1,p2) val seg2 = new Segment(p2,p3) val seg3 = new Segment(p3,p1) val t1 = new Triangle(seg1,seg3,seg2) // error, segments are not consecutive val t1 = new Triangle(seg1,seg2,seg3) // Class Quadrangle models quadrangles as closed shapes with 4 sides class Quadrangle(s1:Segment, s2:Segment, s3:Segment, s4:Segment) extends ClosedShape (List(s1,s2,s3,s4)) {} // Class Parallelogram models parallelograns as special Quadrangles // It adds additional features which are well defined for parallelogram // but not for arbitrary polygons class Parallelogram(s1:Segment, s2:Segment, s3:Segment, s4:Segment) extends Quadrangle (s1, s2, s3, s4) { // Initialization requirements: // - non-consecutive sides are parallel require( s1 parallelWith s3 ) require( s2 parallelWith s4 ) val width = s1.length // Calling a method from the super class. val height = s1 distanceFrom s2.end val area = height * width } val p1 = new Point(1,0) val p2 = new Point(5,0) val p3 = new Point(6,4) val p4 = new Point(2,4) val p5 = new Point(2,5) val seg1 = new Segment(p1, p2) val seg2 = new Segment(p2, p3) val seg3 = new Segment(p3, p4) val seg4 = new Segment(p4, p1) val seg5 = new Segment(p3, p5) val seg6 = new Segment(p5, p1) val par = new Parallelogram(seg1, seg2, seg3, seg4) val par = new Parallelogram(seg1, seg2, seg5, seg6) // gives error, not a parallelogram val par = new Quadrangle(seg1, seg2, seg5, seg6) // ok as a quadrangle // Class Rectangle models rectangles as special parallelograms // It overrides the initialization of some of the inherited fields // with a more efficient one // Note, however, that the new initialization still respects the meaning // of those fields (e.g., the field perimeter is still storing the // value of the perimeter, not something else) class Rectangle (s1:Segment, s2:Segment, s3:Segment, s4:Segment) extends Parallelogram(s1, s2, s3, s4) { // Initialization requirements: // - the first two sides must be perpendicular require( s1 perpendicularWith s2 ) override val height = s1.length override val width = s2.length override val perimeter = 2 * (height + width) } val p1 = new Point(1,0) val p2 = new Point(6,0) val p3 = new Point(6,4) val p4 = new Point(1,4) val seg1 = new Segment(p1, p2) val seg2 = new Segment(p2, p3) val seg3 = new Segment(p3, p4) val seg4 = new Segment(p4, p1) val rec = new Rectangle(seg1, seg2, seg3, seg4) // Class Square models squares as special rectangles // It It adds a new field, side, and provides a square-specific // initialization of perimeter and area which again does not // change their meaning though class Square (s1:Segment, s2:Segment, s3:Segment, s4:Segment) extends Rectangle(s1, s2, s3, s4) { // Initialization requirements: // - the first two sides must have the same length require( s1.length == s2.length ) val side = s1.length override val perimeter = 4 * side override val area = side * side } val rec = new Square(seg1, seg2, seg3, seg4) //error with previous segments val p1 = new Point(1,0) val p2 = new Point(5,0) val p3 = new Point(5,4) val p4 = new Point(1,4) val seg1 = new Segment(p1, p2) val seg2 = new Segment(p2, p3) val seg3 = new Segment(p3, p4) val seg4 = new Segment(p4, p1) val rec = new Square(seg1, seg2, sZeg3, seg4) /* -----------------------------------------*/ /* Subtype polymorphism and dynamic binding */ /* -----------------------------------------*/ class A { def f = println("f says: ``I'm f from A''") def g = {println("g says: ``I'm g from A, calling f''"); f} } val a = new A a.f // will call method f from class A a.g class AA extends A { override def f = println("f says: ``I'm f from AA''") // Does not exist in A def h(n: Int) = n + 1 } val aa = new AA aa.f // will call method f from AA aa.g // will call method g from A, which will then call f from _AA_ val a1:A = new AA // a1 has static type A, but dynamic type AA a1.f // still calling f from AA! a1.h // a1 is considered an A, and h is not a member of A. Error. def m(a:A) = a.h def m(a:A) = a.f val aa = new AA m(aa) class AB extends A {} val ab = new AB ab.f // calls f from A because it was not redefined in AB class AAA extends AA { override def f = println("f says:I'm f from AAA") } val aaa: A = new AAA aaa.g // most recent overriding is the one that matters /* Example adapted from Programming in Scala, Second Edition by Martin Odersky, Lex Spoon, and Bill Venners. Artima, 2010. */ /* We want to work with "text boxes" A text box is an abstraction for a box of text consisting of one or more lines of the same length */ /* Abstract classes */ abstract class TextBox { // contents returns the content of the textbox in an array, // with each element of the array containing one line // of the box, from top to bottom def contents(): Array[String] // height returns the number of lines in the text box def height(): Int // width returns the maximum length of the lines def width(): Int } new TextBox // gives error, abstract classes cannot be instantiated // Scala allows parameterless methods // It also allows the definition of some methods directly // in an abstract class. These methods can use fields and // methods defined in the abstract class itself, but they // cannot use private fields and methods in concrete classes // that implement the abstract class. abstract class TextBox { def contents: Array[String] // height can be computed by obtaning the contents // array and returning its length def height: Int = contents.length // width can be computed by finding the maximum length among the lines def width: Int } /* Extending classes */ // Array Box is concrete class implementing text boxes // by means of an array. class ArrayBox(lines: Array[String]) extends TextBox { // since the text box is actually stored in an array // (as expected), content is implemented simply by // returning that array def contents: Array[String] = lines } val a = new ArrayBox(Array("hello", "world")) a.width a.height /* Overriding methods and fields */ class ArrayBox(conts: Array[String]) extends TextBox { def contents: Array[String] = conts } // we can eliminate the need for the parameter lines above // by turning contents into a field. // putting the field in the parameter list makes it directly // initializable by the constructor class ArrayBox( val contents: Array[String] ) extends TextBox { } val a = Array("abc","123") val ab = new ArrayBox(a) // contents is directly set to the value of a /* Invoking superclass constructors */ // A line box is a text box with exactly one line // We could implemened concretely as a special case of // an array box // we can override the definition of width and height // with a more efficient one though class LineBox(s: String) extends ArrayBox(Array(s)) { override def width = s.length override def height = 1 } // A uniform box is a text box filled uniformly with the // same character // Its constructor needs the height and the width in advance // We can override the method height and width of TextBox // directly with parameters now class UniformBox( ch: Char, override val width: Int, override val height: Int ) extends TextBox { private val line = ch.toString * width val contents = Array.fill(height)(line) } /* ------------------------*/ /* Parametric polymorphism */ /* ------------------------*/ /* making functions generic in their type */ // The method below takes a list of integers and returns its length def len (l:List[Int]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } // There is nothing in the definition above that is specific to integers, // other than the declaration of l's type. // Computing the length of a list of strings is done in exactly the same way: def len (l:List[String]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } // We can generalize the two definitions above by parametrizing not just // the input list l but also the type of its elements def len[T] (l:List[T]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } // The type parameters are listed in square brackets after the method name // and can be used in the type declarations within the method. // In the definition above, T is the (only) type parameter, standing for any Scala type. // The definition effectively defines a *family* of length functions, one for // each type T: len[Int], len[String], len[(Int,Int)], len[Array[Int]],... len[Int]( List(1, 2, 3) ) len[String]( List("a", "b") ) len[(Int,Int)]( List((1,2), (3,4)) ) len[Array[Int]]( List(Array(1,2), Array(3,4)) ) // In most cases, the type annotation for len can be dropped, Scala will figure // out the right version of the method automatically len( List(1, 2, 3) ) len( List("a", "b") ) len( List((1,2), (3,4)) ) len( List(Array(1,2), Array(3,4)) ) // The method len exibits *parametric polymorphism*, it generically stands for // (infinitely-)many length methods differing only for the type of the elements // in the list input. // The type of len is [T](l: List[T])Int // which means that, for any type T, len takes a list of elements of type T // and returns an integer // How is this definition of len def len[T] (l:List[T]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } // different from this one? def len (l:List[Any]):Int = l match { case Nil => 0 case _ :: t => 1 + len(t) } // All the methods below can be made generic by parametrizing the type // of the list elements, because none of them do anything type-specific // to these elements def concat(l:List[Int], m:List[Int]):List[Int] = l match { case Nil => m case h :: t => h :: concat(t, m) } def reverse(x:List[Int]):List[Int] = { def rev(l:List[Int], rl:List[Int]):List[Int] = l match { case h :: t => rev(t, h :: rl) case Nil => rl } rev(x, Nil) } def remove( x:Int, l:List[Int] ): List[Int] = l match { case Nil => Nil case y :: t => if (x == y) remove(x, t) else y :: remove(x, t) } // Generic versions of contact, reverse, and filter // Their type is in comments // concat: [T](l: List[T], m: List[T]) List[T] def concat[T](l:List[T], m:List[T]):List[T] = l match { case Nil => m case h :: t => h :: concat(t, m) } // reverse: [T](x: List[T]) List[T] def reverse[T](x:List[T]):List[T] = { def rev(l:List[T], rl:List[T]):List[T] = l match { case h :: t => rev(t, h :: rl) case Nil => rl } rev(x, Nil) } // filter: [T](x:T, l: List[T]) List[T] def remove[T]( x:T, l:List[T] ): List[T] = l match { case Nil => Nil case y :: t => if (x == y) remove(x, t) else y :: remove(x, t) } /* More examples of generic methods */ // creates a set consisting of the values in the input list // toSet: [A](l: List[A]) scala.collection.immutable.Set[A] def toSet[A](l:List[A]):Set[A] = l match { case Nil => Set[A]() case h :: t => toSet(t) + h } toSet(List(1,4,2,1,4)) toSet(List("a","b","b","a","c")) // creates a list consisting of n copies of x (assuming n >= 0) // rep: [A](x: A, n: Int)List[A] def rep[A](x:A, n:Int):List[A] = n match { case 0 => Nil case _ => x :: rep(x, n - 1) } rep(3.4, 3) rep("Are we there yet?", 3) // produces a triple //toTriple: [A, B, C](x: A, y: B, z: C) (A, B, C) def toTriple[A,B,C](x:A, y:B, z:C) = (x,y,z) toTriple(3, "s", 5.4) toTriple(3, 4, 5) // the next two generic methods are ill defined because not all types T // have a > or a + method def greater[T](x:T, y:T) = x > y // gives error def plus[T](x:T) = 1 + x // gives error // However, the following two methods are OK because all types in Scala have // a method == and toString // distinct: [T](x: T, y: T) Boolean def distinct[T](x:T, y:T) = !(x == y) distinct("a", "a") // T is inferred to be String distinct(1, 2) // T is inferred to be Int distinct(1, 2.0) // T is inferred to be Double (with 1 inplicitly upcast to Double) distinct(1, "a") // T is inferred to be Any (with 1 and "a" inplicitly upcast to Any) // concat: [T](x: T, y: T) String def concat[T](x:T, y:T) = x.toString ++ " " ++ y.toString concat("a", "b") concat(1, 2) concat(1, 2.0) // (*) concat(1, "a") // (**) // (*) since the arguments have to be of the same type, // 1 is implicitly converted to the Double 1.0 // (**) both 1 and "a" are upcast to type Any // Concat can be generalized further. def concat2[S,T](x:S, y:T) = x.toString ++ " " ++ y.toString // But notice its different behavior with concat2(1, 2.0) /*-----------------*/ /* Generic Classes */ /*-----------------*/ // Whole classes can be made generic too by type parametrization, // The class below defines mutable stacks of integers, with usual operations on them // It uses a list internally to implement the stack. // The list is stored in a private mutable field class Stack { private var s = List[Int]() // stack is initially empty def push(x:Int) = s = x :: s def pop = { val (h :: t) = s s = t h } def isEmpty = s == Nil } val st = new Stack st.push(1) st.push(2) st.pop // Generic version, parametric in the stack element type // Type parameters follow the class name, similarly to generic methods class Stack[T] { private var s = List[T]() // stack is initially empty // ** note that Stack's methods are not themselves parametric ** def push(x:T) = s = x :: s def pop = { val (h :: t) = s s = t h } def isEmpty = s == Nil } // creates a string stack val ss = new Stack[String] ss push("a") ss.push("b") ss.pop // creates a string/int pair stack val sis = new Stack[(String,Int)] sis.push(("a",1)) sis.push(("b",2)) sis.pop // gives an error, because stack has only one type parameter, not two val sis = new Stack[String,Int] /*--------------*/ /* Case classes */ /*--------------*/ // Immutable structured data is best encoded with slimmed down // versions of classes/objects: case classes and objects sealed abstract class Gender case object Female extends Gender case object Male extends Gender val m = Male val f:Gender = Female case class Person ( firstName: String, lastName: String, age: Int, gender: Gender ) val p = Person("Jane", "Lu", 45, Female) p p.age val Person(fn, ln, a, g) = p // actual paramaters to the Person constructors can be provided in any order // if they are explicitly named val p1 = Person(age = 4, firstName = "Joe", lastName = "Doe", gender = Male) // creates a new Person equal to p1 except that the age is 8 val p2 = p1.copy(age = 8) // An empty tree is a tree // A node with an Int value and two subtrees is a tree // Nothing else is a tree sealed abstract class Tree case object Empty extends Tree case class Node(value:Int, leftTree:Tree, rightTree: Tree) extends Tree val t = Empty val t1 = Node(5, Empty, Node(7, Empty, Empty)) val Node(n, _, _) = t1 val Node(n1, _, Node(n2, _, _)) = t1 def rightChild(t:Tree) = t match { case Empty => throw new IllegalArgumentException("Empty tree") case Node(_, _, t1) => t1 } rightChild(t1) def rotateRight(t:Tree) = t match { case Node(m, Node(n, t1, t2), s2) => Node(n, t1, Node(m, t2, s2)) case _ => throw new IllegalArgumentException("Empty tree") } // This is similar to the actual definition of the List type in Scala sealed abstract class MyList[+T] case class NEList[T](head:T, tail: MyList[T]) extends MyList[T] case object EmptyList extends MyList[Nothing] // Nothing is the empty type, which is a subtype of every Scala type. // It is the dual of Any, which is a supertype of every Scala type. val l = NEList(3, NEList(4, NEList(5, EmptyList))) // the term above is the analogous of this Scala list val l1 = 3::(4::(5::Nil)) // A case class encoding of a simple expression language sealed abstract class Expr //variable are expressions case class Var(name: String) extends Expr // FP numbers are expressions case class Number(num: Double) extends Expr // applying a unary operator to an expression yields an expressions case class UnOp(operator: String, arg: Expr) extends Expr // applying a binary operator to two expressions yields an expressions case class BinOp(operator: String, left: Expr, right: Expr) extends Expr // this method takes a mapping from variable names to values // and an epxression and computes the value of that expression def evaluate(env:Map[String,Double], expr:Expr):Double = { expr match { case Number(n) => n case Var(s) => env(s) case UnOp("-", a) => -evaluate(env,a) case BinOp("*", l, r) => evaluate(env,l) * evaluate(env, r) case BinOp("+", l, r) => evaluate(env,l) + evaluate(env, r) case _ => throw new IllegalArgumentException("Unknown operator") } } // representing the expression -(x * (y + 3.5)) val e1 = Var("x") val e2 = Var("y") val e3 = Number(3.5) val e4 = BinOp("+", e2, e3) val e5 = BinOp("*", e1, e4) val e6 = UnOp("-", e5) val e = UnOp("-", e5) // -(x * (y + 3.5)) val e = UnOp("-", BinOp("*", Var("x"), BinOp("+", Var("y"), Number(3.5)))) e.operator e.arg val UnOp(o, a) = e val BinOp(o2, l, r) = a // Creates a map that maps x to 2.0 and y to 1.4 val env = Map("x" -> 2.0, "y" -> 1.4) evaluate(env, e) /* Option types */ /* The parameric type Option[T] is a predefined case class with two constructors: None Some(_) */ val n: Option[Int] = None val n: Option[String] = None val s = Some(3) val s = Some("abc") val s = Some( (4, "bb") ) /* Option types are typically used by functions as an alternative to raising an exception when the function is not able to compute a return value. In that case, it returns None. Otherwise it returns Some(v) where v is the computed value. */ // Here is a non failing version of head for lists def head[T](l: List[T]) = l match { case Nil => None case h :: _ => Some(h) } val l: List[Double] = Nil head(l) head(List(1,2,3)) // Here is a non failing version of Double division def div(x: Double, y: Double) = if (y == 0) None else Some(x / y) // m is in immutable map from strings to integers val m1 = Map("a" -> 1, "b" -> 2) // returns Some(1) m1 get "a" // returns None m1 get "c" // As with other case classes, pattern matching is handy with option values too // returns true if k is mapped to v in m, and false otherwise def occurs[K,V](k: K, v: V, m: Map[K,V]) = (m get k) match { case Some(x) => x == v case None => false } occurs("a", 2, m1) occurs("a", 1, m1) occurs("c", 1, m1) /*--------------------------*/ /* Higher-order programming */ /*--------------------------*/ /* Function values and higher-order methods */ // method definition def m(x:Int) = x + 1 // function definition val f = (x:Int) => x + 1 // functions are values // the immutable variable f above is given the value (x:Int) => x + 1 (x:Int) => x + 1 // is a function that // takes an integer value x and returns the value x+1 // since functions are values, they can be used like any other value // they can be evaluated (x:Int) => x + x f // assigned to immutable variables val g = (x:Int) => 2 * x // assigned to mutable variables var f1 = f f1 = g // stored into a pair val p = (f,g) // or in a list val l = List(f,g) // but they can also applied to arguments, like methods f(4) ((x:Int) => x + 1)(4) l.head(6) // l.head is the same as f p._2(6) // p._2 is the same as g // methods are *not* values def m(x:Int) = x + 1 :type m :type f // methods cannot be evaluated, only applied m // gives an error m(4) // OK // However, methods can be easily converted into functions: val h = (y:Int) => m(y) // the whole expression above can be abbreviated as m(_) val fm = m(_) // like methods functions can have more than one argument (x:Int, y:String) => x + y // the function above has type (Int, String) => String . // It takes an integer and a string, and returns a String // binary method def m2(x:Int, y:Int) = x + y // binary methods can be converted into functions too: val fm2 = m(_, _) /* Higher-order functions */ // functions can be constructed and returned by other functions/methods // make_add takes an integer n and returns a function that stores n internally // this function in turn takes an integer x and returns x+n def make_add(n:Int) = (x:Int) => x + n val inc = make_add(1) inc(40) // add6 is a unary function that returns the result of adding 6 to its input val add6 = make_add(6) add6(8) // make_add is in effect a function factory, it can used to generate any offset // function val add10 = make_add(10) // functions can be passed as input to other functions or methods // twice takes a function f from integers to integers plus an integer x, // and returns the result of applying f twice to x def twice(f:(Int) => Int, x:Int) = f(f(x)) // returns the result of applying add1 twice to 3, that is, 3+1+1 twice(inc, 3) // returns the result of applying the input function to 3, that is, (3*3)*(3*3) twice((x:Int) => x*x, 3) // twice can follows a general pattern that can be captured with parametric // polymorphism def twice[T](f:(T) => T, x:T) = f(f(x)) twice((x:String) => x ++ x.reverse, "123") /* generating functions by partial application */ // the effect of make_add can be obtained by applying + partially val add4 = (_:Int) + 4 // (_:Int) + 4 abbreviates (x:Int) => x + 4 // the use of underscore for partial application can be generalized val add2 = applyTwice(add1, _:Int) // the value of add2 is equivalent to (y:Int) => applyTwice(add1, y) // which in turn is equivalent to (y:Int) => add1(add1(y)) // default way to create a binary function val sub = (x:Int, y:Int) => x - y sub(4,6) // abbreviated way val sub = (_:Int) - (_:Int) // Note: each occurrence of _ stands for a different input: // (_:Int) - (_:Int) stands for (x:Int, y:Int) => x - y sub(4, 6) // functions/methods that take a function as input or return a function as output // are called higher/order // // higher-order functions/methods allow us to capture common programming patterns // into a generic function factory def incrementAll( l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => (x+1) :: incrementAll(t) } def squareAll( l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => (x*x) :: squareAll(t) } def doubleAll( l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => (2*x) :: doubleAll(t) } def negateAll( l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => (-x) :: negateAll(t) } // all the functions above do the same thing: // they apply some function f from integers to integers to each element // of the input list and collect the results in the output list val l123 = List(1,2,3) incrementAll(l123) // f is the successor function squareAll(l123) // f is the squaring function doubleAll(l123) // f is the doubling function negateAll(l123) // f is the negation function // we can capture this pattern in a higher-order method that takes // f explicitly as input def map( f:(Int) => Int, l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => f(x) :: map(f,t) } // then, the effect of incrementAll is achieved by instantiating f with add1 map((x:Int) => x * x, List(1,2,3)) // equivalently map(_ + 1, l123) // Same for the others map((x:Int) => x * x, l123) // like squareAll map(2 * _, l123) // like doubleAll map(-_, l123) // like negateAll // Note: in some cases when functions are passed as actual parameters // we can use a more coincise notation for them. Example: map(x => x * x, l123) // like squareAll // of course, we can pass any function of type (Int) => Int to map val map(make_add(3), l123) map(_ + 10, l123) map(5 * _, l123) // in fact, we an also partially apply map and redefine // incrementAll, squareAll, doubleAll, negateAll as functions as follows val incrementAll = map(_ + 1, _:List[Int]) val squareAll = map(x => x*x, _:List[Int]) val doubleAll = map(2 * _, _:List[Int]) val negateAll = map(-_, _:List[Int]) incrementAll(l123) squareAll(l123) doubleAll(l123) negateAll(l123) List((1,2), (3,5), (3,3)) List(3, 8, 6) /* Predefined map method */ // The following method is another example of a function similar to incrementAll etc. def listAddTogether(l:List[(Int,Int)]):List[Int] = l match { case Nil => Nil case (x, y) :: t => (x + y) :: listAddTogether(t) } // addTogether cannot be implemented as an instance of the map function above // because its input is not a list of integers. // However, we can make our map more general by type parametrization def map( f:(Int) => Int, l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => f(x) :: map(f, t) } // for any types S and T, map[S,T] takes a function from S to T and // a list l of Ss, and returns a list of Ts obtained by applying f // to each element of l def map[S,T] ( f:(S) => T, l:List[S] ): List[T] = l match { case Nil => Nil case x :: t => f(x) :: map(f, t) } val addToge0ther = (p:(Int,Int)) => { val (x, y) = p x + y } val listAddTogether = map(addTogether, _:List[(Int, Int)]) listAddTogether( List((1,2), (3,5), (3,3)) ) // The List class has a predefined map method like the above. // Object of type List[Int], // input function of type (List[Int]) => Int, // result of type List[Int] l123.map((x:Int) => x + 10) l123.map(_ + 10) // Object of type List[Int], // input function of type (List[Int]) => Float, // result of type List[Float] l123.map(_.toFloat) // Object of type List[String], // input function of type (List[Int]) => String, // result of type List[String] List("a", "b", "c").map(_ ++ " ") // Object of type List[String], // input function of type (List[String]) => String, // result of type List[String] List("a", "b", "c").map(_.capitalize) // Object of type List[String], // input function of type (List[String]) => String, // result of type List[String] List("a", "b", "c").map(x => x ++ x) // Object of type List[(Int,Int)], // input function of type (List[Int,Int]) => Int // result of type List[Int] List((1,4), (3,4), (2,6)).map(p => p._1 + p._2) // Note: map is defined for others collection classes besides lists, // such as Arrays, Sets and so on Array(1,2,3).map(add1) Set(1,2,3).map(add1) Vector(1,2,3).map(add1) /* More higher-order functions on lists */ /* filter */ // filter(p,l) return a list of all the elements x of l // such that p(x) is true. Equivalently, it filters out all // the elements of l that falsify p def filter( p:(Int) => Boolean, l:List[Int] ): List[Int] = l match { case Nil => Nil case x :: t => if (p(x)) x :: filter(p,t) else filter(p,t) } // filters out list elements not greater than 1 filter(_ > 1, List(0,1,2,3,4)) val isEven = (_:Int) % 2 == 0 // filters out all odd numbers in the list filter(isEven, List(0,1,2,3,4)) // like map, filter can be made generic def filter[T]( p:(T) => Boolean, l:List[T] ): List[T] = l match { case Nil => Nil case x :: t => if (p(x)) x :: filter(p,t) else filter(p,t) } // List types have a predefind generic filter method // filters out all strings in the list coming before c in alphabetical order List("a", "c", "d", "b", "s").filter(_ >= "c") // filters out all 2's in the list l123.filter(_ != 2) /* sortWith */ // the sortWith method of List takes a comparison function over the list elements // and sorts the list according to that // More precisely, for any type T, // if l is of type List[T], and comp is of type (T,T) => Boolean, // l.sortWith(comp) produces a new list, consisting of // the elements of l sorted accoding to comp // list elements get sorted in decreasing order List(11,2,6,5,6,9,10).sortWith((x:Int, y:Int) => x > y) List(11,2,6,5,6,9,10).sortWith(_ > _) // list elements get sorted in increasing order List(11,2,6,5,6,9,10).sortWith(_ < _) // list elements get sorted so that all even numbers are before odd ones List(11,2,6,5,6,9,10).sortWith((x,y) => x % 2 == 0) val l = List((1,4),(3,4),(2,7),(2,6)) // list pairs get sorted in increasing lexicographic order l.sortWith( (p, q) => (p._1 < q._1) || (p._1 == q._1) && (p._2 < q._2) ) // list elements get sorted so that "Iowa" is before anything else List("Illinois","Indiana", "Iowa", "Nebraska").sortWith((x,y) => x == "Iowa") /* foreach */ // For any type T, // if l is of type List[T], and p is of type (T) => Unit, // l.foreach(p) executes p on each element of l // prints each element of the list List(11,2,6,5,6,9,10).foreach(println(_)) // prints a string of the form "element = i" for each element i in the list List(11,2,6,5,6,9,10).foreach(x => println("element = " + x)) var sum = 0 // adds up all the element of the list into sum List(11,2,6,5,6,9,10).foreach(x => sum = sum + x) sum // sample use of foreach in method def addAll(xs:List[Int]) = { var sum = 0 xs.foreach(x => sum = sum + x) sum } // Note: the "for" construct can be seen as syntactic sugar for foreach // e.g., for (p <- l) println(p) // is the same as l.foreach(p => println(p)) for ((i,j) <- l) println(i+j) // is the same as l.foreach(p => {val (i,j) = p; println(i+j)}) for ((i,4) <- l) println(i) // is the same as l.foreach(p => { p match { case (i,4) => println(i) case _ => () } }) /* more higher-order functions */ def compose (f:(Int) => Int, g:(Int) => Int) = (x:Int) => f(g(x)) val add1 = (_:Int) + 1 val square = (x:Int) => x*x val f = compose(add1,square) f(3) def twice(f:(Int) => Int) = compose(f,f) val add2 = twice(add1) add2(3) def flip(f:(Int,Int) => Int) = (x:Int,y:Int) => f(y,x) val fminus = flip(_ - _) fminus(5,3) // If p is a predicate over type T (i.e., a function of type T => Boolean) and // l is a List[T], then all(p,l) returns true iff every element of l satisfies p def all[T](p:(T) => Boolean, l:List[T]):Boolean = l match { case Nil => true case h :: t => p(h) && all(p,t) } all(_ > 0, List(1,2,3)) all(_ > 0, List(1,0,3)) all(isEven, List(1,2,3)) val allPositive = all(_ > 0, _:List[Int]) allPositive(List(1,2,3)) // partition(p,l) return a pair of two lists: // the first with all the elements of l that satisfy p // the second with the other elements of l def partition(p:(Int) => Boolean, l:List[Int]):(List[Int],List[Int]) = l match { case Nil => (Nil,Nil) case h :: t => { val (l1,l2) = partition(p,t) if (p(h)) (h :: l1, l2) else (l1, h :: l2) } } } /* Generic, higher-order classes */ // Classes can be both generic and have constructors that take functions as input // The sorted queue class below is parametrized both by the type // of its elements and a comparison function over them // the queue is stored in a list sorted wrt better class SortedQueue[T](better:(T,T) => Boolean) { // invariants: // - q is initially empty // - q is maintained sorted according to better, that is, // for all positions i,j in q with i < j, better(q(i), q(j)) holds // before and after calling a public method // - when q is non-empty, its head contains the best element of q wrt better private var q = List[T]() // preconditions: // - l is sorted by better // postconditions: // - the returned list consists of x plus the elements of l // - the returned list is sorted wrt better private def insert(x:T, l:List[T]):List[T] = l match { case Nil => x :: Nil case h :: t => if (better(x,h)) x :: l else h :: insert(x,t) } def enqueue(x:T) = q = insert(x,q) // preconditions: // - q is non-empty def dequeue = { val (h :: t) = q q = t h } // postconditions: // - returns true iff q is empty def isEmpty = q == Nil } // creates a sorted queue of integers ordered by > (bigger is better) val p = new SortedQueue[Int]( (x:Int, y:Int) => x > y) p.enqueue(1) p.enqueue(10) p.enqueue(4) p.dequeue // creates a sorted queue of integers ordered by < (smaller is better) val p = new SortedQueue[Int](_ < _) p.enqueue(1) p.enqueue(10) p.enqueue(4) p.dequeue // creates a sorted queue of pairs of integer/string pairs ordered by < // the integer component, (smaller integer component is better) val p = new SortedQueue[(Int,String)]( (a:(Int,String), b:(Int,String)) => a._1 < b._1 ) p.enqueue((3,"apples")) p.enqueue((1,"pears")) p.enqueue((4,"melons")) p.dequeue /*----------------------*/ /* Modules and Packages */ /*----------------------*/ /* Singleton Classes */ // Scala has "singleton classes", classes containing exactly // one object // The syntax for defining such classes is // object { } // where // is any identifier and // is the same as in class definitions // (except that there are no 'projected' qualifiers // since they do not make sense for objects ) object A { private val a = 1 val b = "abc" + a var c = 5 def f(x:Int) = x + 1 } // The definition above creates a class with a single instance with name A // In Scala such instances are called "objects" // The public fields and methods of an object can be used as // with any class instance A.a // error, a is private A.b A.c = 0 A.f(4) A f 4 // Singleton classes can inherit from other classes // (but not from other objects) class Counter { protected var c = 0 def inc { c = c + 1 } def current = c } // Defines a sigleton subclass of Counter, whose only instance is C1 object C1 extends Counter C1.inc C1.current // Defines a sigleton subclass of Counter which extends Counter // with a decrement method, and whose only instance is called C2 object C2 extends Counter { def dec { c = c - 1 } } C2.inc C2.dec C2.current // Object can be used to create distinguished instances of classes // basic class for celestial body in astronomy class CelestialBody ( val mass: Double // body mass in kilograms ) // planet class class Planet (m: Double) extends CelestialBody(m) { } // satellite class class Satellite (m: Double, val planet: Planet) extends CelestialBody(m) { } // a particular planet, Earth, is defined as a singleton subclass of Planet object Earth extends Planet(5.9736e24) // a particular satellite, Earth's moon, is defined as a singleton subclass of Planet object Moon extends Satellite(7.347e22, Earth) // Note: strictly speaking Earth belongs to a subclass of Planet, but of course // it can be used anywhere a direct instance of Planet is used def printMass(p:Planet) { println("The planet's mass is " + p.mass) println } printMass(Earth) // A common use of objects is as modules // For instance, to collect together code in a single place // a programs that uses some global values could put them all together in a single // objects called Global object Globals { var check = true var limit = 1000 var greeting = "Hello" } // Objects are also used as entry points in a program // the Scala convention is to provide an object with method // called main with type (args: Array[String])Unit // running the programs starts with running the method main of the object // any input arguments for the program are passed to main, as strings, in the array // args in the order given object demo { val greetings = "Hello world" def main(args : Array[String]) : Unit = { println if (args.length == 0) printGreetings else println("Hello " + args(0)) println } def printGreetings { println(greetings) } } // Compiling a file demo.scala consisting of the object demo above with // // scalac demo.scala // // produces a JVM byte-code program called demo.class // // Running the program with // // scala demo // // results in the string "Hello world" being sent to the standard output // // Running the program with // // scala demo Joe // // results in the string "Hello Joe" being sent to the standard output /* Examples below adapted from Programming in Scala, Second Edition by Martin Odersky, Lex Spoon, and Bill Venners. Artima, 2010. */ // Packages // scala -cp mypackages // rockets package is in file mypackages/ex1.scala val n1 = new rockets.navigation.Navigator import rockets._ val n1 = new navigation.Navigator import rockets.navigation._ val n2 = new Navigator //----------------------------------- // In file mypackages/ex2.scala package rockets2.navigation { class Navigator } //----------------------------------- import rockets2.navigation._ val n2 = new Navigator //--------------------------- // Imports //--------------------------- //------------------------------- // In file mypackages/fruits.scala package bobsdelights { abstract class Fruit( val name: String, val color: String ) object FruitBasket { object Apple extends Fruit("apple", "red") object Orange extends Fruit("orange", "orange") object Pear extends Fruit("pear", "yellowish") val menu = List(Apple, Orange, Pear) } } //------------------------------- bobsdelights.FruitBasket.Apple bobsdelights.FruitBasket.menu object Kiwi extends bobsdelights.Fruit("kiwi", "green") // easy access to Fruit import bobsdelights.Fruit // easy access to all members of bobsdelights import bobsdelights._ // easy access to all members of FruitBasket import bobsdelights.FruitBasket._ // imports can be also dynamic def showFruit(fruit: Fruit) { import fruit._ println(name +"s are "+ color) } // Implicit imports import java.lang._ // everything in the java.lang package import scala._ // everything in the scala package import Predef._ // everything in the Predef object //----------------------------------- // In file mypackages/ex3.scala package rockets3 { package navigation { // In package rockets.navigation class Navigator package tests { // In package rockets.navigation.tests class NavigatorSuite } } } //----------------------------------- //----------------------------------- // In file mypackages/ex4.scala package rockets4 { // rockets.navigation package navigation { // rockets.navigation.Navigator class Navigator { // No need to say rockets.navigation.StarMap val map = new StarMap } // rockets.navigation.StarMap class StarMap } // rockets.Ship class Ship { // No need to say rockets.navigation.Navigator val nav = new navigation.Navigator } // rockets.fleets package fleets { // rockets.fleets.Fleet class Fleet { // No need to say rockets.Ship def addShip() { new Ship } } } } //----------------------------------- //--------------------------- package rockets { // rockets.Ship class Ship } package rockets.fleets { // rockets.fleets.Fleet class Fleet { // Doesn't compile! Ship is not in scope. def addShip() { new Ship } } } //------------------------------- // In file mypackages/launch.scala // In file launch.scala package launch { class Booster3 } // In file rockets.scala package rockets { package navigation { package launch { class Booster1 } class MissionControl { val booster1 = new launch.Booster1 val booster2 = new rockets.launch.Booster2 val booster3 = new _root_.launch.Booster3 } } package launch { class Booster2 } } //--------------------------- // Access modifiers // In file packages/Ex8.scala.err class Outer { class Inner { private def f() { println("f") } class InnerMost { f() // OK } } (new Inner).f() // error: f is not accessible } //--------------------------- // In file packages/Ex9.scala.err package p { class Super { protected def f() { println("f") } } class Sub extends Super { f() } class Other { (new Super).f() // error: f is not accessible } } //--------------------------- // In file packages/ex10/Ex10.scala package rockets package navigation { private[rockets] class Navigator { protected[navigation] def useStarChart() {} class LegOfJourney { private[Navigator] val distance = 100 } private[this] var speed = 200 } } package launch { import navigation._ object Vehicle { private[launch] val guide = new Navigator } } //--------------------------- val other = new Navigator other.speed // this line would not compile //--------------------------- // In file packages/Rocket.scala class Rocket { import Rocket.fuel private def canGoHomeAgain = fuel > 20 } object Rocket { private def fuel = 10 def chooseStrategy(rocket: Rocket) { if (rocket.canGoHomeAgain) goHome() else pickAStar() } def goHome() {} def pickAStar() {} } //--------------------------- // Package objects // In file packages/package.scala // In file bobsdelights/package.scala package object bobsdelights { def showFruit(fruit: Fruit) { import fruit._ println(name +"s are "+ color) } } // In file PrintMenu.scala package printmenu import bobsdelights.Fruits import bobsdelights.showFruit object PrintMenu { def main(args: Array[String]) { for (fruit <- Fruits.menu) { showFruit(fruit) } } }