004.《Programming in Scala,2nd》干货摘要
程序员文章站
2023-12-28 08:03:22
...
原文:http://www.moilioncircle.com/manshow/004.programming-in-Scala-2nd.html
题图:Scala之父'Martin Odersky'的书,十分精彩和经典,单词量大概4~5k,800多页,推荐进阶阅读。
# 004.《Programming in Scala,2nd》干货摘要
《Programming in Scala,2nd》的天书级的干货摘要,
建议运行并细看,遇到看不懂的,可以认为知识点不扎实了。
@史荣久 / 2015-06-09 / CC-BY-SA-3.0
## 英文的,值得细看
全书单词量,大概5k吧,像我CET4+Dict,能够啃下来。
技术书籍的语法和词汇都简单,看不懂也猜得出的。
不要以为看过本文,就不用看书了,每人每次看书的收获不同。
书中的编程例子特别好,尤其最后200行代码写出个excel。
还有本书叫《Programming Scala》,中间没有in。
书我没看,不知如何。只想说下,本文不是这本书的
# pdf变成text pdftotext Programming-in-Scala-2nd.pdf w.txt # 变小写,只字母,去重复,统计 cat p.txt|tr 'A-Z \t' 'a-z\n'| grep -E '^[A-Za-z]+$'|sort|uniq|wc -l # 6180 # 去掉ing,ed和同根词,乐观估计单词量在4~5k [b]## C5.Basic Types and Operations[/b] scala """ HEX: 0xcafebabe OCT: 0777 DEC: 0XCAFEBABEL, 35L, 255 Float: 1.23E4, 1.23F, 3e5f Double: 3e5D Char:'A', '\101', '\u0041' Symbol:'cymbal symbols are interned """ val s = 'aSymbol s.name """|operators are actually just a nice syntax |for ordinary method calls """.stripMargin 1 + 2 (1).+(2) -2.0 (2.0).unary_- """ How Scala’s == differs from Java’s a * b yields a.*(b) , but a ::: b yields b.:::(a) a ::: b ::: c is treated as a ::: (b ::: c) . But a * b * c , by contrast, is treated as (a * b) * c """ [b]## C6.Functional Objects[/b] scala "This won’t compile" class Rational(n: Int, d: Int) { require(d != 0) override def toString = n +"/"+ d def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d) } "but these are ok, val " class Rational(val n: Int, val d: Int) { require(d != 0) override def toString = n +"/"+ d def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d) def this(n: Int) = this(n, 1) // auxiliary constructor } class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer = n / g val denom = d / g def this(n: Int) = this(n, 1) def add(that: Rational): Rational = new Rational( numer * that.denom + that.numer * denom, denom * that.denom ) override def toString = numer +"/"+ denom private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) } """ ‘$’ is reserved for identifiers generated by compiler ‘_’ have many other non-identifier uses. avoid identifiers like to_string , __init__ , or name_ 'constant' is the 1st character should be upper case, such as XOffset `:->` is represented internally as $colon$minus$greater cannot write Thread.yield(), can write Thread.`yield`() def * (m:###xN) , r * 2 <--> 2 * r Method overloading .vs. Implicit conversions """ [b]## C7.Built-in Control Structures[/b] scala "if, if-else expressions" println(if (!true) "false" else "true") def half(n:Int) = if (n % 2 == 0) n / 2 else throw new RuntimeException("n must be even") "while, do-while loops, not expressions" var line = "" while ((line = readLine()) == "") // This doesn’t work! println("empty line") //`line = readLine()` will always be `()` "for expressions, ***big topic*** monad" for (i <- 1 to 4) println("Iteration "+ i) for (i <- 1 until 4) println("Iteration "+ i) "filter and variable binding" val nothing = for { file <- 1 to 4 if file % 2 == 0 // filter line <- 2 to file trimmed = line + 1 // binding if trimmed % 2 == 0 // filter } yield trimmed "try-catch-finally" def f(): Int = try { return 1 } finally { return 2 } f() // Int = 2 def g(): Int = try { 1 } finally { 2 } g() // Int = 1 """ match-case : ***big topic*** Living without break and continue @tailrec breakable-break, use Exception """ [b]## C8.Functions and Closures[/b] scala (1 to 5).filter((x) => x % 2 == 0) (1 to 5).filter(x => x % 2 == 0) (1 to 5).filter(_ % 2 == 0) val f = _ + _ //missing parameter type val f = (_: Int) + (_: Int) "Partially applied functions" def sum(a: Int, b: Int, c: Int) = a + b + c val a = sum _ val b = sum(1, _: Int, 3) val c = sum //missing arguments """ Why the trailing underscore? Scala normally requires you to specify function arguments that are left out explicitly """ (1 to 5).foreach(println _) (1 to 5).foreach(println) """ The function value (the object) that’s created at runtime from this function literal is called a `closure`. The name arises from the act of “closing” the function literal by “capturing” the bindings of its free variables """ def echo(args: String*) = for (arg <- args) println(arg) echo("hello", "world!") val arr = Array("What's", "up", "doc?") echo(arr) // type mismatch echo(arr: _*) "Named arguments,Default parameter values" def speed(distance: Float, time: Float = 1) = distance / time speed(time = 10, distance = 100) speed(distance = 100, time = 10) speed(100, 10) speed(100) [b]## C9.Control Abstraction[/b] scala "Currying" def plainOldSum(x: Int, y: Int) = x + y plainOldSum(1, 2) def curriedSum(x: Int)(y: Int) = x + y curriedSum(1)(2) def first(x: Int) = (y: Int) => x + y val second = first(1) second(2) val onePlus = curriedSum(1) _ onePlus(2) val twoPlus = curriedSum(2)_ twoPlus(2) "Writing new control structures" import java.io._ def withPrintWriter(file: File)(op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() } } val file = new File("date.txt") withPrintWriter(file) { writer => writer.println(new java.util.Date) } "By-name parameters" var assertionsEnabled = false def myAssert(predicate: () => Boolean) = if (assertionsEnabled && !predicate()) throw new AssertionError myAssert(() => 5 > 3) myAssert(5 > 3) // Won’t work, because missing () => def byNameAssert(predicate: => Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError byNameAssert(5 > 3) def boolAssert(predicate: Boolean) = if (assertionsEnabled && !predicate) throw new AssertionError val x = 0 boolAssert(10 / x == 0) // / by zero byNameAssert(10 / x == 0) // OK [b]## C10.Composition and Inheritance[/b] scala """ The recommended convention is to use a parameterless method whenever there are no parameters and the method accesses mutable state only by reading fields of the containing object (in particular, it does not change mutable state) """ """ Scala has just two namespaces for definitions in place of Java’s four (fields, methods, types, and packages) Scala’s two namespaces are: • values (fields, methods, packages, singleton objects) • types (class and trait names) """ :paste object Element { private class ArrayElement( val contents: Array[String]) extends Element private class LineElement(s: String) extends Element { val contents = Array(s) override def width = s.length override def height = 1 } private class UniformElement( ch: Char, override val width: Int, override val height: Int) extends Element { private val line = ch.toString * width def contents = Array.fill(height)(line) } def apply(contents: Array[String]): Element = new ArrayElement(contents) def apply(chr: Char, width: Int, height: Int): Element = new UniformElement(chr, width, height) def apply(line: String): Element = new LineElement(line) } abstract class Element { def contents: Array[String] def width: Int = contents(0).length def height: Int = contents.length def above(that: Element): Element = { val this1 = this widen that.width val that1 = that widen this.width Element(this1.contents ++ that1.contents) } def beside(that: Element): Element = { val this1 = this heighten that.height val that1 = that heighten this.height Element( for ((line1, line2) <- this1.contents zip that1.contents) yield line1 + line2) } def widen(w: Int): Element = if (w <= width) this else { val left = Element(' ', (w - width) / 2, height) var right = Element(' ', w - width - left.width, height) left beside this beside right } def heighten(h: Int): Element = if (h <= height) this else { val top = Element(' ', width, (h - height) / 2) var bot = Element(' ', width, h - height - top.height) top above this above bot } override def toString = contents mkString "\n" } val space = Element(" ") val corner = Element("+") def spiral(nEdges: Int, direction: Int): Element = { if (nEdges == 1) Element("+") else { val sp = spiral(nEdges - 1, (direction + 3) % 4) def verticalBar = Element('|', 1, sp.height) def horizontalBar = Element('-', sp.width, 1) if (direction == 0) (corner beside horizontalBar) above (sp beside space) else if (direction == 1) (sp above space) beside (corner above verticalBar) else if (direction == 2) (space beside sp) above (horizontalBar beside corner) else (verticalBar above corner) beside (space above sp) } } //end :paste ctrl-d spiral(17,0) [b]## C11.Scala’s Hierarchy[/b] scala """ |<-AnyVal<-(Unit,Int,Double...)<-Nothing Any | v |<-AnyRef<-(Seq,Set,Map,String...)<-Null """ [b]## C12.Traits[/b] scala "mix-in traits rather than inherit from them" class Point(x: Int, y: Int) trait NoPoint(x: Int, y: Int) // Does not compile trait NoPoint(val x: Int, val y: Int) class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal with Furry with FourLegged """ Animal:Animal,AnyRef,Any Furry:Furry,Animal,AnyRef,Any FourLegged:FourLegged,HasLegs,Animal,AnyRef,Any HasLegs:HasLegs,Animal,AnyRef,Any Cat:Cat,FourLegged,HasLegs,Furry,Animal,AnyRef,Any When any of these classes and traits invokes a method via `super` , the implementation invoked will be Cat->FourLegged->HasLegs->Furry->Animal->AnyRef->Any If the behavior will not be reused, then make it a concrete class. If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy. If you want to inherit from it in Java code, use an abstract class. """ [b]## C13.Packages and Imports[/b]scala
"follow Java’s reverse-domain-name convention"
package com {
package moilioncircle {
package public {
class P01
}
class Readme {
val booster1 = 0
}
}
package trydofor {
class X3
}
}
import com.trydofor.X3
import com.moilioncircle._ // *,all
import com.moilioncircle{Readme => R, public} // rename
import com.moilioncircle{Readme => R, _} // rename, *
import com.{trydofor => _, _} // all but trydofor
def regex(){
import java.util.regex
}
"""
private[bobsrockets] access within outer package
private[navigation] same as package visibility in Java
private[Navigator] same as private in Java
private[LegOfJourney] same as private in Scala
private[this] access only from same object
"""
package bobsrockets
package navigation {
private[bobsrockets] class Navigator {
protected[navigation] def useStarChart() {}
class LegOfJourney {
private[Navigator] val distance = 100
}
private[this] var speed = 200
}
}
"""
A class shares all its access rights
with its companion object and vice versa.
"""
"""
package object. Each package is allowed
to have one package object. Any definitions
placed in a package object are
considered members of the package itself.
"""
// In file bobsdelights/package.scala
package object bobsdelights {
def showFruit(fruit: Fruit) {
import fruit._
println(name +"s are "+ color)
}
}
import bobsdelights.showFruit
## C14.Assertions and Unit Testing
""" Assertions (and ensuring checks) can be enabled and disabled using the JVM’s -ea and -da flags """ def addNaturals(nats: List[Int]): Int = { assert(nats != null) require(nats forall (_ >= 0), "negative numbers") nats.foldLeft(0)(_ + _) } ensuring(_ >= 0) """ Using JUnit and TestNG ==>XUnit Tests as specifications==>ScalaTest Property-based testing ==>ScalaCheck """
## C15.Case Classes and Pattern Matching
//case class BinOp,Number "Wildcard patterns" expr match { case BinOp(_, _, _) => println(expr +" is BinOp") case _ => println("It's something else") } "Constant patterns" def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" case _ => "something else" } "Variable patterns" expr match { case 0 => "zero" case somethingElse => "not zero: "+ somethingElse } "Variable or constant?" import math.{E, Pi} E match { case Pi => "strange math? Pi = "+ Pi case _ => "OK" } val pi = math.Pi E match { case pi => "strange math? Pi = "+ pi } E match { case `pi` => "strange math? Pi = "+ pi case _ => "OK" } // as a constant, not as a variable: "Constructor patterns" expr match { case BinOp("+", e, Number(0)) => println("a deep match") case _ => } "Sequence patterns" expr match { case List(0, _, _) => println("found it") case _ => } expr match { case List(0, _*) => println("found it") case _ => } "Tuple patterns" expr match { case (a, b, c) => println("matched "+ a + b + c) case _ => } "Typed patterns" x match { case s: String => s.length case m: Map[_, _] => m.size // Type erasure case _ => -1 } x match { case a: Array[String] => "yes" // NOT Type erasure case _ => "no" } "Variable binding" //UnOp("abs", _) expr match { case UnOp("abs", e @ UnOp("abs", _)) => e case _ => } "Pattern guards" e match { case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2)) case _ => e } "Sealed classes" sealed abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr "The Option type" x match { case Some(s) => s case None => "?" } "Patterns everywhere" val myTuple = (123, "abc") val (number, string) = myTuple val BinOp(op, left, right) = exp val second: List[Int] => Int = { case x :: y :: _ => y } val a :: b :: rest = fruit val List(a, b, c) = fruit for ((country, city) <- capitals) for (Some(fruit) <- results)
## C16.Working with Lists
"The list type in Scala is covariant" val fruit = List("apples", "oranges", "pears") val fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) """ xs ::: ys ::: zs is interpreted like this: xs ::: (ys ::: zs) """ val abcde = List('a', 'b', 'c', 'd', 'e') abcde.last //e abcde.init // List(a, b, c, d) abcde.reverse // List(e, d, c, b, a) abcde take 2 // List(a, b) abcde drop 2 // List(c, d, e) abcde splitAt 2 // (List(a, b),List(c, d, e)) abcde(2) // Char=c abcde.indices //Range(0, 1, 2, 3, 4) List(List(1, 2), List(3), List(), List(4, 5)).flatten // List(1, 2, 3, 4, 5) abcde.indices zip abcde // IndexedSeq((0,a), (1,b), (2,c), (3,d), (4,e)) val zipped = abcde zip List(1, 2, 3) // List((a,1), (b,2), (c,3)) abcde.zipWithIndex // List((a,0), (b,1), (c,2), (d,3),(e,4)) zipped.unzip // (List(a, b, c),List(1, 2, 3)) abcde mkString ("[", ",", "]") // String = [a,b,c,d,e] val buf = new StringBuilder abcde addString (buf, "(", ";", ")") // StringBuilder = (a;b;c;d;e) val arr = abcde.toArray // Array(a, b, c, d, e) arr.toList // List(a, b, c, d, e) val arr2 = new Array[Int](10) //Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) List(1, 2, 3) copyToArray (arr2, 3) //Array(0, 0, 0, 1, 2, 3, 0, 0, 0, 0) "Higher-order methods on class List" List(1, 2, 3) map (_ + 1) // List(2, 3, 4) val words = List("the", "quick") words map (_.toList) //List(List(t, h, e), List(q, u, i, c, k)) words flatMap (_.toList) //List(t, h, e, q, u, i, c, k) for (i <- List.range(1, 5); j <- List.range(1, i)) yield (i, j) List(1, 2, 3, 4, 5) foreach (println) List(1, 2, 3, 4, 5) filter (_ % 2 == 0) //List(2, 4) List(1, 2, 3, 4, 5) partition (_ % 2 == 0) // (List(2, 4),List(1, 3, 5)) List(1, 2, 3, 4, 5) find (_ % 2 == 0) // Some(2) List(1, 2, 3, 4, 5) find (_ <= 0) // None List(1, 2, 3, -4, 5) takeWhile (_ > 0) //List(1, 2, 3) List(1, 2, 3, -4, 5) dropWhile (_ > 0) //List(-4, 5) List(1, 2, 3, -4, 5) span (_ > 0) //(List(1, 2, 3),List(-4, 5)) List(1, 2, 3, -4, 5) forall (_>0) //false List(1, 2, 3, -4, 5) exists (_>0) //true ("0" /: List(1, 2, 3, 4, 5))(_+","+_) List(1, 2, 3, 4, 5)./:("0")(_+","+_) //String = 0,1,2,3,4,5 (List(1, 2, 3, 4, 5) :\ "6")(_+","+_) //String = 1,2,3,4,5,6 List(1, -3, 4, 2, 6) sortWith (_ < _) //List(-3, 1, 2, 4, 6) List.fill(5)('a') // List(a, a, a, a, a) List.fill(2, 3)('b') // List(List(b, b, b), List(b, b, b)) List.tabulate(5)(n => n * n) //List(0, 1, 4, 9, 16) List.tabulate(2,3)(_ * _) //List(List(0, 0, 0), List(0, 1, 2))
## C17.Collections
"Sequences" List("red", "blue", "green") Array(5, 4, 3, 2, 1) new Array[Int](5) import scala.collection.mutable.ListBuffer import scala.collection.mutable.ArrayBuffer // Strings (via StringOps ) Set(1,2,3,4,5) Map("i" -> 1, "ii" -> 2) "factory method will return a different class" scala.collection.immutable.{EmptySet, Set1,Set2,Set3,Set4,HashSet} scala.collection.immutable.{EmptyMap, Map1,Map2,Map3,Map4,HashMap} "Sorted sets and maps" import scala.collection.immutable.TreeSet import scala.collection.immutable.TreeMap "mutable versus immutable collections" import scala.collection.immutable import scala.collection.mutable "Converting between mutable and immutable" val set = Set(1,2,3) val mutaSet = mutable.Set.empty ++= set val immutaSet = Set.empty ++ mutaSet val map = Map("i" -> 1, "ii" -> 2) val mutaMap = mutable.Map.empty ++= map val immutaMap = Map.empty ++ mutaMap """ Because tuples can combine objects of different types, tuples do not inherit from Traversable """
## C18.Stateful Objects
class Time { private[this] var h = 12 def hour: Int = h def hour_= (x: Int) { require(0 <= x && x < 24) h = x } }
## C19.Type Parameterization
"Private constructors and factory methods" class Queue[T] private ( // private constructor private val leading: List[T], // private leading() val trailing: List[T] // public trailing() ) :paste class Queue[T] private ( private val leading: List[T], private val trailing: List[T] ) object Queue { // constructs a queue with initial elements ‘xs’ def apply[T](xs: T*) = new Queue[T](xs.toList, Nil) } // :paste ctlr-d :paste trait Queue[T] { def head: T def tail: Queue[T] } object Queue { def apply[T](xs: T*): Queue[T] = new QueueImpl[T](xs.toList, Nil) private class QueueImpl[T]( private val leading: List[T], private val trailing: List[T] ) extends Queue[T]{ def head: T = leading.head def tail: QueueImpl[T] = new QueueImpl(leading.tail, trailing) } } // :paste ctlr-d "require less and provide more" trait Function1[-S, +T] { def apply(x: S): T } class Queue[T]( private[this] var leading: List[T] //Object private data ){ def lower[U >: T](x: U){} //Lower bounds def upper[V <: T](x: V){} //Upper bounds }
## C20.Abstract Members
trait Abstract { type T //abstract type def transform(x: T): T //abstract method val initial: T //abstract val var current: T //abstract var } class Concrete extends Abstract { type T = String def transform(x: String) = x + x val initial = "hi" var current = initial } abstract class Fruit { val v: String // ‘v’ for value def m: String // ‘m’ for method } abstract class BadApple extends Fruit { def v: String // ERROR: cannot override a ‘val’ with a ‘def’ val m: String // OK to override a ‘def’ with a ‘val’ } trait AbstractTime { var hour: Int } trait AbstractTime { def hour: Int // getter for ‘hour’ def hour_=(x: Int) // setter for ‘hour’ } """ A class parameter argument is evaluated before it is passed to the class constructor (unless the parameter is by-name) """ trait RationalTrait { val numerArg: Int val denomArg: Int require(denomArg != 0) override def toString = numerArg +"/"+ denomArg } new RationalTrait { val numerArg = 1 val denomArg = 2 } // IllegalArgumentException: requirement failed "Pre-initialized fields" val x = 1 new { //anonymous val numerArg = 1 * x val denomArg = 2 * x } with RationalTrait object twoThirds extends { // objects val numerArg = 2 val denomArg = 3 } with RationalTrait class RationalClass(n: Int, d: Int) extends { //named val numerArg = n val denomArg = d } with RationalTrait "" class Food class Grass extends Food abstract class Animal { def eat(food: Food) } class Cow extends Animal { override def eat(food: Grass) {} // This won’t compile, Food } // Cow can eat any Food ? // compiled and type checking abstract class Animal { type SuitableFood <: Food def eat(food: SuitableFood) } class Cow extends Animal { type SuitableFood = Grass override def eat(food: Grass) {} // OK } "Structural subtyping" // animal that eats grass var animals: List[Animal { type SuitableFood = Grass }]=Nil def using[T <: { def close(): Unit }, S](obj: T){} "Enumeration" object Color extends Enumeration { val Red, Green = Value val Blue = Value("My Blue") } "Currency Example is very Good !!"
## C21.Implicit Conversions and Parameters
"Marking Rule: Only definitions marked implicit are available." implicit def intToString(x: Int) = x.toString """ Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion. """ :paste object Count { implicit def Int2Count(x: Int): Count = new Count } class Count { } import Count._ // :paste ctlr-d """ One-at-a-time Rule: Only one implicit is tried. Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted. The compiler will not change code that already works. Where implicits are tried. There are three places implicits are used: 1 conversions to an expected type, 2 conversions of the receiver of a selection, 3 and implicit parameters. """ "conversions to an expected type," val i: Int = 3.5 //type mismatch; implicit def doubleToInt(x: Double) = x.toInt val i: Int = 3.5 val i: Int = doubleToInt(3.5) "Converting the receiver" class Rational(n: Int, d: Int) { def + (that: Int): Rational = new Rational(n+d*that,d) override def toString = n +"/"+ d } val oneHalf = new Rational(1, 2) oneHalf + 1 1 + oneHalf // not good implicit def intToRational(x: Int) = new Rational(x, 1) // how about `def + (that :XXX)` * n "Simulating new syntax --->" object UM { class M[A](x: A) { def ---> [B](y: B): Tuple2[A, B] = Tuple2(x, y) } implicit def any2M[A](x: A): M[A] = new M(x) } import UM._ Map(1 ---> "one", 2 ---> "two") """View bounds You can think of “ T < % Ordered[T] ” as saying, “I can use any T, so long as T can be treated as an Ordered[T].” implicit T => Ordered[T]. """ "Debugging implicits `-Xprint:typer`"
## C22.Implementing Lists
"List in java is interface, but in scala is class" sealed abstract class List[+A] case object Nil extends List[Nothing] final case class ::[T](hd: T, tl: List[T]) extends List[T] """ Class `::` , pronounced “cons” for “construct,” represents non-empty lists. It’s named that way in order to support pattern matching. x :: xs is treated as ::(x, xs) """ """ The List class in practice suffer from the same stack overflow problem as the non-tail recursive implementation of incAll. Therefore, most methods in the real implementation of class List avoid recursion and use loops with list buffers instead The design of Scala’s List and ListBuffer is quite similar to what’s done in Java’s pair of classes String and StringBuffer """
## C23.For Expressions Revisited
case class Person(name: String,isMale: Boolean,children: Person*) val lara = Person("Lara", false) val bob = Person("Bob", true) val julie = Person("Julie", false, lara, bob) val persons = List(lara, bob, julie) """ using a withFilter call instead of filter would avoid the creation of an intermediate data structure """ persons filter (p => !p.isMale) flatMap (p => (p.children map (c => (p.name, c.name)))) persons withFilter (p => !p.isMale) flatMap (p => (p.children map (c => (p.name, c.name)))) "braces instead of parentheses" for (p <- persons; if !p.isMale; c <- p.children) yield (p.name, c.name) for { p <- persons //generator m = p.isMale //definition same as `val m = p.isMale` if !m //filter c <- p.children //generator }yield (p.name, c.name) """ for ( x <- expr1 ) yield expr2 ==> expr1.map( x => expr2 ) for( x <- expr1 if expr2 ) yield expr3 ==> for(x <- expr1 withFilter (x => expr2 )) yield expr3 ==> expr1 withFilter ( x => expr2 ) map ( x => expr3 ) for ( x <- expr1 ; y <- expr2 ; seq) yield expr3 ==> expr1.flatMap( x => for ( y <- expr2 ; seq) yield expr3 ) ==> ... for ( x <- expr1 ; y = expr2 ; seq) yield expr3 ==> for((x, y) <- for(x <- expr1) yield (x, expr2); seq) yield expr3 for ( x <- expr1 ) body // without yield ==> expr1 foreach ( x => body) """ """ If your type defines just map , it allows for expressions consisting of a single generator. If it defines flatMap as well as map , it allows for expressions consisting of several generators. If it defines foreach , it allows for loops (both with single and multiple generators). If it defines withFilter , it allows for filter expressions starting with an if in the for expression. """ abstract class C[A] { def map[B](f: A => B): C[B] def flatMap[B](f: A => C[B]): C[B] def withFilter(p: A => Boolean): C[A] def foreach(b: A => Unit): Unit }
## C24.The Scala Collections API
""" Traversable Iterable Seq IndexedSeq Vector ResizableArray GenericArray LinearSeq MutableList List Stream Buffer ListBuffer ArrayBuffer Set SortedSet TreeSet HashSet (mutable) LinkedHashSet HashSet (immutable) BitSet EmptySet, Set1, Set2, Set3, Set4 Map SortedMap TreeMap HashMap (mutable) LinkedHashMap (mutable) HashMap (immutable) EmptyMap, Map1, Map2, Map3, Map4 """ """ If xs is some collection, then xs.view is the same collection, but with all transformers implemented lazily. To get back from a view to a strict collection, you can use the force method. """ val v = Vector(1 to 3) // Vector(Range(1, 2, 3)) val v = Vector(1 to 3: _*) // Vector(1, 2, 3) val vi = v map (_ + 1) map (_ * 2) // Vector(4, 6, 8) val vl = v.view map (_ + 1) map (_ * 2) // SeqViewMM(...) vl.force // Vector(4, 6, 8) import collection.JavaConversions._
## C25.The Architecture of Scala Collections
trait Builder[-Elem, +To] { def +=(elem: Elem): this.type def result(): To } trait CanBuildFrom[-From, -Elem, +To] { def apply(from: From): Builder[Elem, To] } class TraversableLike[+Elem, +Repr] "'same-result-type' principle: T=>T" """ if you want to fully integrate a new collection class into the framework you need to pay attention to: 1. the collection should be mutable or immutable. 2. Pick the right base traits for the collection. 3. Inherit from the right implementation trait to implement most collection operations. 4. If you want map and similar operations to return instances of your collection type, provide an implicit CanBuildFrom in your class’s companion object. """
## C26.Extractors
""" An extractor in Scala is an object that has a method called `unapply` as one of its members. Often, the extractor object also defines a dual method `apply` for building values, but this is not required. """ object EMail { // The injection method (optional) def apply(user: String, domain: String) = user +"@"+ domain // The extraction method (mandatory) def unapply(str: String): Option[(String, String)] = { val parts = str split "@" if (parts.length == 2) Some(parts(0), parts(1)) else None } } """ To make this more explicit, you could also object EMail extends ((String, String) => String) selectorString match { case EMail(user, domain) => ... } ==> EMail.unapply(selectorString) EMail.unapply(EMail.apply(user, domain)) ==> Some(user, domain) """ object Twice { def apply(s: String): String = s + s def unapply(s: String): Option[String] = { val length = s.length / 2 val half = s.substring(0, length) if (half == s.substring(length)) Some(half) else None } } object UpperCase { def unapply(s: String): Boolean = s.toUpperCase == s } """ It’s possible that an extractor pattern does not bind any variables. `unapply` method returns a boolean. """ def userTwiceUpper(s: String) = s match { case EMail(Twice(x @ UpperCase()), domain) => "match: "+ x +" in domain "+ domain case _ => "no match" } userTwiceUpper("DIDI@hotmail.com") userTwiceUpper("DIDO@hotmail.com") "return a fixed number of sub-elements in the success case." object Domain { // The injection method (optional) def apply(parts: String*): String = parts.reverse.mkString(".") // The extraction method (mandatory) def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse) } def isTomInDotCom(s: String): Boolean = s match { case EMail("tom", Domain("com", _*)) => true case _ => false } isTomInDotCom("tom@sun.com") // true isTomInDotCom("tom@com.net") // false object ExpandedEMail { def unapplySeq(email: String) : Option[(String, Seq[String])] = { val parts = email split "@" if (parts.length == 2) Some(parts(0), parts(1).split("\\.").reverse) else None } } "Extracting with regular expressions" val s = "tom@support.epfl.ch" val ExpandedEMail(name, topdom, subdoms @ _*) = s val Decimal = """(-)?(\d+)(\.\d*)?""".r val input = "for -1.0 to 99 by 3" for (s <- Decimal findAllIn input) println(s) val Decimal(sign, integerpart, decimalpart) = "-1.23" val Decimal(sign, integerpart, decimalpart) = "1.0"
## C27.Annotations
import annotation._ class strategy(arg: Annotation) extends Annotation class delayed extends Annotation @strategy(@delayed) def f(){} // use `new` instead of `@` @strategy(new delayed) def f(){} """ @deprecated("use newShinyMethod() instead") @volatile @serializable @SerialVersionUID(1234) @transient @tailrec @unchecked @native """
## C28.Working with XML
val yearMade = 1955 :paste <a> { if (yearMade < 2000) <old>{yearMade}</old> else xml.NodeSeq.Empty } </a> // :paste ctlr-d <a> {"</a>potential security hole<a>"} </a> //escape '<','&','>' <a> {{brace yourself!}} </a> // `{{}}` ==> `{}` <a>Sounds <tag/> good</a>.text <a><b><c>hello</c></b></a> \ "b" <a><b><c>hello</c></b></a> \ "c" <a><b><c>hello</c></b></a> \\ "c" """ Scala uses \ and \\ instead of XPath’s / and // . The reason is that // starts a comment in Scala! """ val joe = <employee name="Joe" rank="code monkey"/> joe \ "@name" """ scala.xml.XML.save("therm1.xml", node) val loadnode = xml.XML.loadFile("therm1.xml") """ def proc(node: scala.xml.Node): String = node match { case <a>{contents}</a> => "It's an a: "+ contents case <b>{contents @ _*}</b> => "It's a b: "+ contents case _ => "It's something else." } proc(<a>apple</a>) proc(<a>a [i]red[/i] apple</a>) proc(<b>a [i]red[/i] apple</b>) val catalog = <catalog> <cctherm> <description>hot dog #5</description> </cctherm> <cctherm> <description>Sprite Boy</description> </cctherm> </catalog> catalog match { case <catalog>{therms @ _*}</catalog> => for (therm <- therms) println(": "+ (therm \ "description").text) } catalog match { case <catalog>{therms @ _*}</catalog> => for (therm @ <cctherm>{_*}</cctherm> <- therms) println(": "+ (therm \ "description").text) }
## C29.Modular Programming Using Objects
"self type" class Database class SimpleFoods trait SimpleRecipes { this: SimpleFoods => // @ } "Runtime linking" //`.type` is a singleton type object browser { val db:Database = null object browser extends SimpleFoods{ val database: db.type = db // @ } }
## C30.Object Equality
"Pitfall#1: Defining equals with the wrong signature" //An utterly wrong definition of equals class Point(val x: Int, val y: Int){ def equals(other: Point): Boolean = this.x == other.x && this.y == other.y } val p1, p2 = new Point(1, 2) val p2a: Any = p2 p1 equals p2 // true p1 equals p2a // false //A better definition, but still not perfect class Point(val x: Int, val y: Int){ override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } } "Pitfall#2:Changing equals without also changing hashCode" scala.collection.mutable.HashSet(p1) contains p2 // false class Point(val x: Int, val y: Int) { override def hashCode = 41 * (41 + x) + y override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } } "Pitfall#3:Defining equals in terms of mutable fields" class Point(var x: Int, var y: Int) { // Problematic override def hashCode = 41 * (41 + x) + y override def equals(other: Any) = other match { case that: Point => this.x == that.x && this.y == that.y case _ => false } } """ Pitfall#4:Failing to define equals as an equivalence relation relation on non-null objects x.equals(x) ==> true x.equals(y) if and only if y.equals(x) ==>true if x.equals(y),y.equals(z). x.equals(z) ==>true x.equals(y) should consistently return true x.equals(null) should return false """ "-unchecked, by erasure" abstract class Node[+T] { def elem: T override def equals(other: Any) = other match { case that: Node[T] => this.elem == that.elem //case that: Branch[t] => //case that: Branch[_] => //existential type case _ => false } } [b]## C31.Combining Scala and Java[/b]""" value types whenever possible, the compiler translates a Scala Int to a Java int to get better performance. """ "singleton objects" object App { def main(args: Array[String]) { println("Hello, world!") } }public final class App { public static void main(String[] arrstring) { App$.MODULE$.main(arrstring); } } public final class App$ { public static final App$ MODULE$ = new App$();; private App$() { } public void main(String[] args) { System.out.println("Hello, world!"); } }""" Traits as interfaces creates a Java interface of the same name. """ "exception" import java.io._ class Reader(fname: String) { private val in = new BufferedReader(new FileReader(fname)) @throws(classOf[IOException]) def read() = in.read() //public int read() throws java.io.IOException } """ Java: Iterator<?> Scala: Iterator[T] forSome { type T } Scala: Iterator[_] Java: Iterator<? extends Component> Scala: Iterator[T] forSome { type T <: Component } Scala: Iterator[_ <: Component] """ var counter = 0 synchronized { // One thread in here at a time counter = counter + 1 }
## C32.Actors and Concurrency""" Java threading model based on shared data and locks Scala share-nothing, message-passing model """ import scala.actors._ object SillyActor extends Actor { def act() { for (i <- 1 to 5) { Thread.sleep(1000) println("I'm acting!") } } } SillyActor.start() import scala.actors.Actor._ val echoActor = actor { loop { react { case msg:String => println("received string: "+ msg) case msg:Int => println("received int: "+ msg) case msg => println("stop, other: " + msg) exit() } } } echoActor ! 12345 echoActor ! "hello" echoActor ! 1.245 """ Actors should not block Communicate with actors only via messages Prefer immutable messages Make messages self-contained """
## C33.Combinator Parsing"Scala’s combinator parsing framework:)"
## C34.GUI Programmingimport swing._ import event._ object TempConverter extends SimpleSwingApplication { def top = new MainFrame { title = "Celsius/Fahrenheit Converter" object celsius extends TextField { columns = 5 } object fahrenheit extends TextField { columns = 5 } contents = new FlowPanel { contents += celsius contents += new Label(" Celsius=") contents += fahrenheit contents += new Label(" Fahrenheit") border = Swing.EmptyBorder(15, 10, 10, 10) } listenTo(celsius, fahrenheit) reactions += { case EditDone(`fahrenheit`) => val f = fahrenheit.text.toInt val c = (f - 32) * 5 / 9 celsius.text = c.toString case EditDone(`celsius`) => val c = celsius.text.toInt val f = c * 9 / 5 + 32 fahrenheit.text = f.toString } } } TempConverter.main(Array(""))
## C35.The SCells Spreadsheet"spreadsheet can be written in just under 200 lines" :paste trait Arithmetic { this: Evaluator => operations += ( "add" -> { case List(x, y) => x + y }, "sub" -> { case List(x, y) => x - y }, "div" -> { case List(x, y) => x / y }, "mul" -> { case List(x, y) => x * y }, "mod" -> { case List(x, y) => x % y }, "sum" -> { case xs => (0.0 /: xs)(_ + _) }, "prod" -> { case xs => (1.0 /: xs)(_ * _) }) } // a "this" type trait Evaluator { this: Model => type Op = List[Double] => Double val operations = new collection.mutable.HashMap[String, Op] def evaluate(e: Formula): Double = try { e match { case Coord(row, column) => cells(row)(column).value case Number(v) => v case Textual(_) => 0 case Application(function, arguments) => val argVals = arguments flatMap evalList operations(function)(argVals) } } catch { case ex: Exception => Double.NaN } // takes a formula that "may" contain references to other cells and returns the value of those cells private def evalList(e: Formula): List[Double] = e match { case Range(_, _) => references(e) map (_.value) case _ => List(evaluate(e)) } // takes a formula that "may" contain references to other cells and returns references to those other cells def references(e: Formula): List[Cell] = e match { case Coord(row, column) =>List(cells(row)(column)) case Range(Coord(r1, c1), Coord(r2, c2)) => for (row <- (r1 to r2).toList; column <- c1 to c2) yield cells(row)(column) case Application(functrion, arguments) =>arguments flatMap references case _ => List() } } import scala.util.parsing.combinator._ object FormulaParsers extends RegexParsers { def ident: Parser[String] = """[a-zA-Z_]\w*""".r def decimal: Parser[String] = """-?\d+(\.\d*)?""".r def cell: Parser[Coord] = """[A-Za-z]\d+""".r ^^ { s => val column = s.charAt(0).toUpper - 'A' val row = s.substring(1).toInt Coord(row, column) } def range: Parser[Range] = cell ~ ":" ~ cell ^^ { case c1 ~ ":" ~ c2 => Range(c1, c2) } def number: Parser[Number] = decimal ^^ (d => Number(d.toDouble)) def application: Parser[Application] = ident ~ "(" ~ repsep(expr, ",") ~ ")" ^^ { case f ~ "(" ~ ps ~ ")" => Application(f, ps) } def expr: Parser[Formula] = range | cell | number | application def textual: Parser[Textual] = """[^=].*""".r ^^ Textual def formula: Parser[Formula] = number | textual | "=" ~> expr def parse(input: String): Formula = parseAll(formula, input) match { case Success(e, _) => e case f: NoSuccess => Textual("[" + f.msg + "]") } } trait Formula case class Coord(row: Int, column: Int) extends Formula { override def toString = ('A' + column).toChar.toString + row } case class Range(c1: Coord, c2: Coord) extends Formula { override def toString = c1.toString + ":" + c2.toString } case class Number(value: Double) extends Formula { override def toString = value.toString } case class Textual(value: String) extends Formula { override def toString = value } case class Application(function: String, arguments: List[Formula]) extends Formula { override def toString = function + arguments.mkString("(", ",", ")") } object Empty extends Textual("") import swing._ class Model(val height: Int, val width: Int) extends Evaluator with Arithmetic { case class Cell(row: Int, column: Int) extends Publisher { private var f: Formula = Empty def formula: Formula = f def formula_=(f: Formula) { for (c <- references(formula)) deafTo(c) this.f = f for (c <- references(formula)) listenTo(c) value = evaluate(f) } private var v: Double = 0 def value: Double = v def value_=(w: Double) { if (!(v == w || v.isNaN && w.isNaN)) { v = w publish(ValueChanged(this)) } } override def toString = formula match { case Textual(s) => s case _ => value.toString } reactions += { case ValueChanged(_) => value = evaluate(formula) } } case class ValueChanged(cell: Cell) extends event.Event val cells = Array.ofDim[Cell](height, width) for (i <- 0 until height; j <- 0 until width) cells(i)(j) = new Cell(i, j) } import swing._ import event._ class Spreadsheet(val height: Int, val width: Int) extends ScrollPane { val cellModel = new Model(height, width) import cellModel._ val table = new Table(height, width) { rowHeight = 25 autoResizeMode = Table.AutoResizeMode.Off showGrid = true gridColor = new java.awt.Color(150, 150, 150) override def rendererComponent(isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component = if (hasFocus) new TextField(userData(row, column)) else new Label(cells(row)(column).toString) { xAlignment = Alignment.Right } def userData(row: Int, column: Int): String = { val v = this(row, column) if (v == null) "" else v.toString } reactions += { case TableUpdated(table, rows, column) => for (row <- rows) cells(row)(column).formula = FormulaParsers.parse(userData(row, column)) case ValueChanged(cell) => updateCell(cell.row, cell.column) } for (row <- cells; cell <- row) listenTo(cell) } val rowHeader = new ListView((0 until height) map (_.toString)) { fixedCellWidth = 30 fixedCellHeight = table.rowHeight } viewportView = table rowHeaderView = rowHeader } ////////////// object Main extends SimpleSwingApplication { def top = new MainFrame { title = "ScalaSheet" contents = new Spreadsheet(100, 26) } } Main.main(Array("")) // end :paste ctrl-d """ :"add" ,"sub" ,"div" ,"mul" ,"mod" ,"sum" ,"prod" =add(a0,b0) =sum(a0:d0) =a0 """