// ================================================================================= // = Advanced live coding tutorial given on HaL6 (6th Haskell in Leipzig Workshop) // = // = 7th Oct 2011 in Leipzig // = Alf Richter, iba Consulting Gesellschaft mbH&Co.KG, Leipzig // = Duration: more than 90 min // ================================================================================= // -- CONTENTS //= FUNCTIONS & COLLECTIONS //-- val / var = immutable / mutable reference //-- function syntax/convention //-- anonymous function //-- collections (default: immutable collections) //-- for comprehension //= CLASSES & OBJECTS //-- class definition and instances of a class //-- companion object //-- getter/setter //-- override getter/setter //-- equality of objects //= CASE CLASSES, MATCHING and TRAITS //-- case class and structural matching //-- match by structure //-- extractors //-- sealed case class //-- traits //= ADVANCED TOPICS and STANDARD API EXAMPLES //-- implicit parameters //-- regexp //-- xml //-- process api (>= 2.9) //-- implement a cd command using implicit tricks //= TYPES, PACKAGES and VISIBILITY //-- type hierarchy //-- packages definition and imports //-- Predef & default imports //-- visibility //= COMPILING and BUILDTOOL //-- Hello World and compiling //-- SBT - Simple Build Tool (version >= 0.10) // ================================================================================= // -- Let's start //- to run this you need scala in version >= 2.9 //- with $ prefix: input in shell //- without prefix: input in scala interpreter //- CTRL-D : input a keyboard shortcut //- start the scala interpeter $ scala //- copy and paste the following examples into the scala interpreter // ================================================================================= // = FUNCTIONS & COLLECTIONS // --------------------------------------------------------------------------------- // -- val / var = immutable/mutable reference val x = 10 // val stands for value (immutable reference) x = 11 // error var y = 2 // var stands for variable (mutable reference) y = 12 // ok // --------------------------------------------------------------------------------- // -- function syntax/convention //- with side effects def f(x:Int){ ... } def printInt(y:Int) { println(y) } //- without side effects def f(x:Int) : Int = { ... } def plusTwo(y:Int) = y + 2 // -- anonymous function //- standard form is //- (x : Int) => x + 1 //- functions are values / objects val f = (x : Int) => x + 1 val g = () => 42 // function without parameters //-- form with positional parameter //- define two helper functions def f1(f : Int => Int) = f def f2(f : (Int,Int) => Int) = f f1(_ + 1) // (x : Int) => x + 1 f1(_ + 1)(2) == 3 f2(_ + _) // (x : Int, y : Int) => x + y f2(_ + _)(2,3) == 5 //- functions with empty parameter list can be called without brackets def printHello() : Unit = println("Hello") printHello() printHello //- class/object member functions can be applied without brackets or point //- suffix notation 1.toDouble // the method call to the instance 1 of Int class can be written as 1 toDouble //- infix notation 1.+(2) == 1 + 2 // calling method + on instance 1 of Int class (1 :: 2 :: Nil) map ( x => x+2 ) == 3 :: 4 :: Nil // class List has a method :: //- Note: method ending with colon : , e.g. :: , ::: , +: are right-associative Nil.::(4).::(3) == 3 :: 4 :: Nil // --------------------------------------------------------------------------------- // -- Collections (default: immutable collections) //- From a list produce a list of Duplicate Entries List("haskell", "scala", "haskell", "cal"). groupBy(x=>x). filter(_._2.size > 1). map(_._1) == List("haskell") // - Two ways to construct a map, // - Map() is definied as immutable map in Predef package (see below) Map("eins" -> 1, "zwei" -> 2) val m = Map() + ("eins" -> 1) + ("zwei" -> 2) m get "zwei" == 2 // == m.get("zwei") m get "drei" // error m.getOrElse("drei",0) == 0 //- convert and format the map to string m.mkString("["," , ","]") m.mkString("\n") // -- for comprehension //- range generator 1.to(4) //== Range(1,2,3,4) 0 to 4 by 2 //== Range(0, 2, 4) //- calling actions like foreach return type : Unit for(i <- 1 to 10) println(i) //- produce a result sequence (combine map, filter ...) : Seq[T] for( i <- 1 to 10 ; j <- i to 10 ; if i % 2 == 0 ) yield (i,j,i*j) for ( (x,y) <- (10 to 4 by -2).zipWithIndex) yield (x,y) // ================================================================================= // = CLASSES & OBJECTS // --------------------------------------------------------------------------------- // -- class definition and instances of a class //- simple class class Foo new Foo() // or new Foo //- class with one member and one default constructor class Foo { var x = 1 } new Foo().x == 1 // or new Foo.x == 1 //- normal class with constructor class Foo( x : Int) // no member x! //- members are public by default class Foo( x1 : Int) { val x = x1 } //- short hand for the previous class Foo( val x : Int) // member Foo.x new Foo(1).x == 1 class Point(val x : Int, val y : Int){ println("constructing a point") // called in primary constructor! def this() = this(0,0) // must call primary constructor def this(xy : Int) = this(xy,xy) override def toString = "Point(" + x + ", " + y + ")" } // --------------------------------------------------------------------------------- // -- companion object //- contains member function/values which are the same for all instances (java: static) // class and companion object have to be on one line/input sequence in scala interpreter // or definied in one scala file class Foo( val x : Int) ; object Foo { def apply(x : Int) = new Foo(x) } //- Factory methods Foo(1).x == 1 // no 'new' needed, short cut for Foo.apply(1) // --------------------------------------------------------------------------------- // -- getter/setter // only getter class Foo( val x : Int) val f = new Foo(1) f.x // prints 1 f.x = 2 // error // getter and setter class Foo( var x : Int) val f = new Foo(1) f.x == 1 f.x = 2 // assignment to member Foo.x f.x == 2 // --------------------------------------------------------------------------------- // -- override getter/setter class Foo { val x : Int = 0 } //- getter class Foo { private[this] val _x : Int = 0; // private field, [this] means instance private def x = { println("getter called") ; _x } } (new Foo).x //- setter, x_= is a function name (!!), you must write a getter function here class Foo { private[this] var _x : Int = 0; def x = _x ; def x_= (x : Int) : Unit = { println("setting x= " + x) ; _x = x } } (new Foo).x = 100 // -- equality of objects // - == / != checks for value equality and not for reference equality // - implement equals AND hashcode for own types based on class members // - reference equality check with AnyRef.eq or AnyRef.ne // ================================================================================= // = CASE CLASSES, MATCHING and TRAITS // --------------------------------------------------------------------------------- // -- case class and structural matching // - case class vs class //- Pattern matching, //- Construction of instances without using the new keyword, //- Constructor arguments are accessible from outside (as val) //- toString method prints name and values of arguments, //- equals method compare two instances of the same case class structurally rather than by identity. //- hashCode method using the hashCodes of constructor arguments. //-- desugared example, to show generated methods $ cat > Foo.scala case class Foo(x:Int) CTRL-D //- remove syntactic sugar with scalac -print $ scalac -print Foo.scala //- compile and display class and object definition as seen from scala $ scalac Foo.scala # generate .class files $ scalap Foo # display class $ scalap Foo$ # display object //- display bytecode as seen from java $ javap Foo # display class $ javap Foo$ # display object //-- more case class examples case class Point( x : Int, y : Int) //- updating case class instances using copy and named parameters val p = Point(1,2) val q = p.copy( y = 100 ) p.x == q.x p.y == 2 q.y == 100 // -- match by structure case class Line( p : Point , q : Point) val p1 = Point(10,10) val p2 = Point(10,100) val l = Line(p1,p2) //- extract point coordinates of the line val s = l match { case Line( Point(x1,y1), Point(x2,y2) ) => x1 + "," + y1 + " - " + x2 + ", " + y2 } //- using @ to capture a object and using _ as joker val s = l match { case Line( _ , q@Point(x2,y2) ) => q } // -------------------------------------------------------------------------------- // -- extractors //-- unapply can return the following //- Boolean -- only match no extraction //- Option[T] -- match and extract //- Option[(T1,..,Tn)] -- match and extract as tupel //- Option[Seq[T]] -- match and extract as list // Extractor which converts the string to an Int if possible. object AsInt{def unapply(s:String): Option[Int] =try{ Some(s.toInt) }catch{ case _ => None }} "77" match { case AsInt(n) => println(n) ; case _ => println("nothing") } //- Extractor Sequence object Slashes { def unapply(s : String) : Option[List[String]] = Some(s.split("/").toList) } "a/b/c" match { case Slashes(x::y::_) => List(x,y) } // --------------------------------------------------------------------------------- // -- sealed case class // - all case classes in one file extending one common sealed abstract class sealed abstract class Thing case class Foo() extends Thing case class Bar() extends Thing // - compiler warning if some cases are missing def f( x : Thing) : String = x match { case Foo() => "foo" } // :10: warning: match is not exhaustive! // missing combination Bar // --------------------------------------------------------------------------------- // -- traits // - like java interfaces or haskell type classes // - can have members and implementation // - one type can be extends multiple traits trait Color { private var _color : Int = 1 def color = _color def color_( c : Int ) = { this._color = c } } trait Center { private var _center: (Int,Int) = (0,0) def center = _center def center_= (c : (Int,Int)) = { _center = c } // tuple extraction: (10,20)._1 == 10 and (10,20)._2 == 20 and def moveBy( p : (Int, Int)) = { _center = (_center._1 + p._1, _center._2 + p._2) ; this } } class Shape extends Center with Color { override def toString = "color: " + color + " center: " + center } val s = new Shape s.center s.color s.color = 0 s moveby (10,10) s.center s.color //- Traits can be mixed in dynamicly class Thing new Thing with Color with Center //- Traits can be used as interceptors trait LoggableCenter extends Center { override def moveBy( p: (Int,Int) ) = { println("moved by " + p) ; super.moveBy(p) } } class Shape extends LoggableCenter with Color { override def toString = "color: " + color + " center: " + center } //- Traits can have contracts trait ColorPrinter { def color : Int // contract method def print = { println("color = " + color)} } val s2 = new Shape with ColorPrinter s2.print new Thing with Center with ColorPrinter // error: object creation impossible, no method color ... // ================================================================================= // = ADVANCED TOPICS and STANDARD API EXAMPLES // --------------------------------------------------------------------------------- // -- implicit parameters implicit val e = "UTF8" def openFile(filename : String)(implicit encoding : String) = println("encoding=" + encoding) openFile("tmp.txt") //- implicit parameter are resolved by type and not by name !! // --------------------------------------------------------------------------------- // -- regexp // - Quoted multiline-string with """ """ """\d\.\d""".r findFirstIn "Version 9.1" // == Some("9.1") // --------------------------------------------------------------------------------- // -- xml val x = x \\ "dir" \ "@name" text x \\ "file" \\ "@name" map (_.text) //-- construct xml using process api (see below), examples only works in unix like enviroment import sys.process._ //- find files in current dir and convert to string list val s = ("find ." #| "grep -v .hg" lines_! ) toList //- construct a dir xml node with file nodes using file list s val x = {s map {f => } } //- format the nodes new scala.xml.PrettyPrinter(80,2).formatNodes(x) // --------------------------------------------------------------------------------- // -- process api (>= 2.9) import sys.process._ import java.io.File "find ." #| "grep -v .svn" #> new File("files.txt") ! "echo test" #> new File("test2.txt") ! "ls " ! "cat " #< new File("test.txt") #> new File("test-dir/test2.txt") ! val logger = ProcessLogger((o: String) => println("out " + o),(e: String) => println("err " + e)) "ls . " ! logger "ls with err " ! logger "find ." ! "find ." #| "grep test2" ! logger //- ! - runs process (ProcessLogger gets output as tupple std err) //- #< redirect from stdin(File) //- #> redirect to stdout(File) //- #| shell pipe //- !! output as one (multline) String //- lines_! as Stream, convert to String list with toList // --------------------------------------------------------------------------------- // -- implement a cd command using implicit tricks //- see http://www.scala-lang.org/node/10985 // ================================================================================= // = TYPES, PACKAGES and VISIBILITY // --------------------------------------------------------------------------------- // -- type hierarchy //- http://joelabrahamsson.com/entry/learning-scala-type-hierarchy-and-object-equality //- purely object oriented typ system //- two kinds of types: value und reference //- Any is parent of all classes //- Nothing is child of all classes //- AnyRef is used as parent for Java objects // --------------------------------------------------------------------------------- // -- packages definition and imports //- copy and paste (from BEGIN to END) into scala file PackageExample.scala // BEGIN of PackageExample.scala package de { package ibacg { class Bar } } //// same as above //package de; //package ibacg; //class Bar //- extending above package with new class and object package de.ibacg { class Bar2 object Constants { val pi = 22 / 7; } } //- _root_ to access package root package b { class B } package a.b { class A { val x = new _root_.b.B() } } //- imports object PackageExample { // local import { import de.ibacg.Bar; new Bar import de.ibacg.Bar2; new Bar2 } { import de.ibacg.{Bar,Bar2} new Bar ; new Bar2 ; Constants.pi } { import de.ibacg._ new Bar ; new Bar2 ; Constants.pi } { import de._; import ibacg.Bar; new Bar } { // local import of members import de.ibacg.Constants._ println(pi) } // error here: println(pi) { // renaming of imported things import scala.collection.immutable.{Map => ImmutableMap} import scala.collection.mutable.{Map => MutableMap} ImmutableMap(1 -> 2) MutableMap(3 -> 4) } } // END of PackageExample.scala // --------------------------------------------------------------------------------- // -- Predef & default imports //- the packages scala, Predef, java.lang are automatically imported //- e.g. includes conversions (boxing/unboxing) //- and defaults for immutable collections like Map(), List(), Set() // --------------------------------------------------------------------------------- // -- visibility //- public is default //- private member only in current scope and companion visible //- private[this] and private[de.ibacg.service.impl] //- protected only visible in subclasses and companion/companions of subclasses //- protected[this] and protected[de.ibacg.service.impl] // ================================================================================= // = COMPILING and BUILDTOOL // --------------------------------------------------------------------------------- // -- Hello World and compiling //- App trait is used since scala 2.8 //- finish input to cat with CTRL-D $ cat >> HelloWorld.scala object HelloWorld extends App { println("Hello World!") } CTRL-D //- calling the compiler $ scalac HelloWorld.scala //- starting the compiled program $ scala HelloWorld Hello World! //- starting in interpreter $ scala -cp . HelloWorld.main(new Array(0)) // --------------------------------------------------------------------------------- // -- SBT - Simple Build Tool (version >= 0.10) //- installation instruction https://github.com/harrah/xsbt/wiki/Setup //- convert to sbt // move files to maven style layout and clean up class-files $ mkdir -p src/main/scala $ mv *.scala src/main/scala $ rm *.class //- create build file $ cat >> build.sbt name := HelloWorld // after an property insert empty line or ; version := 0.1 // scalaVersion := "2.9.1" // libraryDependencies += "junit" % "junit" % "4.8" % "test" CTRL-D //- sbt tasks (ctrl-c or ctrl-d to cancel) $ sbt compile # compile project assuming standard maven layout $ sbt ~compile # compile and watch src dirs for changes $ sbt ~run # run the main; recompile and run after changes in source files $ sbt console # start interpreter with current project on class path