On Github jtjeferreira / scalatron-workshop-enei
Static and Strong
val x: Int = 4 val y: List[Int] = List(1, 2)
Happily inferred
val x = 4 val y = List(1, 2)
Values can’t change
val x = 4
Variables can
var x = 4 x += 3
Lazy values don’t compute until accessed
lazy val z = launchMissiles() // nothing happens ... z // missiles launched!
Everything is an expression
val p = { val ab = a + b val cd = c + d ab + cd // last line is returned } val x = if (2 > 0) "Plausible" else "No way" def f(a: Int) = a + 1 def biggerFunction(a: Int, b: Int) = { val z = a + b * 3 - 4 * a * b z + 9 }
Classes
class Person(firstName: String, lastName: String) { // properties val name = firstName + " " + lastName // methods def greetMe(greeting: String) = println(s"$greeting, $name") // everything inside body runs on construction println("I have been born") } val person = new Person("Nick", "Stanchenko") assert(person.name == "Nick Stanchenko")
Singletons
object World { val a = 4 val b = 9 } assert(World.a + World.b == 13)
Mixins
trait Barking { def bark() = println("bark") } trait Meowing { def meow() = println("meow") } class CatDog extends Barking with Meowing
Partial implementations
trait Greetable { val name: String // needs implementation val greeting = "Hi" def greet() = println(s"$greeting, $name") } class Person(firstName: String, lastName: String) extends Greeting { val name = s"$firstName $lastName" }
Methods
// can have default values and named arguments def m(x: Int, y: String = "default") = x.toString + y m(3) m(x = 9, y = "foo") // can have type parameters def n[A, B](x: A, y: B) = ???
Functions (function values)
// full declaration val f: Function1[Int, Int] = (x: Int) ⇒ x + 1 val f: Int ⇒ Int = (x: Int) ⇒ x + 1 // with type annotation val f: Int ⇒ Int = x ⇒ x + 1 val f: Int ⇒ Int = _ + 1 // with argument annotation val f = (x: Int) ⇒ x + 1 val f = { x: Int ⇒ ... }
Simple patterns
(x, y) match { case (3, _) ⇒ 9 case (_, p) if p > 8 ⇒ 10 case (p, q) ⇒ p + q } val List(x, y, _*) = List(1, 2, 3, 4, 5)
Case classes
case class Person(name: String, age: Int, partner: Option[Person]) p match { case Person(_, a, _) if a > 18 ⇒ true case Person(_, _, Some(Person(n, a, _))) if n.length > a ⇒ false case Person("Bobby", _, None) ⇒ true case _ ⇒ false } val Person(name, age, _) = p assert(age == 19)
Anonymous function paradise
val x = List(1, 2, 3).map(i ⇒ i + 1).filter(i ⇒ i > 2) // or val x = List(1, 2, 3).map(_ + 1).filter(_ > 2) // or val x = List(1, 2, 3) map { i ⇒ val z = doSomethingWith(i) s"$z is a number, but I’m a string" }
Methods for everything
def digitSum(x: Int) = x % 10 + (x / 10 % 10) val (cool, notCool) = (1 to 100) .sortBy(digitSum) .take(50) .drop(25) .partition(_ % 4 == 0) val reallyCool = cool.find(_ > 500).getOrElse(123)
Predicting the future
// an int will be here... some time... val x: Future[Int] = Future { Thread.sleep(3) 4 } // print it when it comes x onSuccess println // or report if an exception comes instead x onFailure println
Futures are like collections!
// another Future val y = x.map(_ + 1) // a future that depends on x and y val z = (x zip y) map { case (resultX, resultY) ⇒ resultX + resultY } // failure from x or y is propagated! z onFailure println
Juggle futures with ease
// our futures val x: Future[Int] = ... val y: Future[Int] = ... // no blocking here! val z: Future[Int] = async { await(x) + await(y) }
Entire async workflows
async { val data = await(getDataFromDatabase(38)) val processed = await(processData(data)) await(prettify(processed)) }
Actors
class A extends Actor { def receive = { case Message(x) ⇒ ... case OtherMessage(y, z) ⇒ ... } }
Ping-ping
case class Ping(x: Int) class A extends Actor { context.actorSelection("../b") ! Ping(22) def receive = { case Ping(x) ⇒ sender ! Ping(x + 1) } } class B extends Actor { def receive = { case Ping(x) ⇒ sender ! Ping(x + 2) } }
Routing DSL
startServer(interface = "localhost", port = 8080) { path("hello") { get { complete { "<h1>Say hello to spray</h1>" } } } ~ path("test" / IntNumber) { number ⇒ post { complete("OK") } } }
Console
scala> def f(n: Int): Int = if (n > 1) { | f(n-1) * n | } else { | 1 | } f: (n: Int)Int scala> f(5) res0: Int = 120
Online sandboxes
Build definition
name := "needs" organization := "org.needs" version := "1.0.0-RC3" scalaVersion := "2.10.3" libraryDependencies ++= Seq( "com.typesafe.play" %% "play-json" % "2.2.0", "org.needs" %% "play-functional-extras" % "1.0.0", "org.scala-lang.modules" %% "scala-async" % "0.9.0-M4", "org.scalatest" %% "scalatest" % "2.0" % "test" ) ...
Running tasks
sbt > compile ... [success] Total time: 7 s, completed 06.03.2014 0:25:55
Rerunning on file change
sbt > ~test ... [info] All tests passed. [success] Total time: 1 s, completed 06.03.2014 0:32:49 1. Waiting for source changes... (press enter to interrupt)
Plugins for everything
class ControlFunctionFactory { def create: (String => String) = (input: String) => "Status(text=hello)" }
Welcome( name=String, path=string, apocalypse=int, round=int ) React( generation=int, name=string, time=int, view=string, energy=int, master=int:int ) Goodbye(energy=int)
Move(direction=int:int) // Move(direction=-1:1) moves the entity left(-1 in x) and down(1 in y). Spawn(direction=int:int,name=string,energy=int) Set(key=value,...) Explode(size=int) //Multiple commands Move(...)|Spawn(...)
class ControlFunction { def respond(input: String) = "Status(text=Hello World)" } class ControlFunctionFactory { def create = new ControlFunction().respond _ }
class ControlFunction { var n = 0 val directions = List("0:1","1:0","0:-1","-1:0") def respond(input: String) = { val direction = directions(n%4) n += 1 "Move(direction="+ direction +")" } } class ControlFunctionFactory { def create = new ControlFunction().respond _ }
class ControlFunction { def respond(input: String) = { val (opcode, paramMap) = CommandParser(input) if(opcode=="React") { "Status(text=Energy:" + paramMap("energy") + ")" } else { "" } } } object CommandParser { //"React(generation=0,energy=100)" //returns ("React", Map( "generation" -> "0", "energy" -> "100") ) def apply(command: String): (String, Map[String, String]) = ??? }
String.split(String): Array[String] String is Array[Char]... Access a Array index: array.apply(index) or array(index)
object CommandParser { def apply(command: String) = { def splitParam(param: String) = { val segments = param.split('=') if( segments.length != 2 ) throw new IllegalStateException("invalid key/value pair: " + param) (segments(0),segments(1)) } val segments = command.split('(') if( segments.length != 2 ) throw new IllegalStateException("invalid command: " + command) val params = segments(1).dropRight(1).split(',') val keyValuePairs = params.map( splitParam ).toMap (segments(0), keyValuePairs) } }
case class XY(x: Int, y: Int) { def isNonZero: Boolean = ??? def isZero: Boolean = ??? def isNonNegative: Boolean = ??? def updateX(newX: Int): XY = ??? def updateY(newY: Int): XV = ??? def addToX(dx: Int): XY = ??? def addToY(dy: Int): XY = ??? def +(pos: XY): XY = ??? def -(pos: XY): XY = ??? } object XY { def apply(s: String) : XY = ??? def random(rnd: Random) = ??? val Zero = ??? val One = ??? }
case class XY(x: Int, y: Int) { def isNonZero = x!=0 || y!=0 def isZero = x==0 && y==0 def isNonNegative = x>=0 && y>=0 def updateX(newX: Int) = XY(newX, y) def updateY(newY: Int) = XY(x, newY) def addToX(dx: Int) = XY(x+dx, y) def addToY(dy: Int) = XY(x, y+dy) def +(pos: XY) = XY(x+pos.x, y+pos.y) def -(pos: XY) = XY(x-pos.x, y-pos.y) def *(factor: Double) = XY((x*factor).intValue, (y*factor).intValue) def distanceTo(pos: XY) : Double = (this-pos).length def length : Double = math.sqrt(x*x + y*y) def signum = XY(x.signum, y.signum) def negate = XY(-x, -y) def negateX = XY(-x, y) def negateY = XY(x, -y) override def toString = x + ":" + y } object XY { def apply(s: String) : XY = { val xy = s.split(':').map(_.toInt) // e.g. "-1:1" => Array(-1,1) XY(xy(0), xy(1)) } def random(rnd: Random) = XY(rnd.nextInt(3)-1, rnd.nextInt(3)-1) val Zero = XY(0,0) val One = XY(1,1) val Right = XY( 1, 0) val RightUp = XY( 1, -1) val Up = XY( 0, -1) val UpLeft = XY(-1, -1) val Left = XY(-1, 0) val LeftDown = XY(-1, 1) val Down = XY( 0, 1) val DownRight = XY( 1, 1) }
class ControlFunction { var n = 0 val directions = List(XY.Down,XY.Right,XY.Up,XY.Left) def respond(input: String) = { val direction = directions(n%4) n += 1 "Move(direction="+ direction +")" } }
React(view=WWWWWWWW_____WW_____WW__M__WW_____WW____PWWWWWWWW) WWWWWWW W_____W W_____W W__M__W W_____W W____PW WWWWWWW
class ControlFunction { def respond(input: String): String = { val (opcode, paramMap) = CommandParser(input) if( opcode == "React" ) { val viewString = paramMap("view") val view = View(viewString) //use view methods to find food and move to it } else "" } } case class View(cells: String) { }
case class View(cells: String) { val size = math.sqrt(cells.length).toInt val center = XY(size/2, size/2) def offsetToNearest(c: Char): Option[XY] = ??? def apply(relPos: XY) = cellAtRelPos(relPos) def indexFromAbsPos(absPos: XY) = absPos.x + absPos.y * size def absPosFromIndex(index: Int) = XY(index % size, index / size) def absPosFromRelPos(relPos: XY) = relPos + center def cellAtAbsPos(absPos: XY) = cells.apply(indexFromAbsPos(absPos)) def indexFromRelPos(relPos: XY) = indexFromAbsPos(absPosFromRelPos(relPos)) def relPosFromAbsPos(absPos: XY) = absPos - center def relPosFromIndex(index: Int) = relPosFromAbsPos(absPosFromIndex(index)) def cellAtRelPos(relPos: XY) = cells(indexFromRelPos(relPos)) }
if( opcode == "React" ) { val viewString = paramMap("view") val view = View(viewString) view.offsetToNearest('P') match { case Some(offset) => val unitOffset = offset.signum "Move(direction=" + unitOffset + ")|Status(text=Harvesting)" case None => "Status(text=No Food Visible)" } } else ""
case class View(cells: String) { def offsetToNearest(c: Char) = { val relativePositions = cells .view .zipWithIndex .filter(_._1 == c) .map(p => relPosFromIndex(p._2)) if(relativePositions.isEmpty) None else Some(relativePositions.minBy(_.length)) } }