Scala 快速入门(1)

This article is my notes about the book “Programming in Scala”.

Scalability is influenced by many factors, ranging from syntax details to component abstraction constructs. If we were forced to name just one aspect of Scala that helps scalability, though, we’d pick its combination of object-oriented and functional programming (well, we cheated, that’s really two aspects, but they are intertwined).

Scala is an object-oriented language in pure form: every value is an object and every operation is a method call. For example, when you say 1 + 2 in Scala, you are actually invoking a method named + defined in class Int. You can define methods with operator-like names that clients of your API can then use in operator notation.

Functional programming is guided by two main ideas. The first idea is that functions are first-class values. In a functional language, a function is a value of the same status as, say, an integer or a string. You can pass functions as arguments to other functions, return them as results from functions, or store them in variables. You can also define a function inside another function, just as you can define an integer value inside a function. And you can define functions without giving them a name, sprinkling your code with function literals as easily as you might write integer literals like 42.

The second main idea of functional programming is that the operations of a program should map input values to output values rather than change data in place.Another way of expressing this is that strings are immutable in Java whereas they are mutable in Ruby. So looking at just strings, Java is a functional language, whereas Ruby is not. Immutable data structures are one of the cornerstones of functional programming. The Scala libraries define many more immutable data types on top of those found in the Java APIs. For instance, Scala has immutable lists, tuples, maps, and sets.

Functional Objects

The Scala compiler will compile any code you place in the class body, which isn’t part of a field or a method definition, into the primary constructor. For example, you could print a debug message like this:

  class Rational(n: Int, d: Int) {
    
    
    println("Created "+ n +"/"+ d)
  }
scala> new Rational(1,2)
Created 1/2
res13: Rational = Rational@18263fa
package primer

object FunctionalObjects {
    
    
  def main(args: Array[String]): Unit = {
    
    
//    new Rational(1, 0)  // java.lang.IllegalArgumentException
    val x = new Rational(1, 2)
    val y = new Rational(2, 3)
    println(x + y)  // 7/6
    println(x + x * y) // 5/6 (x + x * y) == x + (x * y)
    println((x + x) * y) // 2/3

    // Implicit conversions
    // Note that for an implicit conversion to work, it needs to be in scope. If you place the implicit method definition inside class Rational, it won't be in scope in the interpreter.
    implicit def intToRational(x: Int) = new Rational(x)
    println(2 * y)  // 4/3
  }


}

class Rational(n: Int, d: Int) {
    
    
  require(d != 0) // Checking preconditions

  private val g = gcd(n, d);
  val numerator = n / g;
  val denominator = d / g;

  def this(n: Int) = this(n, 1) // Auxiliary constructors

  def + (that: Rational): Rational =
    new Rational(
      numerator * that.denominator + denominator * that.numerator,
      denominator * that.denominator
    )
  def + (i: Int): Rational =
    new Rational(numerator + i * denominator, denominator)

  def - (that: Rational): Rational =
    new Rational(
      numerator * that.denominator - denominator * that.numerator,
      denominator * that.denominator
    )
  def - (i: Int): Rational =
    new Rational(numerator - i * denominator, denominator)

  def * (that: Rational): Rational =
    new Rational(numerator * that.numerator, denominator * that.denominator)
  def * (i: Int): Rational =
    new Rational(numerator * i, denominator)

  def / (that: Rational): Rational =
    new Rational(numerator * that.denominator, denominator * that.numerator)
  def / (i: Int): Rational =
    new Rational(numerator, i * this.denominator)

  override def toString: String = numerator + "/" + denominator

  def gcd(a: Int, b: Int): Int =
    if(b == 0) a else gcd(b, a % b)
}

Functions and Closures

Function Literal

A function literal is compiled into a class that when instantiated at runtime is a function value.

scala> val increase = (x: Int) => x + 1
increase: Int => Int = <function1>

scala> increase(1)
res2: Int = 2
scala> val increase2 = (x: Int) =>{
    
    
     |   println("hello")
     |   x + 1
     | }
increase2: Int => Int = <function1>

scala> increase2(1)
hello
res4: Int = 2
scala> val f = (_: Int) + (_: Int)
f: (Int, Int) => Int = <function2>

scala> f(1,1)
res3: Int = 2

## Closures ```bash scala> var more = 1 more: Int = 1

scala> val addMore = (x: Int) => x + more
addMore: Int => Int =

