A presentation about Scala and the core of how to use it.
To play with some of the snippets there are a few options:
In which we explore values, variables and functions.
A value is a field that can’t be updated after assignment.
val foo: Int = 2
// foo = 3 wouldn't compile.
A variable is a field that can be updated after assignment.
var foo: Int = 2
foo = 3
Also these can be made lazy.
lazy val foo: Int = 10 // Imagine this took a minute to calculate.
We can define a function using the def keyword.
def square(number: Int): Int = {
number * number
}
Note that we don’t need to use the return keyword, the last expression is taken as the result of the method.
As functions are first class elements in Scala, they can be assigned to a field or passed into other methods.
val squareFunction: (Int) => Int = square
// Alternatively:
// val squareFunction = (number: Int) => number * number
Multiple parameters as well as varargs are supported.
def printAtLeastOneKitteh(firstKitteh: String, kittehs: String*) {
println(firstKitteh)
println(kittehs)
}
printAtLeastOneKitteh("Spot", "Rover", "Jeff")
Also it’s possible to use multiple parameter groups.
def printKittehs(firstKitteh: String)(otherKittehs: String*) {
println(firstKitteh)
println(otherKittehs)
}
printKittehs("Spot")("Rover", "Jeff")
Methods that have no parameters to them (like one form of println) can have the parenthesis omitted.
def printLater(was: Long, now: => Long): Unit = {
Thread.sleep(100)
println("Was: " + was + " Now: " + now)
}
printLater(System.currentTimeMillis, System.currentTimeMillis)
// Was: 1336129701612 Now: 1336129701714
The now expression is executed at the point when it’s used inside the method, not when it’s passed into the method.
Note that the “now” parameter is effectively a function that lacks parameters and parenthesis.
If statements function slightly differently to languages like Java, think of them more like a method themselves as they return a value.
// Java.
String someValue = null;
if (1 == 1) {
someValue = "1 is definitely 1.";
} else {
someValue = "Maths is broken, the end is nigh.";
}
// Scala.
val someValue: String = if (1 == 1) {
"1 is definitely 1."
} else {
"Maths is broken, the end is nigh."
}
Scala is quite flexible in its use of expressions.
val someOfTheText = {
val text = "Some really long text..."
new String(text.substring(5, 12))
}
println(someOfTheText)
println{
someOfTheText
}
Even the contents of a function are an expression:
def square(number: Int): Int = (number * number)
Scala supports type inference on fields:
val number = 1
println(number.getClass)
It also supports type inference for method return types:
def getRandomNumber() = 4
var number = getRandomNumber()
// This would not compile, as the type of number is Int.
// number = "Test"
In which we explore classes, traits and objects.
Much like other languages a class tends to encapsulate some data and expose some methods for interacting with it.
class Monkey(var x: Int, var y: Int) {
def this() = this(0, 0) // Overloaded constructor.
println("Creating a monkey!") // Wut?
def move(xMovement: Int, yMovement: Int): Unit = {
x += xMovement
y += yMovement
}
}
Traits can be used like interfaces in Java, with the exception that they can also include code, values and variables as well.
trait Logging {
def log(logLevel: String, message: String): Unit
def info(message: String) {
log("INFO", message)
}
}
class Monkey extends Logging {
def log(logLevel: String, message: String) {
println(logLevel + ": " + message)
}
def throwBanana() {
// Something else happens.
info("Banana thrown.")
}
}
val monkey = new Monkey()
monkey.throwBanana()
The case keyword provides a whole load of awesome.
case class Monkey(x: Int, y: Int)
From this we get:
Lets have a look at some of that.
val monkey1 = new Monkey(0, 0)
val movedMonkey1 = monkey1.copy(x = 10)
println(monkey1)
println(monkey1 == movedMonkey1)
Note that we used ”==” rather than equals, the method ”==” is a null safe alias of equals(…) from Java. To check for reference equality, use the eq method.
Scala has singletons built into the language.
object CakeConcatenator {
val cake = "CAKE!!"
def concatenate(text: String): String = text + cake
}
println(CakeConcatenator.cake)
println(CakeConcatenator.concatenate("I must have "))
Not possible to “new” up an object.
Objects can extend classes and/or traits.
trait Concatenator {
def concatenate(text: String): String
}
object CakeConcatenator extends Concatenator {
val cake = "CAKE!!"
def concatenate(text: String): String = text + cake
}
Scala allows a class and an object with the same name to co-exist.
class Cake(name: String)
object Cake {
def apply(name: String) = new Cake(name)
}
val cake = Cake("Chocolate")
Case classes do this too:
case class Cake(name: String)
val cake = Cake("Chocolate")
Scala supports generics almost identically to Java.
trait Serializer[T] {
def serialize(target: T): Array[Byte]
}
object StringSerializer extends Serializer[String] {
def serialize(target: String) = target.getBytes
}
println(StringSerializer.serialize("CAKE!"))
Methods can also be generified (note this may not be a real word).
Generic types can have type bounds, which place certain constraints on what types can be used.
import java.io._
trait Serializer[T <: Serializable] {
def serialize(target: T): Array[Byte] = {
// Stock Java "Serializable" style serialization.
val byteArrayOutputStream = new ByteArrayOutputStream()
val objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)
objectOutputStream.writeObject(target)
val bytes = byteArrayOutputStream.toByteArray()
objectOutputStream.close()
bytes
}
}
object StringSerializer extends Serializer[String]
println(StringSerializer.serialize("CAKE!"))
Scala has a series of tuples that are somewhat like nameless classes.
val tuple2 = new Tuple2("Another Test", 4)
println(tuple2)
println(tuple2._2)
val tuple3 = (1, "Test", 2.7)
println(tuple3)
println(tuple3._3)
Since creating a case class is so simple (and worry free), lean towards creating those for method parameters and return types.
In which we explore the world of Sets, Lists and Maps.
For much more information, the collections API docs are the best place to start.
A List is a series of items sequentially stored.
val list = List(5, 6, 1)
println(list)
println(list.head)
println(list.tail)
The stock List implementation is a cons-list, each part of the list consists of an element(head) and another list(tail).
// Alternatively:
val list = 5 :: 6 :: 1 :: Nil
// To show the structure.
val list = (5 :: (6 :: (1 :: Nil)))
A Set is a collection of elements with no duplicates, ordering cannot be guaranteed.
val set = Set(1, 2, 7, 1)
println(set)
println(set.head)
println(set.tail)
println(set + 25) // Creates new instance.
A more general sequential type is Vector, which provides amortised constant time operations for prepending and appending amongst other things.
val vector = Vector(1, 9, 22, 19, -5, 10)
println(vector :+ 3)
println(7 +: vector)
println(vector.patch(3, Vector(1, 2, 3, 4), 0))
Note that pretty much all the methods seen on Set/List/Vector are inherited from Seq(there’s a lot more too), so all of them will work in the same way.
A Map is otherwise known as an associative array or dictionary. It just stores values against keys, with the keys being unique.
val map = Map(1 -> "Sean", 2 -> "Cat")
println(map)
println(map(1)) // apply method.
println(map(2))
println(map + (3 -> "Greg")) // Creates new instance.
println(map.tail) // Wait, what?
An Option contains either zero or 1 items, represented by the None and Some types respectively.
val potentialValue1: Option[String] = None
val potentialValue2: Option[String] = Some("Potential!")
println(potentialValue1)
// None
println(potentialValue2)
// Some(Potential!)
println(potentialValue1.getOrElse("No Potential!"))
// No Potential!
println(potentialValue2.getOrElse("No Potential!"))
// Potential!
def lookupUser(id: Int): Option[String] = {
if (id == 1) Some("Sean") else None
}
All of the collections (including Option and even Map) we’ve seen so far support the map method.
def timesTwo(number: Int): Int = number * 2
val list = List(10, 20, 30)
println(list.map(timesTwo))
// List(20, 40, 60)
val set = Set(9, 10, 23)
println(set.map(timesTwo))
// Set(18, 20, 46)
val some = Some(99)
val none = None
println(some.map(timesTwo))
// Some(198)
println(none.map(timesTwo))
// None
Works very similar to map, but is way more useful.
def lookupScores(playerID: Int): List[Int] = {
List(playerID, playerID * 2, playerID * 3)
}
println(List(1, 2, 3).map(lookupScores))
// List(List(1, 2, 3), List(2, 4, 6), List(3, 6, 9))
println(List(1, 2, 3).flatMap(lookupScores))
// List(1, 2, 3, 2, 4, 6, 3, 6, 9)
Hands up if you’ve written a piece of code like this?
Player player = lookupPlayer(1);
if (player != null) {
Integer score = lookupScore(player);
if (score != null) {
return "Score is: " + score;
}
}
return null;
Lots of boilerplate, easy to get a condition wrong, have to explicitly return (in this case the code is Java). Bear in mind the man who gave us null calls it his Billion Dollar Mistake.
Option and the flatMap method go together really well if we adapt the prior example to use Option in Scala.
case class Player(name: String)
def lookupPlayer(id: Int): Option[Player] = {
if (id == 1) Some(new Player("Sean"))
else if(id == 2) Some(new Player("Greg"))
else None
}
def lookupScore(player: Player): Option[Int] = {
if (player.name == "Sean") Some(1000000) else None
}
println(lookupPlayer(1).map(lookupScore)) // Some(Some(1000000))
println(lookupPlayer(2).map(lookupScore)) // Some(None)
println(lookupPlayer(3).map(lookupScore)) // None
println(lookupPlayer(1).flatMap(lookupScore)) // Some(1000000)
println(lookupPlayer(2).flatMap(lookupScore)) // None
println(lookupPlayer(3).flatMap(lookupScore)) // None
The foldLeft method takes an initial value and “folds” over the collection using a function. Summing up a collection is the prime example.
// Java
Integer[] scores = new Integer[]{200, 300, 600};
int total = 0;
for (Integer score : scores) {
total += score;
}
return total;
// Scala
val scores = List(100, 200, 300)
scores.foldLeft(0)((runningScore, score) => runningScore + score)
// For those comfortable with the syntax:
scores.foldLeft(0)(_ + _)
// Don't use this:
(0 /: scores)(_ + _)
Methods like map can (and probably are) implemented in terms of a fold.
def mapList[T, U](values: List[T], function: T => U): List[U] = {
values.foldRight(Nil: List[U]){(value: T, workingList: List[U]) =>
function(value) :: workingList
}
}
println(mapList(List(1, 2, 3),
(number: Int) => number.toString + " cakes!"))
// List(1 cakes!, 2 cakes!, 3 cakes!)
// Java
for (int count = 0; count < 4; count++) {
System.out.println(count);
}
// Scala
for(count <- 0 until 4) {
println(count)
}
// Also:
for(count <- List(0, 1, 2, 3)) {
println(count)
}
Instead of side effecting like a for loop, a for comprehension returns a value.
val results = for {
number <- List(10, 20, 30)
} yield number * 2
println(results)
// List(20, 40, 60)
case class Player(name: String)
def lookupPlayer(id: Int): Option[Player] = {
if (id == 1) Some(new Player("Sean")) else None
}
def lookupScore(player: Player): Option[Int] = {
if (player.name == "Sean") Some(1000000) else None
}
val scoreText = for {
player <- lookupPlayer(1)
score <- lookupScore(player)
} yield "%s scored %s.".format(player.name, score)
println(scoreText)
// Some(Sean scored 1000000.)
It’s also possible to filter those collections, so to expand on the previous example.
case class Player(name: String, deleted: Boolean)
def lookupPlayer(id: Int): Option[Player] = {
if (id == 1) Some(new Player("Sean", true)) else None
}
def lookupScore(player: Player): Option[Int] = {
if (player.name == "Sean") Some(1000000) else None
}
val scoreText = for {
player <- lookupPlayer(1) if !player.deleted
score <- lookupScore(player)
} yield "%s scored %s.".format(player.name, score)
println(scoreText)
// None
For comprehensions work by convention, there’s no requirement for them to be a collection.
Seq is the prime trait for any of the sequential collections (even Map which is why previously there was a map.tail method call).
val seq = Seq(-10, -5, 0, 1, 2, 3, 9999)
println(seq.collect{
case number if number % 2 == 0 => "Even: " + number
})
println(seq.take(4))
println(seq.drop(4))
println(seq.forall(number => number % 3 == 0))
println(seq.groupBy(number => number % 3))
println(seq.max)
println(seq.permutations.toSet)
println(seq.combinations(3).toSet)
println(seq.map(number => (number % 3, number)).toMap)
println(seq.zip(seq))
In which we explore how matching patterns can make your life easy.
Switch/Case statements are available in a lot of languages, with Scala being no exception.
def getPlayerName(id: Int): String = {
id match {
case 1 => "Sean"
case 2 => "Greg"
case _ => "Unknown"
}
}
println(getPlayerName(1))
println(getPlayerName(2))
println(getPlayerName(99))
Similar to the if statement, the match expression and the cases return a value, so there’s no need for “break;” statements or any such nonsense.
One step beyond(!) that is matching on types.
def whatIsThis(value: Any): String = {
value match {
case int: Int => "It's an Int."
case text: String => "It's a String: " + text
case _ => "I don't know what it is."
}
}
println(whatIsThis("Moshi"))
println(whatIsThis(1))
println(whatIsThis(1.2))
Each case can also be given a guard, which is a further condition that it must satisfy for it to match.
def parseNumberSafely(id: Int): String = {
id match {
case positiveNumber if positiveNumber > 0 => {
"This is a positive number of: " + positiveNumber
}
case negative => {
"This is not a positive number: " + negative
}
}
}
println(parseNumberSafely(1))
println(parseNumberSafely(1000))
println(parseNumberSafely(-200))
println(parseNumberSafely(0))
As the compiler knows the type of “id” it’s not necessary to specify it in the case expressions.
It may be necessary to pattern match on a field, potentially one passed into a method.
def valueMatchesText(value: Any, expected: String): String = {
value match {
case `expected` => "It worked!"
case _ => "It's all gone wrong."
}
}
println(valueMatchesText("One", "One"))
println(valueMatchesText(1, "One"))
It’s possible to match multiple things in each case statement:
def isFourOrFive(value: Any): String = {
value match {
case 4 | 5 => "Is Four Or Five!"
case _ => "No Idea!"
}
}
println(isFourOrFive(4))
println(isFourOrFive(5))
println(isFourOrFive("Something Else"))
case class Player(name: String)
sealed abstract class Act
case class ShotFired(player: Player, x: Int, y: Int) extends Act
case class MedikitUsed(player: Player, percentUsage: Int) extends Act
def describeAction(action: Act): String = {
action match {
case ShotFired(Player(player), x, y) => {
"%s fired a gun at (%s,%s)".format(player, x, y)
}
case MedikitUsed(Player(player), useAmount) => {
"%s used %s%% of a medikit".format(player, useAmount)
}
}
}
println(describeAction(ShotFired(Player("Sean"), 100, 150)))
println(describeAction(MedikitUsed(Player("Sean"), 10)))
Each case statement is an instance of PartialFunction.
val maybeSeven: PartialFunction[Any, String] = {
case 7 => "It's Seven!"
}
println(maybeSeven(1)) // Throws exception.
println(maybeSeven(7))
println(maybeSeven.isDefinedAt(1))
println(maybeSeven.isDefinedAt(7))
Exceptions in Scala function in much the same way as they do in Java.
def parseInt(text: String): Option[Int] = {
try {
Some(text.toInt)
} catch {
case npe: NullPointerException => None
case nfe: NumberFormatException => None
} finally {
// Do nothing.
}
}
println(parseInt("1"))
println(parseInt("Test"))
println(parseInt(null))
Note that we pattern match on the type of the exception itself, so if the exception is a case class, we can extract values from it and use guards.
The unapply method is the crux of pattern matching, it’s automatically created for case classes, but it’s easy to make one yourself.
case class WebRequest(url: String, get: Boolean, params: Map[String, String])
case object GET {
def unapply(webRequest: WebRequest): Option[String] = {
if (webRequest.get) Option(webRequest.url) else None
}
}
def parseGetRequest(webRequest: WebRequest): String = webRequest match {
case GET(url) => "GET request for %s.".format(url)
case _ => "Unknown"
}
println(parseGetRequest(WebRequest("/test", true, Map())))
// GET request for /test.
Types of classes?
If you’ve used Java a reasonable amount there’s a prime candidate of how we do things with inheritance:
public interface Comparable<T> {
public int compareTo(T o);
}
Later on in Java we got this as an alternative:
public interface Comparator<T> {
int compare(T o1, T o2);
}
The above is probably the closest Java has to typeclasses.
Kinda, real typeclasses come from Haskell which has no inheritance of types.
-- Typeclass definition.
class MyComparator a where
mycompare :: a -> a -> Integer
-- Typeclass instance.
instance MyComparator Integer where
mycompare first second = first - second
1 `mycompare` 2
The instance of the typeclass just needs to be in scope, no need to “new up” an instance.
Typeclasses can be emulated in Scala through the use of implicit conversions and implicit parameters.
object MyComparators {
trait MyComparator[T] {
def mycompare(first: T, second: T): Int
}
implicit val intComp: MyComparator[Int] = new MyComparator[Int] {
def mycompare(first: Int, second: Int) = first - second
}
case class ValueWrapper[T](value: T) {
def mycompare(other: T)
(implicit comparator: MyComparator[T]): Int = {
comparator.mycompare(value, other)
}
}
implicit def toValueWrapper[T](value: T): ValueWrapper[T] = {
new ValueWrapper(value)
}
}
import MyComparators._
println(1.mycompare(2))
// Compile error:
// println("Cake".mycompare("Elephant"))
// The above is the same as doing this:
new ValueWrapper(1).mycompare(2)
// Or what the compiler is doing:
new ValueWrapper(1).mycompare(2)(intComp)
Has anyone ever done this?
def method1(): Int = 1
def method2(): String = "1"
println(method1() == method2()) // Always going to be false.
Comparing different types with equality is an error the compiler can catch.
import scalaz._
import Scalaz._
// Doesn't compile.
// println(method1() === method2())
println(1 === 2)
The ”===” method is added in the same way to every type with a requirement of an implicit instance of Equal in scope.
trait Functor[F[_]] { self =>
def map[A, B](fa: F[A])(f: A => B): F[B]
}
We know this as the map method that is seen on Seq/Scala/String
Functor[List].map(List(1, 2, 3))(number => number * 2)
Includes Functor (through Apply).
trait Applicative[F[_]] extends Apply[F] with Pointed[F] { self =>
def apply[A, B, C](fa: => F[A], fb: => F[B])(f: (A, B) => C): F[C]
}
This is what we can do with that:
val result1: Option[String] = "First".some
val result2: Option[String] = "Post".some
def combine(first: String, second: String) = first + " " + second
val result = Applicative[Option].apply(result1, result2)(combine)
println(result) // Some("First Post")
// Or:
println(^(result1, result2)(combine))
trait Monad[F[_]] extends Applicative[F] with Bind[F] { self =>
// No abstract methods, but introduces bind through Bind:
// def bind[A, B](fa: F[A])(f: A => F[B]): F[B]
}
At this point we have the ability to put a value into a context (F[_] as shown above), as well as perform transformations on values held within contexts like that (through bind and map amongst others).
trait Monoid[F] extends Semigroup[F] { self =>
// Semigroup provides:
// def append(f1: F, f2: => F): F
def zero: F
}
The append method is connected up to the |+| method pretty much directly.
println(1 |+| 2) // 3
println("1" |+| "2") // 12
val map1 = Map(1 -> Set("DJ Quack"), 2 -> Set("Big Bad Bill"))
val map2 = Map(1 -> Set("I.G.G.Y."))
println(map1 |+| map2) // ???
// Invert types, turning A[B[C]] into B[A[C]],
// uses Traverse and Applicative.
println(List(1.some, 2.some, 3.some).sequence)
println(List(1.some, 2.some, none).sequence)
// foldLeft is too much work, uses Monoid and Foldable.
println(List(1, 2, 3).foldMap(_.toString))
println(List("1", "2", "3").foldMap(_.toInt))
In which we explore…Other Things.
Nothing is a bottom type, which means it extends every type, but doesn’t allow you to create an instance of that type (because that would be a nonsense class).
The definitions of Some and None might help to clear this up:
final case class Some[+A](x: A) extends Option[A] {...}
case object None extends Option[Nothing] {...}
As None extends Option[Nothing] it can be used in place of an Option regardless of the type.
An underscore in scala is used in multiple places, usually to specify the default of something or that the value should be ignored.
// Wildcard import.
import java.io._
// Default value for a given type.
var number: Int = _
// Function currying.
def multiplier(i: Int)(factor: Int) = i * factor
val byFive = multiplier(5)_
println(byFive(20))
// Default type.
val list: List[_] = List(1, 2, 3)
// Function parameters.
println(List(1, 2, 3).foldLeft(10)(_ + _))
// Default case parameter:
// case _ => "Default"
// Collection expansion to varargs:
def printValues(values: Any*) = values.foreach(println)
printValues(List(1, 2, 3): _*)
Currying is the process of taking a method with N parameters and turning it into one with N parameter groups.
val addValues = (first: Int, second: Int) => first + second
val curriedAddValues = addValues.curried
println(addValues(1, 2))
println(curriedAddValues(1)(2))
val addFour = curriedAddValues(4)
println(addFour(9))
// Also:
val otherAddFour = addValues(_: Int, 4)
println(otherAddFour(23))
Either is a type somewhat like Option, except that instead of one value or no value, it holds one value or another value of potentially differing types.
val left: Either[String, Int] = Left("There was an error.")
val right: Either[String, Int] = Right(9)
println(left)
println(right)
This is useful for encoding a success (by convention a Right) or a failure (Left), being that they are case classes it’s possible to pattern match on them.
case class Player(name: String)
def lookupUser(playerID: Int): Either[String, Player] = {
if (playerID == 1) {
Right(Player("Sean"))
} else {
Left("Player %s could not be found.".format(playerID))
}
}
println(lookupUser(1))
println(lookupUser(2))
Scala works pretty much interoperably with Java, there are just a couple of things to be careful of.
Using Java libraries/code in Scala should be pretty much seamless, the only thing that will become a necessity will be the use of the JavaConverters object.
Using Scala libraries/code from Java will potentially necessitate avoiding features of Scala that don’t map well to Java, implicit and multiple parameter groups as well as functions will be awkward at best. In this case it would be advised to create additional methods using Scala to shield off these difficulties and provide interfaces that are more Java like.
In which we explore actors using Akka.
Note to run these examples in the REPL, you’ll need to include an Akka 2.0 actor jar in the classpath and pass the ”-Yrepl-sync” option to the REPL.
Note that while Scala includes an actor implementation in the core library, in the future they will be replaced by Akka actors.
The Akka Documentation is much more comprehensive than this, this is more of an introduction to what can be done.
import akka.actor._
val actorSystem = ActorSystem("actorSystem")
case class TestActor() extends Actor {
def receive = {
case "Kitteh" => println("Kitteh received, over and out.")
case _ => println("Unknown received.")
}
}
val actorRef = actorSystem.actorOf(Props[TestActor],
name = "testActor")
actorRef.tell("Kitteh")
actorRef ! "Dog"
actorSystem.shutdown()
Instances of actors are independent, only process one message at a time and queue said messages in a mailbox.
import akka.actor._
import akka.util.Timeout
import akka.util.duration._
import akka.pattern._
val actorSystem = ActorSystem("actorSystem")
case class ReplyActor() extends Actor {
def receive = {
case int: Int => {
Thread.sleep(200)
sender ! "Received Int: %s".format(int)
}
case _ => sender ! "Unknown received."
}
}
val actorRef = actorSystem.actorOf(Props[ReplyActor],
name = "replyActor")
implicit val timeout = Timeout(5 seconds)
val future = actorRef ? 2000
println(future.value) // Should be None.
Thread.sleep(400)
println(future.value) // Should be Some(Right(...))
actorSystem.shutdown()
import akka.actor._
import akka.util.Timeout
import akka.util.duration._
import akka.pattern._
val actorSystem = ActorSystem("actorSystem")
case class ReplyActor() extends Actor {
def receive = {
case int: Int => {
Thread.sleep(200)
sender ! "Received Int: %s".format(int)
}
case _ => sender ! "Unknown received."
}
}
val actorRef = actorSystem.actorOf(Props[ReplyActor],
name = "replyActor")
implicit val timeout = Timeout(5 seconds)
val future = actorRef ? 2000
future.onComplete{
case Right(result) => println(result)
case _ => println("Uh oh!")
}
actorSystem.shutdown()
If you want to avoid manually controlling synchronisation around some piece of state, it’s possible to use the actor to hold the state and manage that for you.
It’s possible to create and start hundreds of thousands, if not millions of actors on one machine, while not processing messages they don’t consume any CPU time.
In which we discuss Simple Build Tool.
SBT is run similarly to Ant or Maven via a series of commands that you add on the end of the command line:
$ sbt clean
[info] Loading global plugins from /Users/Sean/.sbt/plugins
[info] Set current project to default-66315d (in build file:/Users/Sean/test/)
[success] Total time: 0 s, completed Feb 15, 2012 12:24:21 PM
SBT also supports being run interactively if no commands are specified:
$ sbt
[info] Loading global plugins from /Users/Sean/.sbt/plugins
[info] Set current project to default-66315d (in build file:/Users/Sean/test/)
> clean
[success] Total time: 0 s, completed Feb 15, 2012 12:25:07 PM
>