Update an array
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val greetStrings = new Array[String](3)
greetStrings.update(0, "Hello")
greetStrings(1) = ", " // this is transformed into .update
greetStrings(2) = "George!"
greetStrings.foreach {
a => print(a)
}
println()
}
Operator invoked from the right side:
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val x = List(2, 3)
val y = 1 :: x // methods ending in `:` is invoked on the right side
val z = x.::(1)
y foreach (a => println(a))
z foreach (a => println(a))
}
List operations
import scala.util.Random
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val x = List(2, 3)
val y = 1 :: x // methods ending in `:` is invoked on the right side
val z = x.::(1)
y foreach (a => println(a))
z foreach (a => println(a))
val z1 = x ::: y // concat two lists
z1.foreach(a => println(a))
// count elements that satisfy a certain predicate
z1.count(a => a == 3)
z1.exists(a => a == 3)
// all elements satisfy the predicate?
z1.forall(a => a > 0)
println(z1.head)
println(z1.tail)
println(z1.init)
println(z1.last)
println(z1.isEmpty)
println(z1.length)
z1.map(x => x + 10)
z1.mkString(", ")
val z2 = (0 until 10).map(_ => Random.nextInt(100)).toList
val z3 = z2.map(x => x % 7)
// Sort by value in Z7
println(z2.sortWith(
(s: Int, t: Int) => (s % 7) < (t % 7)
))
// Check the sort is correct
val z4 = z2.zip(z3)
println(z4)
println(z4.sortWith(
(x: (Int, Int), y: (Int, Int)) => x._2 < y._2
))
}
Immutable and mutable collections
import scala.util.Random
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
var jetSet: Set[String] = Set("boeing", "airbus")
jetSet += "lear" // jetSet is replaced by a new object
println(jetSet)
var jetSet1 = collection.mutable.HashSet("boeing", "airbus")
jetSet1 += "lear" // jetSet1 is updated
println(jetSet1)
// mutable map, access key
import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to island.")
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (3 -> "Dig.")
println(treasureMap(2))
}
Print out length of each line in a file
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def lineLength(filename: String): Unit = {
val fn = "/tmp/readme"
// Note the .toList method at the end. Without it `lines` will be an iterator and can only be used once.
val lines = Source.fromFile(fn).getLines().toList
val lineLengths = lines.map(x => x.length)
val maxLengthWidth = lineLengths.max.toString.length
lines.foreach{
line => {
val msg = "%%%dd : %%s".format(maxLengthWidth).format(line.length, line)
println(msg)
}
}
}
def main(args: Array[String]) {
lineLength("/tmp/readme")
}
}
Checksum app
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = ~(sum & 0xff) + 1
}
// Companion object
object ChecksumAccumulator {
private val cache = collection.mutable.HashMap[String, Int]()
def calculate(s: String): Int = {
if(cache.contains(s)) cache(s)
else {
val acc = new ChecksumAccumulator
for(c <- s) acc.add(c.toByte)
val cs = acc.checksum()
cache += (s -> cs)
cs
}
}
}
}
Multi-line string literal
You can make a multiple-line string within triple quotes:
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val x =
"""
|Welcome
| 1. you
| 2. me
| 3. him
| 4. her
""".stripMargin
println(x)
}
Auxiliary constructor
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
class Rational(n: Int, d: Int) {
require(d != 0)
val numer: Int = n
val denom: Int = d
def this(n: Int) = this(n, 1) // auxiliary constructor
override def toString = numer +"/"+ denom
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
def main(args: Array[String]) {
val x = new Rational(5)
println(x)
}
}
Default parameter value
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
class Rational(n: Int, d: Int = 1) {
require(d != 0)
val numer: Int = n
val denom: Int = d
override def toString = numer +"/"+ denom
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
)
}
def main(args: Array[String]) {
val x = new Rational(5)
println(x)
}
}
Use Java class
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val filesHere = (new java.io.File(".")).listFiles()
filesHere.foreach(println(_))
}
Try…Catch
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try {
val f = new FileReader("input.txt")
// Use and close file
} catch {
case ex: FileNotFoundException => // Handle missing file
case ex: IOException => // Handle other I/O error
}
Yielding a default value when something went wrong
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
import java.net.URL
import java.net.MalformedURLException
def urlFor(path: String) =
try {
new URL(path)
} catch {
case e: MalformedURLException =>
new URL("http://www.scala-lang.org")
}
println(urlFor("http://www.google.com"))
println(urlFor("abc#def"))
}
No break
statement. Example application that searches for filenames with .scala
extension
You can use a mutable state to control the flow instead.
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def findScalaFiles(files: Array[String]): Int = {
var i = 0
var foundIt = false
while (i < files.length && !foundIt) {
if (files(i).endsWith(".scala")) foundIt = true
else i = i + 1
}
if(i >= files.length) -1 else i
}
val files = Array("a.py", "b.pl", "c.sh", "d.scala")
findScalaFiles(files)
}
Or use recursion:
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
object FileScalaFiles {
private def findScalaFiles(files: Array[String], idx: Int): Int = {
if(idx >= files.length) {
-1
} else if(files(idx).contains(".scala")) {
idx
} else {
findScalaFiles(files, idx + 1)
}
}
def findScalaFiles(files: Array[String]): Int = {
findScalaFiles(files, 0)
}
}
def main(args: Array[String]) {
import FileScalaFiles._
val files = Array("a.py", "b.pl", "c.sh", "d.scala")
findScalaFiles(files)
}
}
Multiplication table
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def main(args: Array[String]) {
val res = for {
row <- 1 to 10
col <- 1 to 10
} yield {
"%4d".format(row * col) + {
if(col == 10) "\n" else ""
}
}
println(res.mkString(""))
}
}
Private method vs Local function
Private method:
import scala.io.Source
object LongLines {
def processFile(filename: String, width: Int) {
val source = Source.fromFile(filename)
for (line <- source.getLines())
processLine(filename, width, line)
}
private def processLine(filename: String,
width: Int, line: String) {
if (line.length > width)
println(filename +": "+ line.trim)
}
}
Local function:
def processFile(filename: String, width: Int) {
def processLine(filename: String,
width: Int, line: String) {
if (line.length > width)
println(filename +": "+ line)
}
val source = Source.fromFile(filename)
for (line <- source.getLines()) {
processLine(filename, width, line)
}
}
Partially applied function
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def main(args: Array[String]) {
def sum(a: Int, b: Int, c: Int) = {
a + b + c
}
(1 to 10).map(sum(1, _: Int, 3)) // 1/3 applied
(1 to 10).foreach(println _) // 0% applied
}
}
Varargs (Repeated/multiple parameters of the same type)
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def echo(xs: String*): Unit = {
xs.foreach(println _)
}
echo("1", "2", "3")
// unpacking a seq into varargs
echo((1 to 10).map(_.toString): _*)
}
Named parameter
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
def add(a: Int, b: Int) : Int = {
a + b
}
def main(args: Array[String]) {
println(add(b = 1, a = 2))
}
}
Tail recursion
In Scala, tail-call optimization is limited to situations in which a method or nested function calls itself directly as its last operation, without going through a function value or some other intermediary.
Tow example tail recursions that are not optimized:
// mutually recursive
def isEven(x: Int): Boolean =
if (x == 0) true else isOdd(x - 1)
def isOdd(x: Int): Boolean =
if (x == 0) false else isEven(x - 1)
// tail recursion through an intermediary
val funValue = nestedFun _
def nestedFun(x: Int) {
if (x != 0) { println(x); funValue(x - 1) }
}
Higher order functions
import java.io.File
import scala.io.Source
/**
* Created by IDEA on 07/09/2015.
*/
object CrashCourse {
val filesHere = (new File("/Users/kaiyin/personal_config_bin_files/workspace/spark-1.4.1/mllib/src/main/scala/org/apache/spark/ml")).listFiles()
def filesMatching(matcher: String => Boolean) = {
filesHere.map(_.getName).filter(matcher)
}
def filesMatchEnd(query: String) = {
filesMatching(_.endsWith(query))
}
def filesMatchContain(query: String) = {
filesMatching(_.contains(query))
}
def filesMatchRegex(query: String) = {
filesMatching(_.matches(query))
}
def main(args: Array[String]): Unit = {
filesMatchEnd(".scala").foreach(println _)
filesMatchContain("tion").foreach(println _)
filesMatchRegex("r.*n").foreach(println _)
}
}
User defined (new) control structures
Chained function call:
object NewControl {
def twice[A](op: A => A, x: A): A = {
op(op(x))
}
twice[Int](_ * 2, 1)
}
Management of resources:
import java.io.{File, PrintWriter}
import java.util.Date
/**
* Created by kaiyin on 9/8/15.
*/
class ScalaProg {
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
val writer = new PrintWriter(file)
try {
op(writer)
} finally {
writer.close()
}
}
val file = new File("/tmp/readme")
withPrintWriter(file) {
writer => writer.println(new Date())
}
}
Call by name and call by value:
import java.io.{File, PrintWriter}
import java.util.Date
/**
* Created by kaiyin on 9/8/15.
*/
class ScalaProg {
def testFun(): Int = {
println("testFun called.")
5
}
def echoByValue(x: Int): Unit = {
(1 to 3).foreach(_ => println("Echo by val: %d".format(x)))
}
def echoByName(x: => Int): Unit = {
(1 to 3).foreach(_ => println("Echo by name: %d".format(x)))
}
def main (args: Array[String]): Unit = {
echoByName(testFun)
echoByValue(testFun)
}
}
You will see the following in the console:
testFun called.
Echo by name: 5
testFun called.
Echo by name: 5
testFun called.
Echo by name: 5
Echo by val: 5
Echo by val: 5
Echo by val: 5
Another example:
import java.io.{File, PrintWriter}
import java.util.Date
/**
* Created by kaiyin on 9/8/15.
*/
class ScalaProg {
def test(x: => Int): Unit = {
(1 to 3).foreach(_ => println(x))
}
test({println("I am called"); 5})
}
This is useful for testing snippets of code without the syntax overhead of using a lambda:
import java.io.{File, PrintWriter}
import java.util.Date
/**
* Created by kaiyin on 9/8/15.
*/
class ScalaProg {
val assertionEnabled = true
def myAssert(predicate: => Boolean) = {
if(assertionEnabled && !predicate) {
throw new AssertionError()
}
}
myAssert (5 > 3)
myAssert (5 > 8)
}
Field and method with the same name not allowed
class WontCompile {
private var f = 0 // Won’t compile, because a field
def f = 1
// and method have the same name
}
Final methods and classes
Overriding/Sub-classing is forbidden in these cases.
class ArrayElement extends Element {
final override def demo() {
println("ArrayElement's implementation invoked")
}
}
final class ArrayElement extends Element {
override def demo() {
println("ArrayElement's implementation invoked")
}
}
2D layout engine, drawing a spiral in the console
import java.io.{File, PrintWriter}
import javax.swing.JSpinner.NumberEditor
import scala.io.Source
object SpiralObj1 {
type TextLayout = Array[String]
def textLayout(s: String): TextLayout = Array(s)
// fill by width and height
def textLayout(s: String, w: Int, h: Int) = Array.fill(h)(s * w)
def padString(s: String, n: Int): String = {
val n1 = s.length
val diff = n - n1
if (diff <= 0) s
else {
val halfDiff = diff / 2
List.fill(halfDiff)(" ").mkString("") + s + List.fill(n - halfDiff)(" ").mkString("")
}
}
def width(t: TextLayout): Int = if(t.isEmpty) 0 else t(0).length
def height(t: TextLayout): Int = t.length
def above(t1: TextLayout, t2: TextLayout): TextLayout = {
val t3 = widen(t1, width(t2))
val t4 = widen(t2, width(t1))
val res = t3 ++ t4
res
}
def beside(t1: TextLayout, t2: TextLayout): TextLayout = {
val t3 = heighten(t1, height(t2))
val t4 = heighten(t2, height(t1))
for ((s1, s2) <- t3.zip(t4)) yield s1 + s2
}
def widen(t: TextLayout, n: Int): TextLayout = {
val n1 = width(t)
if (n <= n1) t
else {
val halfDiff = (n - n1) / 2
val h = height(t)
val left = textLayout(" ", halfDiff, h)
val right = textLayout(
" ",
n - halfDiff - width(t),
h
)
beside(beside(left, t), right)
}
}
def heighten(t: TextLayout, n: Int): TextLayout = {
val h = height(t)
if (n <= h) t
else {
val w = width(t)
val halfDiff = (n - h) / 2
val top = textLayout(" ", w, halfDiff)
val bottom = textLayout(" ", w, n - halfDiff - h)
above(
above(top, t),
bottom
)
}
}
def stringify(t: TextLayout): String = {
t.mkString("\n")
}
val corner = textLayout("+")
val space = textLayout(" ")
def spiral(nEdges: Int, direction: Int): TextLayout = {
if(nEdges == 0) corner
else {
val sp = spiral(nEdges - 1, (direction + 3) % 4)
val verticalBar = textLayout("|", 1, height(sp))
val horizontalBar = textLayout("-", width(sp), 1)
if(direction == 0)
above(
beside(corner, horizontalBar),
beside(sp, space)
)
else if(direction == 1)
beside(
above(sp, space),
above(corner, verticalBar)
)
else if(direction == 2)
above(
beside(space, sp),
beside(horizontalBar, corner)
)
else
beside(
above(verticalBar, corner),
above(space, sp)
)
}
}
def spiral(nEdge: Int): TextLayout = {
spiral(nEdge, nEdge % 4)
}
}
object Main {
def main(args: Array[String]) {
import SpiralObj1._
(0 to 10).foreach(
x => {
println()
println(stringify(spiral(x)))
}
)
}
}
Value and reference equality
val x = new String("what")
val y = x
y == x // true, value equality
y eq x // true, reference equality
val z = new String("what")
z == x // true
z eq x // false
Traits as stackable modifications
import java.io.{File, PrintWriter}
import javax.swing.JSpinner.NumberEditor
import scala.collection.mutable.ArrayBuffer
import scala.io.Source
object Stackable {
abstract class IntQueue {
def get(): Int
def put(x: Int)
}
class BasicIntQueue extends IntQueue {
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) = {buf += x}
}
trait Doubling extends BasicIntQueue {
override def put(x: Int) = {super.put(2 * x)}
}
trait Filtering extends BasicIntQueue {
def put(x: Int, f: Int => Boolean) = {
if(f(x)) super.put(x)
}
}
trait Incrementing extends BasicIntQueue {
override def put(x: Int) = {
super.put(x + 1)
}
}
}
object Main {
import Stackable._
val queue = new BasicIntQueue
queue.put(10)
queue.put(20)
queue.get()
val q1 = new BasicIntQueue with Doubling
q1.put(10)
q1.put(20)
q1.get()
// Although both Doubling and Filtering extend BasicIntQueue,
// when they are stacked, Filtering actually is subclass of Doubling in effect.
val q2 = new BasicIntQueue with Doubling with Filtering
q2.put(23, _ % 3 == 0) // rejected
q2.put(22, _ % 2 == 0) // accepted
q2.get() // 44
val q3 = new BasicIntQueue with Incrementing with Doubling with Filtering
q3.put(23, _ % 3 == 0) // rejected
q3.put(22, _ % 2 == 0) // accepted
q3.get() // 45
}
Rename imports
import collection.mutable.{Map => MMap}
def main(args: Array[String]) {
val mutableMap = MMap.empty[Int, Int]
val x: Unit = mutableMap.update(3, 5)
val immutableMap = Map.empty[Int, Int]
val y: Map[Int, Int] = immutableMap.updated(3, 5)
val z = y.updated(5, 7)
println(y)
println(z)
}
Hide members of a package
// imports everything in Fruits, except Pear
import Fruits.{Pear => _, _}
Private methods
class Outer {
class Inner {
private def f() {
println("f")
}
class InnerMost {
f() // OK }
}
(new Inner).f() // error: f is not accessible
}
}
Protected methods
package p {
class Super {
protected def f() { println("f") }
}
class Sub extends Super {
f()
}
class Other {
(new Super).f() // error: f is not accessible
}
}
Scope of modifier (access modifiers with qualifiers)
package society {
package professional {
class Executive {
// accessible within professional
private[professional] var workDetails = null
// accessible within society
private[society] var friends = null
// accessible within the whole project
private[this] var secrets = null
def help(another : Executive) {
println(another.workDetails)
println(another.secrets) //ERROR
}
}
}
}
Package object
package com.alvinalexander.myapp
package object model {
// field
val MAGIC_NUM = 42
// method
def echo(a: Any) { println(a) }
// enumeration
object Margin extends Enumeration {
type Margin = Value
val TOP, BOTTOM, LEFT, RIGHT = Value
}
// type definition
type MutableMap[K, V] = scala.collection.mutable.Map[K, V]
val MutableMap = scala.collection.mutable.Map
}
GUI
import scala.swing._
import scala.swing.event._
// need scala-swing lib
object ReactiveSwingApp extends SimpleSwingApplication {
def top = new MainFrame {
title = "Reactive Swing App"
val button = new Button {
text = "Click me"
}
val label = new Label {
text = "No button clicks registered"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += button
contents += label
border = Swing.EmptyBorder(30, 30, 10, 30)
}
listenTo(button)
var nClicks = 0
reactions += {
case ButtonClicked(b) =>
nClicks += 1
label.text = "Number of button clicks: "+ nClicks
}
}
}
Match a Seq with variable length
expr match {
case List(0, _*) => println("found it")
case _ =>
}
Match with typed patterns
def generalSize(x: Any) = x match {
case s: String => s.length
case m: Map[_, _] => m.size
case _ => -1
}
Match with generic types, problem of type erasure
object Example {
def isIntMap(x: Any) = x match {
case m: Map[Int, Int] => true
case _ => false
}
isIntMap(Map(1 -> 1)) // true
isIntMap(Map(true -> true)) // true
isIntMap(Map("a" -> "b")) // true
}
The type variables cannot possibly be check because of type erasure. The only exception is array, they are handled specially both in java and scala:
object Example {
def isIntArray(x: Any) = x match {
case m: Array[Int] => true
case _ => false
}
isIntArray(Array(1, 2, 3)) // true
isIntArray(Array(true, false)) // false
}
Variable binding in pattern matching
Here is an example implementation of the tail
funciton:
object Example {
def myTail[A](x: List[A]): List[A] = {
x match {
case Nil => Nil
case List(_) => Nil
case _ :: (t@List(_, _*)) => t
}
}
myTail(List(1, 2, 3))
}
Exhaustive pattern matching
In general, this is impossible in Scala, because new case classes can be defined at any time and in arbitrary compilation units. For instance, nothing would prevent you from adding a fifth case class to the Expr class hierarchy in a different compilation unit from the one where the other four cases are defined.
The alternative is to make the superclass of your case classes sealed. A sealed class cannot have any new subclasses added except the ones in the same file. This is very useful for pattern matching, because it means you only need to worry about the subclasses you already know about.
object Example {
sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,
left: Expr, right: Expr) extends Expr
}
object Example1 {
import Example._
case class Var1(name: String) extends Expr
// this would fail, because Var1 is not matched
// the compiler issues a warning
def describe(e: Expr): String = e match {
case Number(_) => "a number"
case Var(_) => "a variable"
case UnOp(_, _) => "unary op"
case BinOp(_, _, _) => "binary op"
}
}
Option instead of null
object Example {
val m = Map("a" -> 1, "b" -> 2)
m get "a"
m get "aa" // None
(m get "aa") match {
case None => "Nothing is with aa"
case Some(i) => i.toString
}
}
Case sequences as partial functions
object Example {
val second: PartialFunction[List[Int], Int] = {
case x :: y :: _ => y
}
}
// The above example is translated into the following by the compiler:
new PartialFunction[List[Int], Int] {
def apply(xs: List[Int]) = xs match {
case x :: y :: _ => y
}
def isDefinedAt(xs: List[Int]) = xs match {
case x :: y :: _ => true
case _ => false
}
}
Pattern matching in for
expressions:
val results = List(Some("apple"), None, Some("orange"))
for (Some(fruit) <- results) println(fruit)
Expression formatter example
This is not a very good example. The operator precedence is entirely unnecessary here. I am putting it here just for completeness’ sake.
object SpiralObj {
object ElementObj {
object Element {
private class ArrayElement(
val contents: Array[String]
) extends Element
private class LineElement(s: String) extends Element {
val contents = Array(s)
}
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 elem(contents: Array[String]): Element = {
new ArrayElement(contents)
}
def elem(s: String): Element = {
new ArrayElement(Array(s))
}
def elem(chr: Char, width: Int, height: Int): Element = {
new UniformElement(chr, width, height)
}
}
abstract class Element {
import Element.elem
// contents to be implemented
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
elem(this1.contents ++ that1.contents)
}
def beside(that: Element): Element = {
val this1 = this heighten that.height
val that1 = that heighten this.height
elem(
for ((line1, line2) <- this1.contents zip that1.contents)
yield line1 + line2
)
}
def heighten(h: Int): Element = {
if (h <= height) this
else {
val top = elem(' ', width, (h - height) / 2)
val bottom = elem(' ', width, h - height - top.height)
top above this above bottom
}
}
def widen(w: Int): Element = {
if (w <= width) this
else {
val left = elem(' ', (w - width) / 2, height)
val right = elem(' ', w - width - left.width, height)
left beside this beside right
}
}
override def toString = contents mkString "\n"
}
}
sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,
left: Expr, right: Expr) extends Expr
class ExprFormatter {
private val opGroups = Array(
Set("|", "||"),
Set("&", "&&"),
Set("^"),
Set("==", "!="),
Set("<", "<=", ">", ">="),
Set("+", "-"),
Set("*", "%")
)
private val precedence = {
val assocs = for {
i <- 0 until opGroups.length
op <- opGroups(i)
} yield op -> i
assocs.toMap
}
private val unaryPrecedence = opGroups.length
private val fractionPrecedence = -1
import ElementObj.Element
import ElementObj.Element.elem
def format(e: Expr): Element = format(e, 0)
private def format(e: Expr, enclPrec: Int): Element = e match {
case Var(name) => elem(name)
case Number(num) => {
def stripDot(s: String) = {
if (s.endsWith(".0")) s.substring(0, s.length - 2)
else s
}
elem(stripDot(num.toString))
}
case UnOp(op, arg) => elem(op) beside format(arg, unaryPrecedence)
case BinOp("/", left, right) => {
val top = format(left, fractionPrecedence)
val bot = format(right, fractionPrecedence)
val line = elem('-', top.width max bot.width, 1)
val frac = top above line above bot
if (enclPrec != fractionPrecedence) frac
else elem(" ") beside frac beside elem(" ")
}
case BinOp(op, left, right) => {
val opPrec = precedence(op)
val l = format(left, opPrec)
val r = format(right, opPrec + 1)
val oper = l beside elem(" " + op + " ") beside r
if (enclPrec <= opPrec) oper
else elem("(") beside oper beside elem(")")
}
}
}
val f = new ExprFormatter
val e1 = BinOp(
"*",
BinOp("/", Number(1), Number(2)),
BinOp("+", Var("x"), Number(3))
)
def show(e: Expr) = {
println()
println(f.format(e) + "\n\n")
}
show(e1)
}
Insert sort
object Example {
def insert(x: Int, xs: List[Int]): List[Int] = {
if(xs.isEmpty || x <= xs.head) x :: xs
else xs.head :: insert(x, xs.tail)
}
def isort(xs: List[Int]): List[Int] = {
if(xs.isEmpty) Nil
else insert(xs.head, isort(xs.tail))
}
val x = List(5, 9, 1, 2, 3)
println(isort(x))
}
Another version using pattern matching:
object Example {
def insert(x: Int, xs: List[Int]): List[Int] = xs match {
case List() => x :: xs
case y :: ys => {
if(x <= y) x :: xs
else y :: insert(x, ys)
}
}
def isort(xs: List[Int]): List[Int] = xs match {
case List() => Nil
case x :: xs1 => insert(x, isort(xs1))
}
val x = List(5, 9, 1, 2, 3)
println(isort(x))
}
List/Array operations
object Example {
val x = List(5, 9, 2, 3, 1)
x.init
x.last
x.head
x.tail
val y = List(7, 8, 11)
(23 :: x) ::: y // prepend and concat
List().isEmpty // true
x.reverse
x.take(3)
x.drop(2)
x.splitAt(3)
x(4) == x.apply(4) // indexing
x.indices // Range 0 to 4
List(x, y).flatten
List("apple", "orange").map(_.toCharArray).flatten
x zip x.indices
x.zipWithIndex // same as above
x.zipWithIndex.unzip
(x zip y).unzip // same as List(x, y)
Array(1, 2, 3).sameElements(Array(1, 2, 3))
x.mkString(start = "[", sep = ", ", end = "]")
x.mkString(sep = ", ")
// concat a string array into one string and add it to a string builder
val abcde: Array[String] = "a,b,c,d,e".split(",")
val buf = new StringBuilder
abcde addString(buf, "(", ",", ")")
abcde addString(buf, "[", ",", "]")
println(buf.toString())
// convert an array to a list and then back
abcde.toList.toArray.sameElements(abcde)
// copy content of an array into another array
val newArray = new Array[String](abcde.length)
abcde.copyToArray(newArray, 0)
newArray.sameElements(abcde)
println(newArray.toList)
}
Merge sort
I modified the original implementation to allow specification of the comparator as either the 1st or the 2nd parameter.
object Example {
def merge[T](xs: List[T], ys:List[T], less: (T, T) => Boolean): List[T] = (xs, ys) match {
case (Nil, _) => ys
case (_, Nil) => xs
case (x :: xs1, y :: ys1) => {
if(less(x, y)) x :: merge(xs1, ys, less)
else y :: merge(xs, ys1, less)
}
}
def msort[T](xs: List[T])(less: (T, T) => Boolean): List[T] = {
val n: Int = xs.length / 2
if(n == 0) return xs
val (xs1, xs2) = xs.splitAt(n)
merge(msort(xs1)(less), msort(xs2)(less), less)
}
def msort[T](less: (T, T) => Boolean)(xs: List[T]): List[T] = {
val n: Int = xs.length / 2
if(n == 0) return xs
val (xs1, xs2) = xs.splitAt(n)
merge(msort(less)(xs1), msort(less)(xs2), less)
}
}
object Test {
val x = List(12, 75, 1, 3, 19)
Example.msort(x)(_ < _)
// Impossible to infer T in the following case:
// Example.msort(_ < _)(x) // this will not compile
Example.msort((a: Int, b: Int) => a < b)(x)
val msortInt = Example.msort((a: Int, b: Int) => a < b) _
msortInt(x)
}
List methods
object Example {
val words = "the quick brown fox".split(" ")
words map (_.toList)
words flatMap(_.toList)
List.range(1, 5) flatMap(
i => List.range(1, i) map (j => (i, j))
)
// The same as above, but clearer
for (
i <- List.range(1, 5);
j <- List.range(1, i)
) yield (i, j)
// foreach takes a procedure (returning Unit) as the second operand
var sum = 0
List(1, 2, 3, 4) foreach(sum += _)
println(sum) // 10
val int4 = List(1, 2, 3, 4)
int4 filter (_ < 4) // List(1, 2, 3)
int4 partition(_ % 2 == 0)
int4 find (_ % 2 == 0) // Some(2), the first even number
int4 find (_ < 0) // None
val x = List(1, 2, 3, -1, -2, -3, 4, 5, 6)
x.takeWhile(_ > 0)
x.dropWhile(_ > 0)
x span (_ > 0) // combines takeWhile and dropWhile
x.forall(_ > 0) // all elements of x are > 0?
x.exists(_ > 0) // some elements of x are > 0?
x.exists(_ < 0) // some elements of x are < 0?
// foldLeft
(0 /: x) (_ + _) // 15, sum of x
// foldRight, for a list this is slower than foldLeft
(x :\ 0) (_ + _) // 15
// Implement flatten with fold
def flattenLeft[T](xss: List[List[T]]) =
(List[T]() /: xss) (_ ::: _)
def flattenRight[T](xss: List[List[T]]) =
(xss :\ List[T]()) (_ ::: _)
val y = List(List(1, 2), List(3, 4))
flattenLeft(y)
flattenRight(y)
// Implement reverse with fold
def reverse[T](xs: List[T]) = {
(List[T]() /: xs) ((ys, y) => y :: ys)
}
reverse(int4)
// sort with custom comparator
List(1, -3, 4, 2, 6) sortWith(_ > _)
List(1, -3, 4, 2, 6) sortWith(_ < _)
// range
List.range(1, 5) // 1, 2, 3, 4
List.range(1, 5, 2) // 1, 3
List.range(5, 1, -2) // 5, 3
// fill a 3x3 2D List with 5
List.fill(3, 3)(5)
// tabulate a 3x3 2D List
List.tabulate(3, 3)((i, j) => i * j)
// Implement fill with tabulate
def fill[T](n1: Int, n2: Int)(x: T): List[List[T]] = {
List.tabulate(n1, n2)((_, _) => x)
}
fill(3, 3)(5)
// concat
List.concat(List(1, 2, 3), List(4, 5, 6))
// zip
(List(1, 2), List(3, 4), List(5, 6)).zipped.map(_ * _ * _) // 15, 48
(List("abc", "de"), List(3, 2)).zipped.map(_.length == _)
(List("abc", "de"), List(3, 2)).zipped.forall(_.length == _)
}
Collection drills
object Example {
// ListBuffer for O(1) prepend and append
import scala.collection.mutable.ListBuffer
val buf = new ListBuffer[Int]
buf += 1
buf += 2 // append
3 +=: buf // prepend
buf.toList
import scala.collection.mutable.ArrayBuffer
val abuf = new ArrayBuffer[Int]()
abuf += 12
abuf += 15
// prepend is probably slow, since ArrayBuffer is just a wrapper around array
17 +=: abuf
abuf.toList
// StringOps
def hasUpperCase(s: String) = s.exists(_.isUpper)
hasUpperCase("Abuse")
hasUpperCase("bizar")
// Set and Map are immutable by default:
object Predef {
type Map[A, +B] = collection.immutable.Map[A, B]
type Set[A] = collection.immutable.Set[A]
val Map = collection.immutable.Map
val Set = collection.immutable.Set
// ...
}
// Using sets
import collection.mutable
val words = "See Spot run. run, spot. run!".split("[.,!? ]+")
val wordSet = mutable.Set.empty[String]
// get all the unique words
for (word <- words) {
wordSet += word.toLowerCase()
}
print(wordSet)
val nums = Set(1, 2, 3)
nums + 5 // new set with 1, 2, 3, 5
nums - 3 // new set with 1, 2
nums ++ List(5, 6) // new set with 1, 2, 3, 5, 6
nums -- List(1, 2) // new set with 3
nums & Set(1, 3, 7) // new set with 1, 3
nums.size
nums.contains(3)
// using mutable set
val x = mutable.Set.empty[String]
x += "the" // x with "the"
x -= "the" // x is empty now
x ++= List("do", "re", "mi") // x with do, re, mi
x --= List("do", "re") // x with mi
x.clear() // x is empty again
x
// counts word frequency with a map
val s = """AMSTERDAM -
|Volkert van der G., de moordenaar van Pim Fortuyn, heeft doelbewust een voorwaarde van zijn voorwaardelijke invrijheidsstelling geschonden. Dat blijkt vanavond in Brandpunt-Reporter, dat opnames heeft gemaakt met een verborgen camera.
|Van der G. spande een kort geding aan tegen KRO-NCRV waarin hij eiste dat de uitzending werd verboden. De rechtbank van Amsterdam heeft die eis aan het begin van de avond afgewezen, zo meldt de omroep in een persbericht.
|Van der G. riskeert nu dat hij alsnog de resterende zes jaar gevangenisstraf geheel of gedeeltelijk moet uitzitten, aldus KRO-NCRV.
|In een heimelijk opgenomen gesprek zegt de moordenaar dat hij foto’s die vorig jaar in De Telegraaf verschenen zelf in scène heeft gezet. Hij vertelt dat zijn toenmalige advocaat, Stijn Franken, contact legde met de fotograaf: ,,Die maakte bij wijze van spreken 200 foto’s en dan konden wij zeggen: die wel en die niet”, aldus Van der G. Stijn Franken wil volgens KRO-NCRV hier geen commentaar op geven.
|Mediaverbod
|De omroep legt uit: ,,Van der G. heeft een mediaverbod waardoor hij niet actief contact mag leggen met de pers. Het mediaverbod maakt onderdeel uit van de bijzondere voorwaarden die hem door de rechter werden opgelegd, die mede tot doel hebben de kans op recidive te verminderen en daarmee de samenleving te beschermen. Uitzonderingen kunnen alleen worden gemaakt na voorafgaande toestemming van zowel Reclassering als Openbaar Ministerie. De Reclassering laat in een reactie aan Brandpunt Reporter weten geen toestemming te hebben verleend en dit te beschouwen als een overtreding van zijn mediaverbod. Het Openbaar Ministerie geeft geen commentaar."
|Uit de uitzending blijkt volgens KRO-NCRV verder dat Volkert van der G. zijn resocialisatie tegenwerkt. Hij vertelt ,,misbruik te kunnen maken” van de soepele opstelling van uitkeringsinstantie UWV in zijn woonplaats Apeldoorn, die geen haast maakt met een re-integratietraject. Hij zegt niet te willen werken ,,omdat je dan niet meer gratis kan procederen”. Tegen de Reclassering zegt hij een klacht te hebben ingediend en hij weigerde een handtekening te zetten onder afspraken over zijn re-integratie. ,,Ik zie die hele reclassering als treiterij."
|Reactie Telegraaf-hoofdredacteur Paul Jansen:
|“Ik heb geen kennis van de exacte omstandigheden waaronder de foto in 2014 aan ons door een freelancer is aangeboden. Het ging De Telegraaf destijds om de nieuwswaarde van de foto, en die is evident”.
|Brandpunt-Reporter is zondagavond te zien om 22.35 uur op NPO2."""
println(s)
def countWords(text: String): Map[String, Int] = {
val mMap = new mutable.HashMap[String, Int]
for {
w <- text.split("[., ?!\"']+")
if (!w.isEmpty)
} {
val word = w.toLowerCase()
if(mMap.contains(word)) mMap.update(word, mMap(word) + 1)
else mMap.update(word, 1)
}
mMap.toMap
}
// count words and sort in descending order, have a look at the most frequently used Dutch words in the piece of news
countWords(s).toSeq.sortWith((x, y) => x._2 > y._2)
// another implementation
def countWords1(text: String): Map[String, Int] = {
val mMap = new mutable.HashMap[String, Int]
for {
w <- text.split("[., ?!\"']+")
if (!w.isEmpty)
} {
val word = w.toLowerCase()
val oldCount = mMap.getOrElse(word, 0)
mMap += (word -> (oldCount + 1))
}
mMap.toMap
}
countWords1(s).toSeq.sortWith((x, y) => x._2 > y._2)
// common operations for maps
// immutable
val nums = Map("i" -> 1, "ii" -> 2)
nums + ("vi" -> 6) // add a key
nums - "ii" // remove a key
nums ++ List("iii" -> 3, "v" -> 5) // add more keys
nums -- List("ii", "i") // remove more keys
nums.size
nums.contains("ii")
nums("ii")
nums.keys // returns an iterable over the keys
nums.keySet // returns a set
nums.values // iterable over the values
nums.isEmpty
// for mutables:
// +=
// ++=
// -=
// --=
// Ordered Map/Set
import collection.immutable.{TreeSet, TreeMap}
val ts = TreeSet(9, 23, 1, 2)
ts
val tm = TreeMap(3 -> "c", 1 -> "a", 2 -> "b")
tm
// convert a map to a sorted map
val m = Map("98" -> List(4, 12, 14), "001" -> List(22, 11))
val t = TreeMap(m.toSeq: _*)
t // sorted by key
// sort an unsorted map
m.toSeq.sortWith((x, y) => x._2(0) < y._2(0))
// add a unsorted map into a sorted map
val m1 = Map("07" -> List(3, 5, 1), "05" -> List(12, 5, 3))
val t1: TreeMap[String, List[Int]] = t ++ m1
t1
// custom ordering with implicit Ordering[String]
implicit val OrderingStringLength: Ordering[String] = Ordering.by((_: String).length)
val t2 = TreeMap(t.toSeq: _*)
// custom ordering provided directly to constructor
val t3 = TreeMap(t.toSeq: _*)(Ordering.by((_: String).length))
// syntactic sugar for immutable objects
// += is not supported for immutable set
// val people = Set("Nancy", "Jane")
// people += "bob" // Error!
// but `x += y` is interpreted as `x = x + y` for immutables if x is a var
var people = Set("Nancy", "Jane")
people += "Bob" // Error!
people
people ++= List("Luke", "Wendy")
people
// the same syntactic sugar for Double literal
var roughlyPi = 3.0
roughlyPi += 0.1
roughlyPi += 0.04
roughlyPi
// initialization of collections
List(1, 2, 3)
Array(1.1, 2.2, 3.3)
Set[Any](32, "a")
val t4 = TreeSet[Int]() ++ List(1, 2, 3, 4)
// converting to array or list
t4.toList
t4.toArray
}
More about custom ordering (unfinished)
object Example {
object ComplexOrdering extends Ordering[Double] {
def compare(a: Double, b: Double) = {
if(math.sin(a) < math.sin(b)) -1 else 1
}
}
}
object Test {
import Example._
{
import collection.immutable.TreeMap
val t1 = TreeMap(1.0 -> "c", 1.1 -> "b", 1.2 -> "a", 7.08 -> "d", 7.07 -> "e")(ComplexOrdering)
}
}
Conversion between mutable and unmutable
object Example {
import collection.mutable
import collection.immutable.TreeSet
val x = TreeSet(1, 5, 8, 12)
val y = mutable.Set.empty ++= x // mutable.Set[Int]
val y1 = mutable.Set.empty[Int] ++= x // mutable.Set[Int]
val z = TreeSet.empty ++ y // Set[Any]
val z1 = TreeSet.empty[Int] ++ y // TreeSet[Int]
val m = mutable.Map("i" -> 1, "ii" -> 2) // mutable.Map[String, Int]
val im = Map.empty ++ m // Map[String, Int]
}
Tuples
object Example {
val words = Array("a", "ab", "abc")
def longestWord(words: Array[String]) = {
var word = words(0)
var idx = 0
words.zipWithIndex.foreach(
x => {
if(x._1.length > word.length) {
word = x._1
idx = x._2
}
}
)
(word, idx)
}
val (word, idx) = longestWord(words)
}
Stateful object using a var
object Example {
class BankAccount {
private var bal: Int = 0
def balance = bal
def deposit(amount: Int) = {
require(amount > 0)
bal += amount
}
def withdraw(amount: Int): Boolean = {
if(0 <= amount && amount <= bal) {
bal -= amount
true
} else false
}
}
val ba = new BankAccount()
ba.deposit(15)
ba.withdraw(7)
ba.withdraw(7)
ba.withdraw(7)
}
Purely functional while using var
Using a var doesn’t necessarily mean the function is not pure, here is an example:
object Example {
def time[E](block: => E): E = {
val t0 = System.nanoTime()
val result = block
println("Elapsed time: %d ns".format(System.nanoTime() - t0))
result
}
class Keyed {
def computeKey: Int = {
Thread.sleep(5000)
1530
}
}
class MemoKeyed extends Keyed {
private var keyCache: Option[Int] = None
override def computeKey: Int = {
// if the result is cached, use the cache, otherwise compute it
keyCache.getOrElse({
keyCache = Some(super.computeKey)
keyCache.get
})
}
}
val m = new MemoKeyed()
time(m.computeKey) // takes 5s
time(m.computeKey) // takes very little time
}
Var as properties
object Example {
class Time(var h: Int = 12, var m: Int = 0)
val t = new Time()
t.h
t.m
t.h = 17 // Time.h_= is implemented for you
t.m = 25 // Time.m_= is implemented for you
t.h
t.m
t.m = 67 // Doesn't make sense for minute, not checked
}
Custom setters for validation
object Example {
class Time {
private [this] var h = 12
private [this] var m = 0
def hour: Int = h
def hour_=(x: Int): Unit = {
require(0 <= x && x < 24)
h = x
}
def minute: Int = m
def minute_=(x: Int): Unit = {
require(0 <= x && x < 60)
m = x
}
}
val t = new Time()
t.hour = 17
t.minute = 25
t.hour = 25 // error checked
}
Getter and setter without a corresponding var
object Example {
class Thermometer {
var c: Double = _
def f = c * 9 / 5 + 32
def f_=(x: Double): Unit = {
c = (x - 32) * 5 / 9
}
override def toString = "%.2fF/%.2fC".format(f, c)
}
val t = new Thermometer()
t.c = 32.0
println(t.f) // 89.6
t.f = 89.6
println(t.c) // 32.0
}
Assign one value to multiple variables
object Example {
val a, b, c = List(1, 2, 3)
}
Digital circuit simulation
Negator, half adder and full adder.
object Example {
abstract class Simulation {
type Action = () => Unit
case class WorkItem(time: Int, action: Action)
private var curtime = 0
def currentTime: Int = curtime
private var agenda: List[WorkItem] = List()
private def insert(ag: List[WorkItem], item: WorkItem): List[WorkItem] = {
if (ag.isEmpty || item.time < ag.head.time) item :: ag
else ag.head :: insert(ag.tail, item)
}
def afterDelay(delay: Int)(block: => Unit): Unit = {
val item = WorkItem(currentTime + delay, () => block)
agenda = insert(agenda, item)
}
private def next(): Unit = {
agenda match {
case item :: rest =>
agenda = rest
curtime = item.time
item.action()
}
}
def run(): Unit = {
afterDelay(0) {
println("*** simulation started, time = " + currentTime + " ***")
}
while (!agenda.isEmpty) next()
}
}
abstract class BasicCircuitSimulation extends Simulation {
def InverterDelay: Int
def AndGateDelay: Int
def OrGateDelay: Int
class Wire {
private var sigVal = false
private var actions: List[Action] = List()
def getSignal = sigVal
def setSignal(s: Boolean) = if (s != sigVal) {
sigVal = s
actions foreach (_())
}
def addAction(a: Action) = {
actions = a :: actions
a()
}
}
def inverter(input: Wire, output: Wire) = {
def invertAction(): Unit = {
val inputSig = input.getSignal
afterDelay(InverterDelay) {
output setSignal (!inputSig)
}
}
input addAction (invertAction)
}
def andGate(a1: Wire, a2: Wire, output: Wire): Unit = {
def andAction() = {
val a1Sig = a1.getSignal
val a2Sig = a2.getSignal
afterDelay(AndGateDelay) {
output setSignal (a1Sig & a2Sig)
}
}
a1.addAction(andAction)
a2.addAction(andAction)
}
def orGate(o1: Wire, o2: Wire, output: Wire): Unit = {
def orAction(): Unit = {
val o1Sig = o1.getSignal
val o2Sig = o2.getSignal
afterDelay(OrGateDelay) {
output.setSignal(o1Sig | o2Sig)
}
}
o1.addAction(orAction)
o2.addAction(orAction)
}
def probe(name: String, wire: Wire): Unit = {
def probeAction(): Unit = {
println(name + " " + currentTime + " new-value = " + wire.getSignal)
}
wire.addAction(probeAction)
}
}
abstract class CircuitSimulation extends BasicCircuitSimulation {
def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire): Unit = {
val d, e = new Wire
orGate(a, b, d)
andGate(a, b, c)
inverter(c, e)
andGate(d, e, s)
}
def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire): Unit = {
val s, c1, c2 = new Wire
halfAdder(a, cin, s, c1)
halfAdder(b, s, sum, c2)
orGate(c1, c2, cout)
}
}
object MySimulation extends CircuitSimulation {
def InverterDelay = 1
def AndGateDelay = 3
def OrGateDelay = 5
}
import MySimulation._
val input1, input2, sum, carry = new Wire
probe("sum", sum)
probe("carry", carry)
halfAdder(input1, input2, sum, carry)
input1.setSignal(true)
run()
}
Implement a Queue
One slow implementation and one fast:
object Example {
class SlowAppendQueue[T](elems: List[T]) {
// head and tail is O(1)
def head = elems.head
def tail = new SlowAppendQueue(elems.tail)
// enqueue is O(n)
def enqueue(x: T) = new SlowAppendQueue(elems ::: List(x))
}
class SlowHeadQueue[T](smele: List[T]) {
// head and tail is O(n)
def head = smele.last
def tail = new SlowHeadQueue(smele.init)
// enqueue is O(1)
def enqueue(x: T) = new SlowHeadQueue(x :: smele)
}
// A faster implementation using 2 lists
class Queue[T](private val leading: List[T],
private val trailing: List[T]
) {
// when the leading list is empty,
// the trailing becomes the leading
private def mirror =
if (leading.isEmpty) {
new Queue(trailing.reverse, Nil)
} else {
this
}
def head = mirror.leading.head
def tail = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def enqueue(x: T) = new Queue(leading, x :: trailing)
}
}
Hiding information
Hiding the fact that you are using two lists in the implementation is probably more elegant and allows more flexibility for future development.
This can be achieved using (public) overloaded/auxiliary constructors.
object Example {
// the constructor is now private
class Queue[T] private (private val leading: List[T],
private val trailing: List[T]
) {
// hiding the fact that you are using 2 lists in this implementation
def this() = this(Nil, Nil)
def this(elems: T*) = this(elems.toList, Nil)
def this(elems: List[T]) = this(elems, Nil)
// when the leading list is empty,
// the trailing becomes the leading
private def mirror =
if (leading.isEmpty) {
new Queue(trailing.reverse, Nil)
} else {
this
}
def head = mirror.leading.head
def tail = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def enqueue(x: T) = new Queue(leading, x :: trailing)
}
}
Alternatively, use a companion object:
object Example {
// the constructor is now private
class Queue[T] private(private val leading: List[T],
private val trailing: List[T]
) {
// when the leading list is empty,
// the trailing becomes the leading
private def mirror: Queue[T] =
if (leading.isEmpty) {
new Queue(trailing.reverse, Nil)
} else {
this
}
def size = leading.size + trailing.size
def head: T = mirror.leading.head
def tail: Queue[T] = {
val q = mirror
new Queue(q.leading.tail, q.trailing)
}
def enqueue(x: T) = new Queue(leading, x :: trailing)
}
object Queue {
def apply[T] = new Queue[T](Nil, Nil)
def apply[T](xs: T*) = new Queue[T](xs.toList, Nil)
def apply[T](xs: List[T]) = new Queue[T](xs, Nil)
}
}
object Test {
import Example._
val q = Queue(1, 2, 3)
q.size
val q1 = q.enqueue(1)
q1.size
q1.head
q1.tail.head
val q2 = Queue[Int]()
val q3 = q2.enqueue(3)
q3.size
q3.head
q3.tail.head // error, tail is empty
}
Or, more radically, use a trait and a private class:
import _root_.Example.Queue.QueueImpl
object Example {
trait Queue[T] {
def head: T
def tail: Queue[T]
def enqueue(x: T): Queue[T]
def size: Int
}
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 mirror = {
if (leading.isEmpty)
new QueueImpl[T](trailing.reverse, Nil)
else
this
}
def head: T = mirror.leading.head
def tail = {
val q = mirror
new QueueImpl[T](q.leading.tail, q.trailing)
}
def enqueue(x: T) = new QueueImpl[T](leading, x :: trailing)
def size = leading.size + trailing.size
}
}
}
object Test {
import Example._
val q: Queue[Int] = Queue(1, 2, 3)
q.size
val q1 = q.enqueue(1)
q1.size
q1.head
q1.tail.head
val q2 = Queue[Int]()
val q3 = q2.enqueue(3)
q3.size
q3.head
q3.tail.head // error, tail is empty
}
Contravariant and covariant
object Example {
class Publication(val title: String)
class Book(title: String) extends Publication(title)
object Library {
val books: Set[Book] = {
Set(
new Book("Programming in Scala"),
new Book("Walden")
)
}
def printBookList(info: Book => AnyRef): Unit = {
for(book <- books) println(info(book))
}
}
}
object Test {
import Example._
{
// See the type of printBookList
// Publication is superclass of Book: contravariant
// String is subclass of AnyRef: covariant
def getTitle(p: Publication): String = p.title
Library.printBookList(getTitle)
}
}
Demonstrate type lower bounds with a Queue implementation:
object Example {
class Queue[+T] private (
private[this] var leading: List[T],
private [this] var trailing: List[T]
) {
private def mirror: Unit = {
if(leading.isEmpty) {
while(!trailing.isEmpty) {
leading = trailing.head :: leading
trailing = trailing.tail
}
}
}
def this() = this(Nil, Nil)
def head: T = {
mirror
leading.head
}
def tail: Queue[T] = {
mirror
new Queue(leading.tail, trailing)
}
def enqueue[U >: T](x: U) = new Queue[U](leading, x :: trailing)
def enqueue[U >: T](xs: U*) = new Queue[U](leading, xs.reverse.toList ::: trailing)
def size = leading.size + trailing.size
}
object Queue {
def apply[T, U <: T](xs: U*): Queue[T] = new Queue(xs.toList, Nil)
def apply[T]: Queue[T] = new Queue[T](Nil, Nil)
}
class Publication
class Book extends Publication
class Novel extends Book
class Journal extends Publication
class Magazine extends Publication
}
object Test {
import Example._
{
val q = new Queue[Int]()
val q1 = q.enqueue[Int](1, 2, 3)
q1.head // 1
val p = Queue.apply[Book, Book](new Book, new Book, new Book) // Queue[Nothing] ?
val p1 = p.enqueue(new Journal, new Magazine) // p1 is Queue[Publication]
}
}
0 comments:
Post a Comment