380 likes | 389 Views
Discover Scala, a JVM-based language, blending functional & object-oriented paradigms. Learn its features & benefits in this introductory talk. Delve into Scala's object-oriented, functional, and extensible nature, and how it interoperates with Java and .NET. Explore classes, user-defined type coercions, traits, compound types, and abstract types.
E N D
Introduction to Scala Markus Völtervoelter@acm.orgwww.voelter.de
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
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
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.
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
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
Scala in a nutshell II • There is a Scala plugin for Eclipse that includes an editor, compiler integration and a debugger.
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!"); }
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
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) } }
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))}
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 }
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 = { //...}
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}
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) }
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) }
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)
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 }
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“}
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)) }
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
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)) }
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
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)) }
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))) }
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
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}
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]}
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
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)
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) }
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)
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 }}
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) }
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary
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!
Contents • General • Object Oriented Programming • Functional Programming • Other Features • Summary THE END. Questions?