Notes from a Short Introductory Lecture on Scala (Based on Programming in Scala, 2nd Ed.) David Haraburda January 30, 2013 1 Introduction Scala is a multi-paradigm language that runs on the JVM (is totally intererable with Java). The name Scala is a play on the word scalable. Scala is scalable, it was designed to grow and evolve at a rapid pace (which it does). Multi-paradigm what does this mean? Scala is both object-oriented and functional. Object-oriented: Most of you are familiar with an object-oriented language like Java. You have the same OO principles: encapsulation, polymorphism, inheritance, etc. Scala also adds traits. These are essentially like interfaces in Java except that they can contain code. Functional: There are two important principles here: 1. Functions are first class objects. They can be passed as arguments to other functions, functions can return functions, etc. 2. Immutability. Mutable means something can be changed immutable not changeable. In Scala, immutable data structures like lists, maps, and sets (think of everything in Java Collections API) these are the defaults. The idea here is to prevent side effects. This is a key trait of functional languages, and it can also make things a lot easier to debug (avoid concurrency/multi-threading bugs). Scala is concise. In Java we have: These notes were used to give a short talk about Scala on January 30, 2013 to CSCE 4430 at UNT. As such, they may not make a lot of sense unless you were present for the lecture. As alluded to in the title, these notes were based on [1]. As such, it is not completely apprriate to consider me the author because there is little original content: the structure, examples, and sometimes the text was taken from [1]. 1
boolean namehasuppercase = false; for (int i = 0; i < name.length(); ++i) { if (Character.isUpperCase(name.charAt(i))) { namehasuppercase = true; break; } } The same thing in Scala: val namehasuppercase = name.exists(_.isuppercase) Once you get the syntax down, this is really nice. Scala has a very good type system. Scala is statically typed this may seem like a paradox, how can a language be both concise and statically typed, after all one of the key advantages of dynamic languages (Python, Ruby, etc) is that you don t have to specify types. Scala makes up for this by having a very good type inference system. That is, in many cases you don t have to specify the type, the Scala compiler can figure it out. 2 Basics about Scala Try out Scala using Eclipse IDE: www.scala-ide.org The examples here were generated using the Scala interpreter (or REPL - readevaluate-print-lo). 2.1 Variables Scala has two kinds of variables: vals and vars. A val is immutable. Once initialized it cannot be changed. This is similar to a final variable in Java. scala> val msg = "Hello, world!" msg: java.lang.string = Hello, world! Several things to note: 1. Scala infered the type (String) 2. It says java.lang.string because Scala strings are Java strings. 3. Types are indicated after a variable s name: variablename: typename 2
4. Semicolons are tional. Convention is that they are only used when needed. It takes a little bit to appreciate, especially coming from languages where they are required, but you eventually realize it is cleaner and nice. We can t reassign a val: scala> msg = "Goodbye!" <console>:8: error: reassignment to val msg = "Goodbye!" ^ To reassign, we need to use a var: scala> var greeting = "Hello" greeting: java.lang.string = Hello scala> greeting = "Goodbye" greeting: java.lang.string = Goodbye scala> 2.2 Functions Now let s write a function: scala> def max(x: Int, y: Int): Int = { if(x > y) x else y } max: (x: Int, y: Int)Int scala> max(4,5) res1: Int = 5 scala> max(9,8) res2: Int = 9 scala> Functions are defined starting with the keyword def, followed by name and arguments. So x and y are arguments, they both have a type of Int. Note you must always specify the types of function parameters. The type specification on the end is the result type. Note the absence of a return statement. In Scala, the body is being evaluated again think functional, not imperative the if/else works sort of like the? ternary erator in Java, it evaluates to the apprriate result. Note if we had 3
two sets of if else statements, or any other statements for that matter, it would just use the last. scala> def silly(x: Int) = { x 10 30 } silly: (x: Int)Int scala> silly(5) res4: Int = 30 We can also write a function on one line, and leave off the return type. Scala will infer it. scala> def max2(x: Int, y: Int) = if (x > y) x else y max2: (x: Int, y: Int)Int scala> max2(10,20) res5: Int = 20 A function that returns nothing results in the Unit type. This is sort of like void in Java. scala> def greet() = println("hello!") greet: ()Unit scala> greet Hello! The empty parenthesis indicate there are no parameters. The result type is Unit this essentially means we only executed this function for its side effects in this case, writing output to the screen. 2.3 Iteration A standard iterative way to iterate (run this as a script): var i = 0 while(i < args.length) { println(args(i)) i += 1 } Note args refers to the command line arguments of a Scala script. Scala has a more concise and functional way: the foreach function. args.foreach(arg => println(arg)) 4
First, note that args is a list. foreach is a method on Scala lists. It takes a function as its only argument remember, we can pass functions as arguments. Here we are passing a function literal. The left side of the => is the argument(s) and the right side is the body of the function. Just like you can create a String with new String("str") or define a literal using quotes, you can create a new function with def or define a function literal using this syntax. There is an even more concise way: args.foreach(println) This works because the function literal consists of a single statement that takes one argument. Of course, function literals can take more than one argument: (x: Int, y: Int) => x + y 3 Map and Fold Let s look at map and fold two standard functions in the world of functional programming. Recall that the map function takes a list and a function to apply to members of that list. scala> List(1,2,3).map(x => x + 1) res4: List[Int] = List(2, 3, 4) scala> Again there are some more shortcuts. We can use any method as an erator (erator notation): List(1,2,3) map (x => x + 1) In fact, that is what is happening when you use any of the normal erators. Operators are actually methods in Scala. For example: List(1,2,3) map (x => (x).+(1)) Although in this case it is not very nice looking. Any method can be an erator! Let s look at fold. We can fold left or fold right. scala> (0 /: List(1,2,3)) ((x,y) => x + y) Another shortcut: we can use the underscore as a placeholder, so we don t have to name the arguments. 5
scala> (0 /: List(1,2,3)) (_ + _) res0: Int = 6 This is fold left. Similarly we can fold right (Figure 2). scala> (List(1,2,3) :\ 0) (_ + _) res1: Int = 6 Note that in our examples, we get the same result (6). What s the difference? To aid, a graphical representation is given of (z /: xs) (), where z is the starting value, xs is the list, and is the binary eration. This is foldleft in Figure 1. Likewise we show foldright in Figure 2 c b z a Figure 1: Parse tree of fold left. a b c z Figure 2: Parse tree of fold right. We got the same result because addition is associative. That is, it does not 6
matter in what order the apply the add erator (alternatively, it doesn t matter where we place the parenthesis) Subtraction is not associative. scala> (0 /: List(1,2,3)) (_ - _) res0: Int = -6 This is (((0 1) 2) 3) = 6 scala> (List(1,2,3) :\ 0) (_ - _) res1: Int = 2 This is (1 (2 (3 0))) = 2 4 Conclusion Scala has a LOT of features. Some other interesting features: Pattern matching. Combinator parsers. Actors. Check them out for more fun! References [1] M. Odersky, L. Spoon, and B. Venners. Programming in Scala. Artima Series. Artima Press, 2 edition, 2010. 7