1 / 38

Markus Völter voelter@acm voelter.de

Introduction to Scala. Markus Völter voelter@acm.org www.voelter.de. About me. Markus Völter voelter@acm.org www.voelter.de. Independent Consultant Based out of Heidenheim, Germany Focus on Model-Driven Software Development Software Architecture Middleware. About this talk.

Download Presentation

Markus Völter voelter@acm voelter.de

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Introduction to Scala Markus Völtervoelter@acm.orgwww.voelter.de

  2. About me Markus Völter voelter@acm.org www.voelter.de • Independent Consultant • Based out of Heidenheim, Germany • Focus on • Model-Driven SoftwareDevelopment • Software Architecture • Middleware

  3. About this talk • This talk is an introduction to the Scala language. • Scala is a JVM-based, functional & object-oriented language developed at EPFL, lead by Martin Odersky. • This introduction is based on tutorials and other documentation by the Scala team. • I am using their material with permission. • Thanks to the Scala team for letting me use it! • The goals of this talk are to • get you interested in Scala • Show the power of functional programming • Show where today’s language research is headed

  4. This talk is not … • … based on huge amounts of hands-on experience. • In contrast to most of my other talks where I primarily talk about stuff I know inside out, I don’t have a whole lot of experience using Scala. • … a complete reference of Scala features. I had to pick.

  5. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  6. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  7. Scala in a nutshell • Scala is object-oriented • Scala is functional • Scala is statically typed • Scala is extensible • Scala interoperates with Java and .NET • Information about Scala can be found at: www.scala-lang.org

  8. Scala in a nutshell II • There is a Scala plugin for Eclipse that includes an editor, compiler integration and a debugger.

  9. Scala in a nutshell III • Scala Hello World • However, as usual a Hello World does not tell us much about the language. Therefore… object HelloWorld extends Application { Console.println("Hello, world!"); }

  10. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  11. Classes class Point(xc: Int, yc: Int) {var x: Int = xc var y: Int = yc def move(dx: Int, dy: Int): Unit = { x = x + dx y = y + dy } override def toString(): String ="(" + x + ", " + y + ")"; } • Basically as in Java, but… • no return • def keyword • var keyword • void => Unit • constructor args => class args • vals are read-only variables • objects are top-levelsingleton instances object Classes { def main(args: Array[String]): Unit = {val pt = new Point(1, 2) Console.println(pt) pt.move(10, 10) Console.println(pt) } }

  12. Automatic, User-Defined Type Coercions • An automatic type coercion is an implicit conversion from a type to another, unrelated type if such a conversion becomes necessary from the context. • You can now use a Frac where float or double is expected: class Frac(numerator: Int, denominator: Int) { def +(that: Frac): Frac = new Frac(numerator * that.denominator + that.numerator * denominator, denominator * that.denominator) def neg: Frac = new Frac(-numerator, denominator) def coerce: Float = numerator.asInstanceOf[Float] / denominator def coerce: Double = numerator.asInstanceOf[Double] / denominator } object CoercionTest extends Application { Console.println(Math.sqrt(Frac(1,2)) + ", " +Math.sqrt(0.5))}

  13. Traits • A trait is somewhat like an interface in Java, but can be partially implemented: • Traits get integrated into classes using mixin class composition. The “abstract” methods of the trait need to be implemented in that class. trait Similarity { def isSimilar(x: Any): Boolean def isNotSimilar(x: Any): Boolean = !isSimilar(x)} class Point(xc: Int, yc: Int) extends Similarity { var x: Int = xc var y: Int = ycdef isSimilar(obj: Any) = obj.isInstanceOf[Point] && obj.asInstanceOf[Point].x == x }

  14. Compound Types • Assume you have two traits: • You can write use an (anonymous) compound type in places where you expect an object to be Cloneable and Resetable. • Any number of … with … statements can be used. trait Cloneable extends java.lang.Cloneable { override def clone(): Cloneable = { super.clone(); this }} trait Resetable { def reset: Unit } def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //...}

  15. Abstract Types • Types can be abstract. One way of making them abstract is to keep some type definitions open: • Type definitions can be overridden in subclasses; type bounds must be tightened. abstract class Buffer { type T; val element: T} abstract class SeqBuffer extends Buffer { type Utype T <: Seq[U] def length = element.length}

  16. Abstract Types II • Types can be instantiated even if they are anonymous: abstract class IntSeqBuffer extends SeqBuffer {type U = Int} object AbstractTypeTest1 extends Application { def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =new IntSeqBuffer { type T = List[U] val element = List(elem1, elem2) } val buf = newIntSeqBuf(7, 8) Console.println("length = " + buf.length) Console.println("content = " + buf.element) }

  17. Abstract Types III • An alternative notation uses type parameters (aka Generics) abstract class Buffer[+T] { val element: T} abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { def length = element.length} object AbstractTypeTest2 extends Application { def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] = new SeqBuffer[Int, List[Int]] { val element = List(e1, e2) } val buf = newIntSeqBuf(7, 8) Console.println("length = " + buf.length) Console.println("content = " + buf.element) }

  18. Case Classes • Case classes are regular classes that • export their constructor parameters • provide a recursive decomposition mechanism via pattern matching. • Objects can be constructed without using new • Constructor vars can be accessed as if they were public (r/o) variables; structural equals is also created. abstract class Termcase class Var(name: String) extends Termcase class Fun(arg: String, body: Term) extends Termcase class App(f: Term, v: Term) extends Term Fun("x", Fun("y", App(Var("x"), Var("y")))) val x = Var("x")Console.println(x.name)

  19. Case Classes II def print(term: Term): Unit = term match { case Var(n) => Console.print(n) case Fun(x, b) => Console.print("^"+x+".") print(b) case App(f, v) => Console.print("(") print(f) Console.print(" “) print(v) Console.print(")") } • Case classes should be used if recursive decomposition is needed  • You can also use deep matchingand guard clauses def isIdentityFun(term: Term): Boolean = term match { case Fun(x, Var(y))if x == y => true case _ => false }

  20. More General Pattern Matching • You cannot just match against case class instances, but also against primitve values: • … or against types: def matchTest(x: Int): String = x match {case 1 => "one“case 2 => "two“case _ => "many“} def matchTest(x: Any): Any = x match {case 1 => "one“case "two" => 2case y: Int => "scala.Int“}

  21. Upper Type Bounds • An upper type bound restricts a type parameter to be a subtype of another type. trait Similar { def isSimilar(x: Any): Boolean} case class MyInt(x: Int) extends Similar { def isSimilar(m: Any): Boolean = m.isInstanceOf[MyInt] && m.asInstanceOf[MyInt].x == x} object UpperBoundTest extends Application { def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean = if (xs.isEmpty) false else if (e.isSimilar(xs.head)) true else findSimilar[T](e, xs.tail) val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3)) Console.println(findSimilar[MyInt](MyInt(4), list)) Console.println(findSimilar[MyInt](MyInt(2), list)) }

  22. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  23. Nested Functions • Just like in Good’Ol Pascal, functions can contain other, nested functions. • This is very nice for defining recursive algorithms with an “entry function” • e.g. one that creates a list that you then pass around object FilterTest extends Application { def filter(xs: List[Int], threshold: Int) = { def process(ys: List[Int]): List[Int] =if (ys.isEmpty) ys else if (ys.head < threshold) ys.head :: process(ys.tail) else process(ys.tail) process(xs) } Console.println(filter(List(1, 9, 2, 8, 3, 7, 4), 5)) }

  24. Anonymous Function Syntax x: Int => x + 1 • … is an anonymous function that computes the successor of an integer • It is an abbreviation for the following anonymous function • Functions with multiple parameters are also possible • Function Types (i.e. signatures) can be specified with a similar syntax: new Function1[Int, Int] { def apply(x: Int): Int = x + 1;} (x: Int, y: Int) => "(" + x + ", " + y + ")" Int => Int(Int, Int) => StringUnit => String

  25. Higher Order Functions • Higher order functions are functions either take other functions as an argument or return other functions. • Something like apply is especially useful for working on collections (“internal iterators”). • Methods are automatically coerced to functions if required. def apply(f: Int => String, v: Int) => f(v) class Decorator(left: String, right: String) { def layout[A](x: A) = left + x.toString() + right} object FunTest extends Application {def apply(f: Int => String, v: Int) = f(v) val decorator = new Decorator("[", "]") Console.println(apply(decorator.layout, 7)) }

  26. Currying • If you call a function (method) with fewer parameters than what it is formally defined with, the the return value is a new function with the remaining number of parameters. object CurryTest extends Application { def filter(xs: List[Int], p: Int => Boolean): List[Int] = if (xs.isEmpty) xs else if (p(xs.head)) xs.head :: filter(xs.tail, p) else filter(xs.tail, p) def modN(n: Int)(x: Int) = ((x % n) == 0) val nums = List(1, 2, 3, 4, 5, 6, 7, 8) Console.println(filter(nums, modN(2))) Console.println(filter(nums, modN(3))) }

  27. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  28. Type Inference • Scala is statically typed, i.e. all type information is known to the compiler. • However, you can still omit types iff the compiler can automatically infer the type from the context. • Note: in recursive methods, the type cannot be inferred and thus must be given explicitly. object InferenceTest1 extends Application {val x = 1 + 2 * 3 // the type of x is Intval x:Int = 1 + 2 * 3 // same statement WITH types val y = x.toString() // the type of y is Stringval y:String = x.toString() // same statement WITH types def succ(x: Int) = x + 1 // method succ returns Int valuesdef succ(x: Int): Int = x+1// same statement WITH types}

  29. Type Inference II • Type inference also works with generic types. • This is very useful. I hate the Java way, where I have to write generics arguments all over the place. case class MyPair[A, B](x: A, y: B); object InferenceTest3 extends Application {val p = new MyPair(1, "scala") // type: MyPair[Int, String]}

  30. Lists • Lists are an essential data structure in Scala • They are homogeneous (all elements same type) • They can be nested • Type parameter is optional (type inference!) • Lists are built from atomics: Nil and :: • Lists can be representes as head :: tail • :: is called cons, and is right associative val fruit = List("apples", "oranges", "pears")val nums : List[int] = List(1, 2, 3, 4)val diag3: List[List[int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))val empty = List() val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))val nums = 1 :: 2 :: 4 :: 4 :: Nilval diag3 = (1 :: (0 :: (0 :: Nil))) :: (0 :: (1 :: (0 :: Nil))) :: (0 :: (0 :: (1 :: Nil))) :: Nilval empty = Nil

  31. Lists II • :: is defined as a case class, therefore you can use lists in pattern matching contexts: • Several higher order methods are available on Lists: def isort(xs: List[int]): List[int] = xs match {case List() => List()case x :: xs1 => insert(x, isort(xs1))} def insert(x: int, xs: List[int]): List[int] = xs match { case List() => List(x) case y :: ys => if (x <= y) x :: xs else y :: insert(x, ys)} abstract class List[a] { ... def map[b](f: a => b): List[b] = this match { case Nil => this case x :: xs => f(x) :: xs.map(f) } def scaleList(xs: List[double], factor: double) =xs map (x => x * factor)

  32. Sequence Comprehensions • Sequence comprehensions are basically a convenience notation for creating lists. • More complex example: object ComprehensionTest1 extends Application { def even(from: Int, to: Int): List[Int] =for (val i <- List.range(from, to); i % 2 == 0) yield i Console.println(even(0, 20))// List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) } object ComprehensionTest2 extends Application { def foo(n: Int, v: Int): Iterator[Pair[Int, Int]] = for (val i <- Iterator.range(0, n); val j <- Iterator.range(i + 1, n); i + j == v) yield Pair(i, j); foo(20, 32) foreach {case Pair(i, j) => Console.print("(" + i + ", " + j + “) ") }// (13, 19) (14, 18) (15, 17) }

  33. Operators • Methods that take one parameter can be used as an infix operator. Methods without parameters can be used as postfix operators. class MyBool(x: Boolean) {def and(that: MyBool): MyBool = if (x) that else thisdef or(that: MyBool): MyBool = if (x) this else thatdef negate: MyBool = new MyBool(!x)} def not(x: MyBool) = x negate; // semicolon required heredef xor(x: MyBool, y: MyBool) = (x or y) and not(x and y)

  34. Automatic Type-Dependent Closure Construction • You can use parameterless function names as parameters of methods. • This is parameterless function name syntax: • Once such a method is called, • the actual parameters are NOT evaluated! • A nullary function is automatically defined (which encapsulates the computation of the parameter evaluation, aka call-by-name) • This function is only evaluated when its accessed in the function (cond: => Boolean) // a function (name) that evaluates to Boolean object TargetTest1 extends Application { def whileLoop(cond: => Boolean)(body: => Unit): Unit = if (cond) {bodywhileLoop(cond)(body) } } var i = 10whileLoop (i > 0) { Console.println(i) i = i 1 }}

  35. Syntax Extension • Using automatic closure construction and operator syntax, you can easily create new syntactic forms. • Note how intermediate objects are created to which you then subsequently apply an operator! object TargetTest2 extends Application { def loop(body: => Unit): LoopUnlessCond =new LoopUnlessCond(body); private class LoopUnlessCond(body: => Unit) { def unless(cond: => Boolean): Unit = { body if (!cond) unless(cond); } } var i = 10; loop{ Console.println("i = " + i) i = i 1}unless (i == 0) }

  36. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary

  37. Summary • The combination of OO features and functional features yields a very powerful and convincing language. • … that is quite a bit bigger and more complex than Java (“the PL1 of our time” – James Noble ) • It emphasizes today’s trend on language power (and not library power, as in the last 10 years) • I am missing (compile time) meta programming. Some kind of Converge-like macro facility would make this language quite a bit more powerful. • … as would a Weak type, on which typing would be deferred to runtime – including methodNotFound callbacks and the like. • Go to scala-lang.org, download Scala and play with it!

  38. Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary THE END. Questions?

More Related