scala> addMore(1)
res5: Int = 2

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. A function literal with no free variables, such as (x: Int) => x + 1, is called a closed term, where a term is a bit of source code. Thus a function value created at runtime from this function literal is not a closure in the strictest sense, because (x: Int) => x + 1 is already closed as written. But any function literal with free variables, such as (x: Int) => x + more, is an open term. Therefore, any function value created at runtime from (x: Int) => x + more will by definition require that a binding for its free variable, more, be captured. The resulting function value, which will contain a reference to the captured more variable, is called a closure, therefore, because the function value is the end product of the act of closing the open term, (x: Int) => x + more.

This example brings up a question: what happens if more changes after the closure is created? In Scala, the answer is that the closure sees the change. For example:

```bash
scala> more = 100
more: Int = 100

scala> addMore(1)
res6: Int = 101

scala> def makeIncreaser(more: Int) = (x: Int) => x + more
makeIncreaser: (more: Int)Int => Int

scala> val inc1 = makeIncreaser(1)
inc1: Int => Int = <function1>

scala> val inc999 = makeIncreaser(999)
inc999: Int => Int = <function1>

scala> inc1(1)
res7: Int = 2

scala> inc999(1)
res8: Int = 1000

Repeated Parameters

scala> def echo(args: String*) =
     |   for(arg <- args) println(arg)
echo: (args: String*)Unit

scala> echo()

scala> echo("one")
one

scala> echo("hello", "world")
hello
world

scala> val arr = Array("1", "2", "3")
arr: Array[String] = Array(1, 2, 3)

scala> echo(arr)
<console>:14: error: type mismatch;
 found   : Array[String]
 required: String
       echo(arr)
            ^

scala> echo(arr: _*)
1
2
3

An Example(Imperative VS Functional )

To transform a while loop that updates vars into a more functional style that uses only vals…

// Functional style
  def multiTable() = {
    
    
    val tableSeq =
      for(row <- 1 to 10)
        yield makeRow(row)
    tableSeq.mkString("\n")
  }
  def makeRow(row: Int) = makeRowSeq(row).mkString
  def makeRowSeq(row: Int) =
    for(col <- 1 to 10) yield {
    
    
      val product = (row * col).toString
      val padding = " " * (4 - product.length)
      padding + product
    }
  
// Imperative style
  def printMultiTable() = {
    
    
    var x = 1
    while(x <= 10) {
    
    
      var y = 1
      while(y <= 10) {
    
    
        val product = (x * y).toString
        var length = product.length
        while(length < 4){
    
    
          print(" ")
          length += 1
        }
        print(product)
        y += 1
      }
      println()
      x += 1
    }
  }

在这里插入图片描述

Tail recursion

// not tail recursion
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)

// not tail recursion
def boom(x: Int): Int = 
    if (x == 0) throw new Exception("boom!")
    else boom(x - 1) + 1

// tail recursion
def bang(x: Int): Int = 
    if (x == 0) throw new Exception("bang!")
    else bang(x - 1)

Reducing code duplication

def filesContaining(query: String) =
    for (file <- filesHere; if file.getName.contains(query))
      yield file

def filesRegex(query: String) =
    for (file <- filesHere; if file.getName.matches(query))
      yield file

def filesMatching(query: String, method) =
    for (file <- filesHere; if file.getName.method(query))
      yield file

object FileMatcher {
    
    
      private def filesHere = (new java.io.File(".")).listFiles
  
      private def filesMatching(matcher: String => Boolean) =
        for (file <- filesHere; if matcher(file.getName))
          yield file
  
      def filesEnding(query: String) =
        filesMatching(_.endsWith(query))
  
      def filesContaining(query: String) =
        filesMatching(_.contains(query))
  
      def filesRegex(query: String) =
        filesMatching(_.matches(query))
    }

Simplifying client code

def containsNeg(nums: List[Int]): Boolean = {
    
    
    var exists = false
    for (num <- nums)
      if (num < 0)
        exists = true
    exists
  }
def containsNeg(nums: List[Int]) = nums.exists(_ < 0)

Curring

A curried function is applied to multiple argument lists, instead of just one.

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Int

scala> curriedSum(1)(1)
res16: Int = 2

scala> val onePlus = curriedSum(1)_
onePlus: Int => Int = <function1>

scala> onePlus(1)
res17: Int = 2
scala> def f(x: Int)(op: Int => Boolean){
    
    
     |  val xx = x * x
     |  val bool = op(xx)
     |  println(bool)
     | }
f: (x: Int)(op: Int => Boolean)Unit

scala> f(2)(_ < 3)
false

scala> f(2){
    
     _ < 3 }
false

By-name parameters

scala> def myAssert(predicate: () => Boolean) = if(!predicate()) println(1)
myAssert: (predicate: () => Boolean)Unit

scala> myAssert(() => 5 < 2)
1




scala> def byNameAssert(predicate: => Boolean) = if(!predicate) println(1)
byNameAssert: (predicate: => Boolean)Unit

scala> byNameAssert(5 < 2)
1

Composition and Inheritance

In Scala, fields and methods belong to the same namespace. This makes it possible for a field to override a parameterless method. Generally, Scala has just two namespaces for definitions in place of Java’s four. Java’s four namespaces are fields, methods, types, and packages. By contrast, Scala’s two namespaces are values (fields, methods, packages, and singleton objects) and types (class and trait names).

Only inheritance suffers from the fragile base class problem, in which you can inadvertently break subclasses by changing a superclass.

The example below is a little difficult to understand but it is actually a good one!

package primer

import Element.elem

object Spiral {
    
    
  val space = elem(" ")
  val corner = elem("+")

  def spiral(nEdges: Int, direction: Int): Element = {
    
    
    if (nEdges == 1)
      elem("+")
    else {
    
    
      val sp = spiral(nEdges - 1, (direction + 3) % 4)
      def verticalBar = elem('|', 1, sp.height)
      def horizontalBar = elem('-', 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)
    }
  }

  def main(args: Array[String]) {
    
    
    val nSides = args(0).toInt
    println(spiral(nSides, 0))
  }
}

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
    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 widen(w: Int): Element =
    if (w <= width) this
    else {
    
    
      val left = elem(' ', (w - width) / 2, height)
      var right = elem(' ', w - width - left.width, height)
      left beside this beside right
    }

  def heighten(h: Int): Element =
    if (h <= height) this
    else {
    
    
      val top = elem(' ', width, (h - height) / 2)
      var bot = elem(' ', width, h - height - top.height)
      top above this above bot
    }

  override def toString = contents mkString "\n"
}

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 elem(contents:  Array[String]): Element =
    new ArrayElement(contents)

  def elem(chr: Char, width: Int, height: Int): Element =
    new UniformElement(chr, width, height)

  def elem(line: String): Element =
    new LineElement(line)
}

Note! You should compile and run(with the package name as below) int the directory of Spiral.scala’s package.

……\src\main\scala\primer>scala primer.Spiral 6
+-----
|
| +-+
| + |
|   |
+---+

Scala’s Hierarchy

在这里插入图片描述

Traits

The Ordered Trait

class Rational(n: Int, d: Int) {
    
    
    // ...
    def < (that: Rational) = 
      this.numer * that.denom > that.numer * this.denom
    def > (that: Rational) = that < this
    def <= (that: Rational) = (this < that) || (this == that)
    def >= (that: Rational) = (this > that) || (this == that)
  }

Instead of the way above, use the way as below to implement the comparison of Rationals.

class Rational(n: Int, d: Int) extends Ordered[Rational] {
    
    
    // ...
    def compare(that: Rational) =
      (this.numer * that.denom) - (that.numer * this.denom)
  }

This is the complete code which enhance the previous chapter’s.

package primer

object FunctionalObjects {
    
    
  def main(args: Array[String]): Unit = {
    
    
//    new Rational(1, 0)  // java.lang.IllegalArgumentException
    val x = new Rational(1, 2)
    val y = new Rational(2, 3)
    println(x + y)  // 7/6
    println(x + x * y) // 5/6 (x + x * y) == x + (x * y)
    println((x + x) * y) // 2/3

    // Implicit conversions
    // Note that for an implicit conversion to work, it needs to be in scope. If you place the implicit method definition inside class Rational, it won't be in scope in the interpreter.
    implicit def intToRational(x: Int) = new Rational(x)
    println(2 * y)  // 4/3

    // extends Ordered[Rational]
    println(x < y)

  }


}

class Rational(n: Int, d: Int) extends Ordered[Rational] {
    
    
  require(d != 0) // Checking preconditions

  private val g = gcd(n, d);
  val numerator = n / g;
  val denominator = d / g;

  def this(n: Int) = this(n, 1) // Auxiliary constructors

  def + (that: Rational): Rational =
    new Rational(
      numerator * that.denominator + denominator * that.numerator,
      denominator * that.denominator
    )
  def + (i: Int): Rational =
    new Rational(numerator + i * denominator, denominator)

  def - (that: Rational): Rational =
    new Rational(
      numerator * that.denominator - denominator * that.numerator,
      denominator * that.denominator
    )
  def - (i: Int): Rational =
    new Rational(numerator - i * denominator, denominator)

  def * (that: Rational): Rational =
    new Rational(numerator * that.numerator, denominator * that.denominator)
  def * (i: Int): Rational =
    new Rational(numerator * i, denominator)

  def / (that: Rational): Rational =
    new Rational(numerator * that.denominator, denominator * that.numerator)
  def / (i: Int): Rational =
    new Rational(numerator, i * this.denominator)

  override def toString: String = numerator + "/" + denominator

  def gcd(a: Int, b: Int): Int =
    if(b == 0) a else gcd(b, a % b)

  override def compare(that: Rational): Int =
    (this.numerator * that.denominator) - (that.numerator * this.denominator)
}

Traits as stackable modifications

package primer

import scala.collection.mutable.ArrayBuffer

object TraitsAsStackableModifications {
    
    
  def main(args: Array[String]) {
    
    
    // Filtering's put is invoked first and then Incrementing's put is invoked. (from right to left)
    // Filtering's put doesn't put the number into buf except that it choose what is not less than 0.
    // Really putting is done by the most left trait Increasing!
    val queue = new BasicIntQueue with Increasing with Filtering
    queue.put(-1)
    queue.put(0)
    queue.put(1)
    // queue: 1 2 (not: 0 1 0 1 2)
    println(queue.get())  // 1
    println(queue.get())  // 2
  }
}

abstract class IntQueue {
    
    
  def put(x: Int)
  def get(): Int
}

class BasicIntQueue extends IntQueue {
    
    
  private val buf = new ArrayBuffer[Int]

  override def put(x: Int) {
    
     buf += x }
  override def get(): Int = buf.remove(0)
}

trait Increasing extends IntQueue {
    
    
  abstract override def put(x: Int) = super.put(x + 1)
}
trait Filtering extends IntQueue {
    
    
  abstract override def put(x: Int) = if(x >= 0) super.put(x)
}

Case Classes and Pattern Matching

scala> import Math.{E, PI}
import Math.{E, PI}

scala> E match {
     |  case PI => "strange math? PI="+PI
     |  case _ => "OK"
     | }
res51: String = OK

scala> val pi = PI
pi: Double = 3.141592653589793

scala> E match {
     |  case pi => "strange math? pi="+pi
     | }
res52: String = strange math? pi=2.718281828459045

scala> E match {
     |  case pi => "strange math? pi="+pi
     |  case _ => "OK"
     | }
<console>:16: warning: patterns after a variable pattern cannot match (SLS 8.1.1)
        case pi => "strange math? pi="+pi
             ^
<console>:17: warning: unreachable code due to variable pattern 'pi' on line 16
        case _ => "OK"
                  ^
<console>:17: warning: unreachable code
        case _ => "OK"
                  ^
res53: String = strange math? pi=2.718281828459045

scala> E match {
     |  case `pi` => "strange math? pi="+pi
     |  case _ => "OK"
     | }
res54: String = OK

Generics Erased

scala> def isIntIntMap(x: Any) = x match {
     |            case m: Map[Int, Int] => true
     |            case _ => false
     |          }
<console>:15: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
                  case m: Map[Int, Int] => true
                          ^
isIntIntMap: (x: Any)Boolean

scala> isIntIntMap(Map(1 -> 1))
res55: Boolean = true

scala> isIntIntMap(Map("a" -> "a"))
res56: Boolean = true

The only exception to the erasure rule is arrays, because they are handled specially in Java as well as in Scala. The element type of an array is stored with the array value, so you can pattern match on it. Here’s an example.

scala> def isStringArray(x: Any) = x match {
     |            case a: Array[String] => "yes"
     |            case _ => "no"
     |          }
isStringArray: (x: Any)String

scala> isStringArray(Array("a"))
res57: String = yes

scala> isStringArray(Array(1, 2, 3))
res58: String = no

Variable Binding

scala> list
res79: (String, String) = (hello,scala)

scala> list match {
     |   case ("hello", e @ "scala") => println(e)
     |   case _ => println("...")
     |  }
scala

scala>

Pattern Guard

  // match only positive integers
  case n: Int if 0 < n => ...  
  
  // match only strings starting with the letter `a'
  case s: String if s(0) == 'a' => ... 

