A scala introduction – IDE support – Scala-IDE (Eclipse plugin)



A scala introduction – IDE support – Scala-IDE (Eclipse plugin)

0 0


scalatron-workshop-sinfo


On Github jtjeferreira / scalatron-workshop-sinfo

A scala introduction

João Ferreira @jtjeferreira

Pedro Ramos @pedropalmaramos

Who…

…makes Scala?

…uses Scala?

Scala

Scala

Poll

Part 1

Main features

Scala at a glance

  • General-purpose
  • Object-Oriented + Functional
  • Clarity + Scalability
  • Compiles to JVM & JS

Types

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

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!

Expressions

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
}

Flexible syntax

List("a", "b", "c").contains("b")
// is equivalent to
List("a", "b", "c") contains "b"
3 + 4
// is equivalent to
3.+(4)
"string".length()
// is equivalent to
"string".length

Classes & objects

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("João", "Ferreira")
assert(person.name == "João Ferreira")

Singletons

object World {
  val a = 4
  val b = 9
}
assert(World.a + World.b == 13)

Traits

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"
}

Functions

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 ⇒
  ...
}

Pattern matching

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)

Part 2

Libraries and applications

Overview

  • Cool functional collections
  • Fantastic concurrency support
  • Web

Collections

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)

Futures (info)

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

Akka (info)

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)
  }
}

Spray (info)

  • Uses Akka
  • Actors handle HTTP requests
  • Connects Actor system to the web
  • Is being replaced by Akka HTTP

Spray (info)

Routing DSL

startServer(interface = "localhost", port = 8080) {
  path("hello") {
    get {
      complete {
        "<h1>Say hello to spray</h1>"
      }
    }
  } ~
  path("test" / IntNumber) { number ⇒
    post {
      complete("OK")
    }
  }
}

Part 3

Tooling & documentation

Overview

  • REPL
  • Build system (SBT)
  • IDE support
  • Where to learn

REPL

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

SBT (info)

Build definition

name := "needs"

organization := "org.needs"

version := "1.0.0-RC3"

scalaVersion := "2.11.7"

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"
)

...

SBT (info)

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)

SBT (info)

Plugins for everything

  • Generate IDE project files
  • Code autoformatting
  • Native packaging (.msi, .deb, …)
  • Compile web assets (LESS, CoffeeScript, YUI compressor)
  • git inside sbt console
  • Twitter inside sbt console

IDE support

Scala-IDE (Eclipse plugin)

IDE support

Intellij IDEA

Learn more

Part 4

Scalatron Tournament

Scalatron

Arena

  • Finite grid with walls and several entities

Entities

  • Bot (that you will control)
  • Mini-bot (that you will control)
  • Fluppet (nice beasts, blue dots)
  • Snorg (evil beasts, red dots)
  • Zugar (good plants, green dots)
  • Toxifera (bad plants, yellow dots)

Actions a bot can perform

  • Move
  • Spawn Mini bot
  • Explode
  • Set state
  • Say
  • Status
  • Log

Control Function

class ControlFunctionFactory {
  def create = new ControlFunction().respond _
}

class ControlFunction {
  def respond(input: String) = "Status(text=hello)"
}

Opcodes of Server-to-Bot Commands

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)

Opcodes of Bot-to-Server Commands

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)

Status(text=string)

//Multiple commands Move(...)|Spawn(...)

Hello World bot

class ControlFunctionFactory {
    def create = new ControlFunction().respond _
}

class ControlFunction {
    def respond(input: String) = "Status(text=Hello World)"
}

Move in circles bot

class ControlFunctionFactory {
    def create = new ControlFunction().respond _
}

class ControlFunction {
    var n = 0
    val directions = ???
    def respond(input: String) = ???
}

Move in circles bot

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 +")"
    }
}

Food finder bot

Command parser

sealed trait Command
case class Welcome(name: String, apocalypse: Int, round: Int) extends Command
case class React(generation: Int, name: String, time: Int, view: View,
                 energy: Int, master: Option[XY], collision: Option[XY]) extends Command
