scala notes (3) - Files & Regular Expression, Trait, Operation and Function

- 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.
class SavingsAccount extends Account with FileLogger with ShortLogger -> linearization is,

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
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章