Sealed Classes

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
scala> def describe(e: Expr): String = e match {
     |     case Number(_) => "a number"
     |     case Var(_)    => "a variable"
     |   }
<console>:18: warning: match may not be exhaustive.
It would fail on the following inputs: BinOp(_, _, _), UnOp(_, _)
       def describe(e: Expr): String = e match {
                                       ^
describe: (e: Expr)String

scala> def describe(e: Expr): String = e match {
     |     case Number(_) => "a number"
     |     case Var(_) => "a variable"
     |     case _ => throw new RuntimeException // Should not happen
     |   }
describe: (e: Expr)String

scala> def describe(e: Expr): String = (e: @unchecked) match {
     |     case Number(_) => "a number"
     |     case Var(_)    => "a variable"
     |   }
describe: (e: Expr)String

The Option Type

Scala has a standard type named Option for optional values. Such a value can be of two forms. It can be of the form Some(x) where x is the actual value. Or it can be the None object, which represents a missing value.

scala> val capitals =
     |            Map("France" -> "Paris", "Japan" -> "Tokyo")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo)

scala> capitals get "France"
res82: Option[String] = Some(Paris)

scala> capitals get "North Pole"
res83: Option[String] = None


scala> def show(x: Option[String]) = x match {
     |            case Some(s) => s
     |            case None => "?"
     |          }