case class Goodbye(energy: Int) extends Command


object CommandParser {
  def apply(input: String): Command = ???
}

Command parser

object CommandParser {

  def apply(input: String): Command = ???

  private def extractMappings(command: String): (String, Map[String, String]) = {
    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)
  }

  private def splitParam(param: String): (String, String) = {
    val segments = param.split('=')
    if( segments.length != 2 )
      throw new IllegalStateException("invalid key/value pair: " + param)
    (segments(0),segments(1))
  }
}

Command parser

object CommandParser {
  def apply(input: String): Command = {
    val (opcode, mappings) = extractMappings(input)
    opcode match {
      case "Welcome" => Welcome(
        mappings("name"),
        mappings("apocalypse").toInt,
        mappings("rounds").toInt
      )
      case "React" => React(
        mappings("generation").toInt,
        mappings("name"),
        mappings("time").toInt,
        View(mappings("view")),
        mappings("energy").toInt,
        mappings.get("master") map XY.apply,
        mappings.get("collision") map XY.apply
      )
      case "Goodbye" => Goodbye(
        mappings("energy").toInt
      )
    }
  }
}

Case class XY

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 = ???

    val Zero = ???
    val One =  ???
}

Case class XY Implementation

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))
    }

    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)
}

Parse view

React(view=WWWWWWWW_____WW_____WW__M__WW_____WW____PWWWWWWWW)

WWWWWWW
W_____W
W_____W
W__M__W
W_____W
W____PW
WWWWWWW
  • “_” empty cell
  • “W” wall
  • “M” Bot (always in the center)
  • “P” Zugar (=good plant, food)

View class methods

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))
}

View Class methods

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))
    }
}

Food finder bot

class ControlFunction {
  def respond(input: String): String = {
    val command = CommandParser(input)
    command match {
      case react: React => ???
      case _ => ""
    }
  }
}

Food finder bot

class ControlFunction {
  def respond(input: String): String = {
    val command = CommandParser(input)
    command match {
      case react: React =>
        react.view.offsetToNearest('P') match {
          case Some(offset) => s"Move(direction=$offset)|Status(text=Harvesting)"
          case None => "Status(text=No Food Visible)"
        }
      case _ => ""
    }
  }
}

That's all folks!

Pushing the boundaries

  • Sized Collections
  • HLists
  • Monads
  • Macros

Sized Collections

Problem

def foo(a: Array[Int], b: Array[Int])

guaranteeing that a and b have the same size

Sized Collections

def foo(a: Int, b: Int)
def foo(a: (Int, Int), b: (Int, Int))
def foo(a: (Int, Int, Int), b: (Int, Int, Int))
def foo(a: (Int, Int, Int, Int), b: (Int, Int, Int, Int))
...

Sized Collections

Shapeless to the rescue

//a and b have size 3
def foo(a: Sized[Int, nat._3], b: Sized[Int, nat._3]) = ???

Sized Collections

Shapeless to the rescue

//a and b have size 3
def foo[N <: Nat]
  (a: Sized[Int, N], b: Sized[Int, N]) = ???

//we could even say that
def foo[N <: Nat, M <: Nat]
  (a: Sized[Int, N], b: Sized[Int, M])
  (implicit ltEq: N <= M) = ???

Hlists

  • Tuples
    • Fixed length sequence of distinct types
      val t: (Int, String, Long) = (1, "2", 3l)
      
  • Collections
    • Varying length sequence of elements of the same types
      val c1: Seq[Any] = Seq(1, "2", 3l)
      val c2: Seq[String] = Seq("1", "2", "3")
      
  • HLists
    • Varying length sequence of elements of distinct type
      val hl = 1 ::  "2" ::  3l :: HNil
      val first: Int = hl(0)
      val second: String = hl(0)
      

Questions??

A scala introduction João Ferreira @jtjeferreira Pedro Ramos @pedropalmaramos