Slides of a talk I gave at work on Scala. It is geared toward Java developers. Some of the examples are in my company's domain, which is analyzing energy usage (i.e. a "read" is an electric meter read).
19. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
19
20. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
20
21. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
val doh = booyah / 3; // not a Float!!
21
22. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
val doh = booyah / 3; // not a Float!!
val better:Float = booyah / 3;
22
23. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
val doh = booyah / 3; // not a Float!!
val better:Float = booyah / 3;
// return type is java.lang.String
def fullName = { first + “,” + last; }
23
24. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
val doh = booyah / 3; // not a Float!!
val better:Float = booyah / 3;
// return type is java.lang.String
def fullName = { first + “,” + last; }
If the type seems obvious to you...
24
25. List<String> oyVey = new ArrayList<String>();
var hellsYeah = new ArrayList[String];
val booyah = 45; // it’s an Int
val doh = booyah / 3; // not a Float!!
val better:Float = booyah / 3;
// return type is java.lang.String
def fullName = { first + “,” + last; }
If the type seems obvious to you...
Scala can usually figure it out, too
25
27. val semicolons = “don’t need ‘em”
val but = “if it’s not clear”; val you = “need them”
27
28. val semicolons = “don’t need ‘em”
val but = “if it’s not clear”; val you = “need them”
def braces(b:Boolean) =
if (b)
“needed for multiple expressions”
else
“you don’t need them”
28
29. val semicolons = “don’t need ‘em”
val but = “if it’s not clear”; val you = “need them”
def braces(b:Boolean) =
if (b)
“needed for multiple expressions”
else
“you don’t need them”
• if/else is a singular expression
29
30. class Read(d:Date, val usage:Int) {
def +(other:Int) = new Read(d,usage + other)
override def toString = d + “:” + u
}
30
31. class Read(d:Date, val usage:Int) {
def +(other:Int) = new Read(d,usage + other)
override def toString = d + “:” + u
}
val f = new Read(new Date,10)
val g = f + 20
31
32. class Read(d:Date, val usage:Int) {
def +(other:Int) = new Read(d,usage + other)
override def toString = d + “:” + u
}
val f = new Read(new Date,10)
val g = f + 20
val gee = f.+(20) // yuck, but legal
32
33. class Read(d:Date, val usage:Int) {
def +(other:Int) = new Read(d,usage + other)
override def toString = d + “:” + u
def later(ms:Int) = new Read(d + ms,usage)
}
val f = new Read(new Date,10)
val g = f + 20
val gee = f.+(20) // yuck, but legal
val l = f later (3600 * 1000)
33
35. abstract class SaneCompare[T] {
def <(other:T):Boolean
def >(other:T) = !(this < other)
def <=(other:T) = this == other || this < other
def >=(other:T) = this == other || this > other
}
class Read(d:Date,u:Int) extends SaneCompare[Read] {
val usage = u
def <(other:Read) = usage < other.usage
}
if (someRead >= someOtherRead) println(”finally!”)
35
36. abstract class SaneCompare[T] {
def <(other:T):Boolean
def >(other:T) = !(this < other)
def <=(other:T) = this == other || this < other
def >=(other:T) = this == other || this > other
}
class Read(d:Date,u:Int) extends AbstractEntity {
val usage = u
def <(other:Read) = usage < other.usage
}
// hmmm....what now?
36
37. abstract class SaneCompare[T] {
def <(other:T):Boolean
def >(other:T) = !(this < other)
def <=(other:T) = this == other || this < other
def >=(other:T) = this == other || this > other
}
class Read(d:Date,u:Int) extends AbstractEntity {
val usage = u
def <(other:Read) = usage < other.usage
}
// AbstractEntity extends SaneCompare?
// God class above AbstractEntity subsumes it?
37
38. abstract class SaneCompare[T] {
def <(other:T):Boolean
def >(other:T) = !(this < other)
def <=(other:T) = this == other || this < other
def >=(other:T) = this == other || this > other
}
class Read(d:Date,u:Int) extends AbstractEntity {
val usage = u
def <(other:Read) = usage < other.usage
}
// AbstractEntity extends SaneCompare?
// God class above AbstractEntity subsumes it?
// these are different concepts entirely
38
39. trait SaneCompare[T] {
def <(other:T):Boolean
def >(other:T) = !(this < other)
def <=(other:T) = this == other || this < other
def >=(other:T) = this == other || this > other
}
class Read(d:Date,u:Int) extends AbstractEntity
with SaneCompare[Read] {
val usage = u
def <(other:Read) = usage < other.usage
}
// now we have both!
39
42. Traits
•Separate Concerns
• Precedence is based on declaration order
• All abstract – just like Java interface
• None abstract – multiple inheritance
42
52. class State(val code:String,val desc:String)
val states = getAllStates
// returns a List[String] with the codes
states.map( (state) => state.code )
// returns true if any state has a code of “DC”
states.exists( (state) => state.code == “DC” )
// returns the state with the desc of “Hawaii”
states.find( (state) => state.desc == “Hawaii” )
// returns a List[State] if states with descs matching
states.filter( (state) => state.desc.startsWith(”V”) )
// Tons more
52
61. Proper F’ing Properties
class ServicePoint(val id:String,var name:String)
val sp = new ServicePoint(”foo”,”The Foo House”)
println(sp.id) // get, but no set
println(sp.name)
sp.name = “Thy Foo Haüs”
61
62. Proper F’ing Properties
class ServicePoint(val id:String,private var _name:String) {
def name = _name.toUpperCase
def name_=(newName:String) = _name = newName
}
val sp = new ServicePoint(”foo”,”The Foo House”)
sp.name = “Thy Foo Haüs”
println(sp.name) // prints THY FOO HAÜS
62
63. ¡Adiós Checked Exceptions!
def readFile(f:File) = {
val is = new FileInputStream(f)
var ch = f.read
while (ch != -1) {
print(ch)
ch = f.read
}
} // Wow, that was clean!
63
64. ¡Adiós Checked Exceptions!
def readFile(f:File) = {
try {
val is = new FileInputStream(f)
var ch = f.read()
while (ch != -1) {
print(ch)
ch = f.read
}
} catch {
case fnfe:FileNotFoundException =>
println(f + ” not found, dude: ” + fnfe)
} // All others bubble out, even if checked in Java
}
64
65. Can I get a closure?
class Logger(level:Int) {
def debug(message: => String) = log(20,message)
def info(message: => String) = log(10,message)
def log(logLevel:Int, message: => String) = {
if (level >= logLevel) println(message)
}
}
val log = new Logger(10)
log.debug(“Got read for “ + read.date +
“ with usage “ + read.usage)
read.usage = 44
log.info(if read.usage < 10 “low read”
else “high read”)
65
66. Can I get a closure?
class Logger(level:Int) {
def debug(message: => String) = log(20,message)
def info(message: => String) = log(10,message)
def log(logLevel:Int, message: => String) = {
if (level >= logLevel) println(message)
}
}
val log = new Logger(10)
log.debug(“Got read for “ + read.date +
“ with usage “ + read.usage)
read.usage = 44
log.info(if read.usage < 10 “low read”
else “high read”)
66
67. Can I get a closure?
class Logger(level:Int) {
def debug(message: => String) = log(20,message)
def info(message: => String) = log(10,message)
def log(logLevel:Int, message: => String) = {
if (level >= logLevel) println(message)
}
}
val log = new Logger(10)
log.debug(“Got read for “ + read.date +
“ with usage “ + read.usage)
read.usage = 44
log.info(if read.usage < 10 “low read”
else “high read”)
67
68. Can I get a closure?
class Logger(level:Int) {
def debug(message: => String) = log(20,message)
def info(message: => String) = log(10,message)
def log(logLevel:Int, message: => String) = {
if (level >= logLevel) println(message)
}
}
val log = new Logger(10)
log.debug(“Got read for “ + read.date +
“ with usage “ + read.usage)
read.usage = 44
log.info(if read.usage < 10 “low read”
else “high read”)
68
69. Can I get a closure?
class Logger(level:Int) {
def debug(message: => String) = log(20,message)
def info(message: => String) = log(10,message)
def log(logLevel:Int, message: => String) = {
if (level >= logLevel) println(message)
}
}
val log = new Logger(10)
log.debug(“Got read for “ + read.date +
“ with usage “ + read.usage)
read.usage = 44
log.info(if read.usage < 10 “low read”
else “high read”)
69
70. “Literals”
val triStateArea = List(”MD”,”DC”,”VA”)
val theSouth = Map(”MD” -> true,”DC” -> false,
”VA” ->true)
val perlCirca96 = (true,”Tuples rule”)
val (hasTuples,message) = perlCirca96
70
71. “Literals”
val triStateArea = List(”MD”,”DC”,”VA”)
val theSouth = Map(”MD” -> true,”DC” -> false,
”VA” ->true)
val perlCirca96 = (true,”Tuples rule”)
val (hasTuples,message) = perlCirca96
These are actually API calls
71
72. “Literals”
val triStateArea = List(”MD”,”DC”,”VA”)
val theSouth = Map(”MD” -> true,”DC” -> false,
”VA” ->true)
val perlCirca96 = (true,”Tuples rule”)
val (hasTuples,message) = perlCirca96
This is done by the compiler
creates a Tuple2[Boolean, String]
72
73. “Literals”
class Read(val id:Int, val usage:Int, val age:Int)
object Read {
def apply(id:Int,usage:Int,age:Int) =
new Read(id,usage,age)
}
val read = Read(4,10,33)
73
74. “Literals”
class Read(val id:Int, val usage:Int, val age:Int)
object Read {
def apply(id:Int,usage:Int,age:Int) =
new Read(id,usage,age)
}
val read = Read(4,10,33) // shortcut via compiler
val read2 = Read.apply(4,10,33)
74
75. Crazy Awesome - Pattern Matching
def fromEnglish(string:String) = string match {
case “none” => 0
case “one” => 1
case _ => 2
}
75
76. Crazy Awesome - Pattern Matching
def toUML(obj:Any) = obj match {
case 0 => “0”
case 1 => “0..1”
case n:Int => “0..” + n
case true => “1”
case false => “0”
case “many” => “0..*”
case _ => “0..*”
}
76
77. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
77
78. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
• properties
• equals/toString/hashCode
• “extractor”
• no need for “new”
78
79. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
79
80. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
80
81. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
81
82. Crazy Awesome - Pattern Matching
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
def proRate(read:Read) = read match {
case AMIRead(d,usage,duration) => usage / duration
case BillingRead(d,usage,days,c) => usage / days
case CorrectedRead(BillingRead(d,oldUsage,days,c),usage) =>
(oldUsage + usage) / days
}
82
83. Crazy Awesome - Implicits
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
implicit def readToSeconds(r:Read):Int = r.date.getTime / 1000
def areConsecutive(from:Read, to:Read) =
(from - to) <= from.duration
83
84. Crazy Awesome - Implicits
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
implicit def readToSeconds(r:Read):Int = r.date.getTime / 1000
def areConsecutive(from:Read, to:Read) =
(from - to) <= from.duration
Have a Read, but need an Int
84
85. Crazy Awesome - Implicits
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
implicit def readToSeconds(r:Read):Int = r.date.getTime / 1000
def areConsecutive(from:Read, to:Read) =
(from - to) > from.duration
Needs a Read and gives an Int
85
86. Crazy Awesome - Implicits
sealed abstract class Read
case class AMIRead(date:Date,usage:Int,duration:Int) extends Read
case class BillingRead(toDate:Date,usage:Int,numDays:Int,charges:Int) extends Read
case class CorrectedRead(read:BillingRead, usage:Int) extends Read
implicit def readToSeconds(r:Read):Int = r.date.getTime / 1000
def areConsecutive(from:Read, to:Read) =
(from - to) > from.duration
• Given this and matching, casting is rarely needed
86
87. Crazy Awesome - XML Literals
val xml = <html>
<head>
<title>Scala Pronunciation Guide</title>
</head>
<body>
<h1>How to Pronounce It</h1>
</body>
</html>
println(xml)
87
88. Crazy Awesome - XML Literals
val lang = getLang
val title = translate(”scala.title”,lang)
val xml = <html lang={lang}>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title.toUpperCase}</h1>
</body>
</html>
println(xml)
88
89. Crazy Awesome - XML Literals
val lang = getLang
val title = translate(”scala.title”,lang)
val xml = <html lang={lang}>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title.toUpperCase}</h1>
</body>
</html>
println(xml)
89
90. Crazy Awesome - XML Literals
val states = List(”DC”,”MD”,”VA”)
val xml = <html>
<body>
<h1>States</h1>
<ul>
{ states.map( (state) => <li>{state}</li> ) }
</ul>
</body>
</html>
println(xml)
90
94. Message Passing
Immutable objects
“actors” with “mailboxes”
94
95. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
95
96. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
96
97. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
97
98. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
98
99. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
99
100. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
100
101. case class Accumulate(amount: Int)
case class Reset
case class Total
object Accumulator extends Actor {
def act = {
var sum = 0
loop {
react {
case Accumulate(n) => sum += n
case Reset => sum = 0
case Total => reply(sum); exit
}
}
}
}
object Accumulators extends Application {
Accumulator.start
for(i <- (1 to 100)) {
Accumulator ! Accumulate(i)
}
Accumulator !? Total match {
case result: Int => println(result)
}
}
101
107. for all consecutive reads r1 and r2
if r2 - r1 > one day
fill gaps for (r1,r2)
107
108. fill:
for all reads (first,second,List(rest))
if gap(first,second)
fill_gap(first,second) + fill(second + rest)
else
first + fill(second + rest)
108
109. fill:
for all reads (first,second,List(rest))
if !first || !second
reads
else if gap(first,second)
fill_gap(first,second) + fill(second + rest)
else
first + fill(second + rest)
109
110. def fillReads(
strategy: (MeterRead,MeterRead) => Seq[MeterRead],
reads:List[MeterRead]):List[MeterRead] =
reads match {
case List() => List()
case first :: List() => List(first)
case first :: second :: rest if gap(first, second) =>
first :: strategy(x,y).toList ::: fillReads(strategy, second :: rest)
case first :: rest => first :: fillReads(strategy,rest)
}
110
111. def fillReads(
strategy: (MeterRead,MeterRead) => Seq[MeterRead],
reads:List[MeterRead]):List[MeterRead] =
reads match {
case List() => List()
case first :: List() => List(first)
case first :: second :: rest if gap(first, second) =>
first :: strategy(x,y).toList ::: fillReads(strategy, second :: rest)
case first :: rest => first :: fillReads(strategy,rest)
}
111
112. def fillReads(
strategy: (MeterRead,MeterRead) => Seq[MeterRead],
reads:List[MeterRead]):List[MeterRead] =
reads match {
case List() => List()
case first :: List() => List(first)
case first :: second :: rest if gap(first, second) =>
first :: strategy(x,y).toList ::: fillReads(strategy, second :: rest)
case first :: rest => first :: fillReads(strategy,rest)
}
112