show: (x: Option[String])String

scala> show(capitals get "Japan")
res84: String = Tokyo

scala> show(capitals get "North Pole")
res85: String = ?

Case Sequences as Partial Functions

scala> val withDefault: Option[Int] => Int = {
     |     case Some(x) => x
     |     case None => 0
     |   }
withDefault: Option[Int] => Int = <function1>

scala> withDefault(Some(10))
res91: Int = 10

scala> withDefault(Non)
<console>:15: error: not found: value Non
       withDefault(Non)
                   ^

scala> withDefault(None)
res93: Int = 0

Use List/Queue/Stack/Set/Map/Tuple

Chapter 16
Chapter 17

Abstract Members

A quick tour of abstract members

The following trait declares one of each kind of abstract member: an abstract type (T), method (transform), val (initial), and var (current).

trait Abstract {
    
    
    type T
    def transform(x: T): T
    val initial: T
    var current: T
  }

A concrete implementation of Abstract needs to fill in definitions for each of its abstract members. Here is an example implementation that provides these definitions.

 class Concrete extends Abstract {
    
    
    type T = String
    def transform(x: String) = x + x
    val initial = "hi"
    var current = initial
  }

Type members

One reason to use a type member is to define a short, descriptive alias for a type whose real name is more verbose, or less obvious in meaning, than the alias. Such type members can help clarify the code of a class or trait. The other main use of type members is to declare abstract types that must be defined in subclasses.

