- Files & Regular Expressions
- read from file, url and string, remember to close source
val source = Source.fromFile("myfile.txt", "UTF-8")
val source1 = Source.fromURL("http://horstmann.com", "UTF-8")
val source2 = Source.fromString("Hello, World!")
- iterator over source, character ,
for (c <- source) process c
source.close
- use Java classes to write, out.print(f"$quantity%6d $price%10.2f")
- collections are serializable, objectoutputstream, objectinputstream
- process control, invoke shell script
import scala.sys.process._
"ls -al ..".! //output to standard output
val result = "ls -al /".!! //write to string
=============regular expression==============
- var numPat = "[0-9]+".r
- """\s+[0-9]+\s+""".r -> raw string
- numPat.findAllIn("dd99"), numPat.findFirstIn, replaceAllIn, replaceFirstIn
- match entire string -> "^[0-9]+$".r or string.matches(regex string)
- replaceSomeIn
val varPattern = """\$[0-9]+""".r
def format(message: String, vars: String*) =
varPattern.replaceSomeIn(message, m => vars.lift(m.matched.tail.toInt))//m.matched is a string. lift method turns vars sequence to a
//function. return Some or None if index is invalid
format("At $1, there was $2 on $0.", "planet 7", "12:30 pm", "a disturbance of the force")
- match group, findAllMatchIn, findFirstMatchIn
val numitemPattern = "([0-9]+) ([a-z]+)".r
for (m <- numitemPattern.findAllMatchIn("99 bottles, 98 bottles"))// m.group, m.start, m.end
println(m.group(1)) // Prints 99 and 98
- extract matches (unapply)
val numitemPattern(num, item) = "99 bottles"
for (numitemPattern(num, item) <- numitemPattern.findAllIn("99 bottles, 98 bottles"))
process num and item
- Traits (not only interface but also service)
- avoid diamond inheritance and have more power than interface
trait Logger {
def log(msg: String) // An abstract method
}
class ConsoleLogger extends Logger { // Use extends, not implements
def log(msg: String) { println(msg) } // No override needed
}
class ConsoleLogger extends Logger with Cloneable with Serializable
- mix-in concrete trait to object
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else ...
}
...
}
trait ConsoleLogger extends Logger {
def log(msg: String) { println(msg) }
}
val acct = new SavingsAccount with ConsoleLogger
val acct2 = new SavingsAccount with FileLogger
- layered traits (super call, invoke from right to left, or super[ConsoleLogger].log, ConsoleLogger must beimmediate supertype)
trait TimestampLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(s"${java.time.Instant.now()} $msg")
}
}
trait ShortLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(
if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ShortLogger with TimestampLogger
super either call supertype's concrete method or the method where super call is issued itself is abstract.
- rich interface in trait -> other concrete methods call abstract method
- fields in trait are added to subclass, abstract fields need to be set in subclass or provide dynamically when construct object, like, class SavingsAccount extends ShortLogger{ val maxLen = 20}. If val maxLen: Int is defined in ShortLogger trait.
- construction order, superclass -> common parent trait (once) -> left trait -> right trait -> subclass. The order is reverse of linearization of class.
SavingsAccount » ShortLogger » FileLogger » Logger » Account, both log and super.log follow the linearization.
class ExBase(private val exNbr: Int) extends Logger{
def log(msg: String){
println(s"ExBase log: $exNbr: $msg")
}
}
class Ex4(private val msg: String, exNbr: Int) extends ExBase(exNbr) with CipherLogger with LeveledLogger{
def logMsg(){
// super.log(msg)
log(msg)
}
}
trait Logger{
def log(msg: String)
}
trait ConsoleLogger extends Logger{
abstract override def log(msg: String){
super.log(msg)
}
}
trait CipherLogger extends ConsoleLogger{
var key = 3
abstract override def log(msg: String){
super.log(Exercise.encrypt(msg, key))
}
}
trait LeveledLogger extends ConsoleLogger{
abstract override def log(msg: String){
super.log("level: "+msg)
}
def getLevel()={
1
}
}
- trait fields initialization, early definition or lazy variable.
trait FileLogger extends Logger {
val filename: String
val out = new PrintStream(filename)
}
val acct = new { // Early definition block after new
val filename = "myapp.log"
} with SavingsAccount with FileLogger
class SavingsAccount extends { // Early definition block after extends
val filename = "savings.log"
} with Account with FileLogger {
... // SavingsAccount implementation
}
- trait can extend class
- self type
trait LoggedException extends ConsoleLogger {//cannot be mixed into subclass of Exception
this: Exception =>
def log() { log(getMessage()) }
}
trait LoggedException extends ConsoleLogger {//structural type is also supported
this: { def getMessage() : String } =>
def log() { log(getMessage()) }
}
- Operations
- `` to escape keyword, like Thread.`yield`()
- unary operator, - => unary_-
- right-associative operators: end with ':' and assignment operators
- apply and update
f(a1, a2, ...) -> f.apply(a1, a2, ...)
f(a1, a2, ...) = value -> f.update(a1, a2, ..., value)
val scores = new scala.collection.mutable.HashMap[String, Int]
scores("Bob") = 100 // Calls scores.update("Bob", 100)
val bobsScore = scores("Bob") // Calls scores.apply("Bob")
class Fraction(n: Int, d: Int) {
...
}
object Fraction {
def apply(n: Int, d: Int) = new Fraction(n, d)
}
val f = Fraction(2, 3)
- extractor (unapply)
object Fraction {
def unapply(input: Fraction) = // return type is Option tuple
if (input.den == 0) None else Some((input.num, input.den))
}
val f = Fraction(2, 3)
val Fraction(a, b) = f // MatchError if unapply return None
object Name { //no need to have Name class
def unapply(input: String) = {
val pos = input.indexOf(" ")
if (pos == -1) None
else Some((input.substring(0, pos), input.substring(pos + 1)))
}
}
val author = "Cay Horstmann"
val Name(first, last) = author
case class has apply and unapply methods automatically.
object Number {
def unapply(input: String): Option[Int] = //if only one value to be returned, just use Option without tuple
try {
Some(input.trim.toInt)
} catch {
case ex: NumberFormatException => None
}
}
val Number(n) = "1729"
when extract no value, unapply needs to return boolean
object IsCompound {
def unapply(input: String) = input.contains(" ") // boolean returned
}
author match {
case Name(first, IsCompound()) => ... //no value extracted
// Matches if the last name is compound, such as van der Linden
case Name(first, last) => ...
}
- unapplySeq return arbitrary number of values.
object Name {
def unapplySeq(input: String): Option[Seq[String]] =
if (input.trim == "") None else Some(input.trim.split("\\s+"))
}
author match {
case Name(first, last) => ...
case Name(first, middle, last) => ...
case Name(first, "van", "der", last) => ...
...
}
- Dynamic invocation (extends scala.Dynamic)
person.lastName = "Doe" => person.updateDynamic("lastName")("Doe")
val name = person.lastName => val name = person.selectDynamic("lastName")
val does = people.findByLastName("Doe") => val does = people.applyDynamic("findByLastName")("Doe")
val johnDoes = people.find(lastName = "Doe", firstName = "John") => people.applyDynamicNamed("find")(("lastName, "Doe"), ...)
- String Interpolation (constructor with parameter of StringContext and a method)
implicit class JsonHelper(private val sc: StringContext) extends AnyVal {
def json(args: Any*): JSONObject = ...
}
val x: JSONObject = json"{ a: $a }"
- Functions
- first-class citizens, can be passed around and manipulated just like other data types
val fun = math.ceil _
val f: (Double) => Double = ceil //no need _
val f = (_: String).charAt(_: Int) // A function (String, Int) => Char, get function from class
f("xyz", 2) //print z
- anonymous function
val triple = (x: Double) => 3*x // def triple(x: Double) = 3*x
- higher-order function (a function that receives a function)
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
f(ceil _)
higher-order function can produce another function
def mulBy(factor: Double) = (x: Double) => x*factor // val f = mulBy(3); f(5)
- closure, function to use nonlocal variables, like val f1 = mulBy(3); val f2 = mulBy(4)
- currying (type reference and reuse)
def mulOneAtATime(x: Int)(y: Int) = x * y
- control abstraction
def run(block: ()=>Unit) = block()
to
def run(block: =>Unit) = block //looks more like keyword than function
- return statement needs function to specify return type explicitly. return is implemented by throwing a special exception. no try-catch. otherwise, return will be caught.
def indexOf(str: String, ch: Char): Int = {
var i = 0
until (i == str.length) {
if (str(i) == ch) return i
i += 1
}
return -1
}