Abstract vals

abstract class Fruit {
    
    
    val v: String // `v' for value
    def m: String // `m' for method
  }
  
  abstract class Apple extends Fruit {
    
    
    val v: String
    val m: String // OK to override a `def' with a `val'
  }
  
  abstract class BadApple extends Fruit {
    
    
    def v: String // ERROR: cannot override a `val' with a `def'
    def m: String
  }

Abstract vars

trait RationalTrait {
    
     
      val numerArg: Int 
      val denomArg: Int 
      require(denomArg != 0)
      private val g = gcd(numerArg, denomArg)
      val numer = numerArg / g
      val denom = denomArg / g
      private def gcd(a: Int, b: Int): Int = 
        if (b == 0) a else gcd(b, a % b)
      override def toString = numer +"/"+ denom
    }
scala> val x = 2
x: Int = 2

scala> new RationalTrait {
     |            val numerArg = 1 * x
     |            val denomArg = 2 * x
     |          }
java.lang.IllegalArgumentException: requirement failed
  at scala.Predef$.require(Predef.scala:212)
  at RationalTrait$class.$init$(<console>:18)
  ... 36 elided

This example demonstrates that initialization order is not the same for class parameters and abstract fields. A class parameter argument is evaluated before it is passed to the class constructor (unless the parameter is by-name). An implementing val definition in a subclass, by contrast, is evaluated only after the superclass has been initialized.

Pre-initialized fields

The first solution, pre-initialized fields, lets you initialize a field of a subclass before the superclass is called. To do this, simply place the field definition in braces before the superclass constructor call.

scala> new {
     |              val numerArg = 1 * x
     |              val denomArg = 2 * x
     |            } with RationalTrait
res113: RationalTrait = 1/2

Pre-initialized fields are not restricted to anonymous classes; they can also be used in objects or named subclasses.

scala> object twoThirds extends {
     |       val numerArg = 2
     |       val denomArg = 3
     |     } with RationalTrait
defined object twoThirds

scala> twoThirds.
denom   denomArg   numer   numerArg   toString

scala> twoThirds.denom
denom   denomArg

scala> twoThirds.denom
res119: Int = 3

scala> twoThirds.n
ne   notify   notifyAll   numer   numerArg

scala> twoThirds.numer
numer   numerArg

scala> twoThirds.numerArg
res120: Int = 2

Because pre-initialized fields are initialized before the superclass constructor is called, their initializers cannot refer to the object that’s being constructed. Consequently, if such an initializer refers to this, the reference goes to the object containing the class or object that’s being constructed, not the constructed object itself. Here’s an example.

scala> new {
     |          val numerArg = 1
     |          val denomArg = this.numerArg * 2
     |        } with RationalTrait
<console>:17: error: value numerArg is not a member of object $iw
                val denomArg = this.numerArg * 2
scala> class RationalClass(n: Int, d: Int) extends {
     |       val numerArg = n
     |       val denomArg = d
     |     } with RationalTrait {
     |       def + (that: RationalClass) = new RationalClass(
     |         numer * that.denom + that.numer * denom,
     |         denom * that.denom
     |       )
     |     }
defined class RationalClass

scala> new RationalClass(1,2)
res122: RationalClass = 1/2

Lazy vals

scala> object Demo {
     |            val x = { println("initializing x"); "done" }
     |          }
defined object Demo

scala> Demo
initializing x
res125: Demo.type = Demo$@1975351

scala> Demo.x
res126: String = done
scala> object Demo {
     |            lazy val x = { println("initializing x"); "done" }
     |          }
defined object Demo

scala> Demo
res127: Demo.type = Demo$@1f0da4

scala> Demo.x
initializing x
res128: String = done

Abstract types

scala> class Food
defined class Food

scala>   abstract class Animal {
     |     def eat(food: Food)
     |   }
defined class Animal

scala> class Grass extends Food
defined class Grass

scala>   class Cow extends Animal {
     |     override def eat(food: Grass) {} // This won't compile
     |   }
<console>:15: error: class Cow needs to be abstract, since method eat in class Animal of type (food: Food)Unit is not defined
(Note that Food does not match Grass: class Grass is a subclass of class Food, but method parameter types must match exactly.)
         class Cow extends Animal {
               ^
<console>:16: error: method eat overrides nothing.
Note: the super classes of class Cow contain the following, non final members named eat:
def eat(food: Food): Unit
           override def eat(food: Grass) {} // This won't compile
scala> class Food
defined class Food

scala>     abstract class Animal {
     |       type SuitableFood <: Food
     |       def eat(food: SuitableFood)
     |     }
defined class Animal

scala> class Grass extends Food
defined class Grass

scala>     class Cow extends Animal {
     |       type SuitableFood = Grass
     |       override def eat(food: Grass) {}
     |     }
defined class Cow

scala> class Fish extends Food
defined class Fish

scala> val bessy: Animal = new Cow
bessy: Animal = Cow@272102

scala> bessy eat (new Fish)
<console>:16: error: type mismatch;
 found   : Fish
 required: bessy.SuitableFood
       bessy eat (new Fish)
                  ^

Enumerations

object Color extends Enumeration {
    
    
    val Red = Value
    val Green = Value
    val Blue = Value
  }

// or

object Color extends Enumeration {
    
    
    val Red, Green, Blue = Value
  }
object Direction extends Enumeration {
    
    
    val North, East, South, West = Value
  }

// or

object Direction extends Enumeration {
    
    
    val North = Value("North")
    val East = Value("East")
    val South = Value("South")
    val West = Value("West")
  }
scala> Direction.values.foreach(println)
North
East
South
West
scala> Direction.values.foreach(x => print(x + " "))
North East South West
scala> Direction.East
res141: Direction.Value = East

scala> Direction.East.id
res142: Int = 1

scala> Direction(1)
res143: Direction.Value = East

Reference

[1] Programming in Scala, First Edition by Martin Odersky, Lex Spoon, and Bill Venners December 10, 2008

猜你喜欢

转载自blog.csdn.net/ccnuacmhdu/article/details/107404540