这一篇是Scala语言自学的开篇,是一个快速熟悉的概览。之后每一篇日志会深入学习每一个知识块。

这篇文章cover了Programming in Scala的前两章:Frist Steps in Scala 和 Next Steps in Scala

Define some functions

The basic form of a function definition in Scala

The basic form of a function definition in Scala

  • In the case of max, however, you may leave the result type off and the compiler will infer it.
  • If a function consists of just one statement, you can optionally leave off the curly braces.
def max(x: Int, y: Int) = if (x > y) x else y
  • It is often a good idea to indicate function result types explicitly, even when the compiler doesn’t require it.
def greet() = println("Hello, world!")
  • Unit is greet's result type
  • Every void- returning method in Java is mapped to a Unit-returning method in Scala.

Write some Scala scripts

// Say hello to the first argument
println("Hello, " + args(0) + "!")

then run:

scala helloarg.scala planet
  • In Scala, arrays are zero based, and you access an element by specifying an index in parentheses.

Loop with while; decide with if

var i = 0
while (i < args.length) {
  println(args(i))
  i += 1
}
  • Each indented two spaces, the recommended indentation style for Scala.
  • Java’s ++i and i++ don’t work in Scala.

Interate with foreach and for

The syntax of a function literal in Scala

The syntax of a function literal in Scala

for (arg <- args)
  println(arg)
  • To the left of <- is "arg", the name of a val, not var.
  • arg can’t be reassigned inside the body of the for expression
  • For each element of the args array, a new arg val will be created and initialized to the element value.

Parameterize arrays with types

val greetStrings: Array[String] = new Array[String](3)
  • When you define a variable with val, the variable can’t be reassigned, but the object to which it refers could potentially still be changed.
  • array itself is mutable
for (i <- 0 to 2)
  print(greetStrings(i))
  • if a method takes only one parameter, you can call it without a dot or parentheses
  • The code 0 to 2 is transformed into the method call (0).to(2)
  • this syntax only works if you explicitly specify the receiver of the method call
  • Scala doesn’t technically have operator overloading, because it doesn’t actually have operators in the traditional sense. Instead, characters such as +, -, , and / can be used in method names.
  • When you apply parentheses surrounding one or more values to a variable, Scala will transform the code into an invocation of a method named apply on that variable. So greetStrings(i) gets transformed into greetStrings.apply(i).
  • any application of an object to some arguments in parentheses will be transformed to an apply method call
All operations are method calls in Scala

All operations are method calls in Scala

Similarly, when an assignment is made to a variable to which parentheses and one or more arguments have been applied, the compiler will transform that into an invocation of an update method that takes the arguments in parentheses as well as the object to the right of the equals sign.

Thus, the following is semantically equivalent code

val greetStrings = new Array[String](3)
greetStrings.update(0, "Hello")
greetStrings.update(1, ", ")
greetStrings.update(2, "world!\n")
for (i <- 0.to(2))
  print(greetStrings.apply(i))
  • The compiler infers the type of the array to be Array[String], because you passed strings to it.
val numNames = Array("zero", "one", "two")

What you’re actually doing above is calling a factory method, named apply, which creates and returns the new array. This apply method takes a variable number of arguments and is defined on the Array companion object.

A more verbose way to call the same apply method is:

val numNames2 = Array.apply("zero", "one", "two")

Use lists

A Scala array is a mutable sequence of objects that all share the same type.
Although you can’t change the length of an array after it is instantiated, you can change its element values. Thus, arrays are mutable objects.

For an immutable sequence of objects that share the same type you can use Scala’s List class.

Scala’s List, scala.List, differs from Java’s java.util.List type in that Scala Lists are always immutable (whereas Java Lists can be mutable). More generally, Scala’s List is designed to enable a functional style of programming.

When you call a method on a list that might seem by its name to imply the list will mutate, it instead creates and returns a new list with the new value.

val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
println(oneTwo + " and " + threeFour + " were not mutated.")
println("Thus, " + oneTwoThreeFour + " is a new list.")
  • You don’t need to say new List because “List.apply()” is defined as a factory method on the scala.List companion object.
  • List has a method named ::: for list concatenation.
val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)
  • ::, which is pronounced “cons” prepends a new element to the beginning of an existing list and returns the resulting list.
  • :: is a method of its right operand
  • an empty list is Nil
  • The reason you need Nil at the end is that :: is defined on class List. If you try to just say 1 :: 2 :: 3, it won’t compile because 3 is an Int, which doesn’t have a :: method.

For example, the following script will produce the same output as the previous one, “List(1, 2, 3)”:

val oneTwoThree = 1 :: 2 :: 3 :: Nil
println(oneTwoThree)

Why not apped to lists?
Class List does offer an append operation—it’s written :+, but this operation is rarely used, because the time it takes to append to a list grows linearly with the size of the list, whereas prepending with :: takes constant time.

Use tuples

A tuple could contain both an integer and a string at the same time.

For example, The type of ('u', 'r', "the", 1, 4, "me") is Tuple6[Char, Char, String, Int, Int, String].

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)
  • Although conceptually you could create tuples of any length, currently the Scala library only defines them up to Tuple22.
  • The actual type of a tuple depends on the number of elements it contains and the types of those elements.

Accessing the elements of a tuple
You may be wondering why you can’t access the elements of a tuple like the elements of a list, for example, with “pair(0)”. The reason is that a list’s apply method always returns the same type, but each element of a tuple may be a different type: _1 can have one result type, _2 another,andsoon. These _N numbers are one-based,instead of zero-based, because starting with 1 is a tradition set by other languages with statically typed tuples, such as Haskell and ML.

Use sets and maps

Scala API contains a base trait for sets, where a trait is similar to a Java interface.

Although in Java you “implement” interfaces, in Scala you “extend” or “mix in” traits.

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))
Class hierarchy for Scala sets

Class hierarchy for Scala sets

  • you invoke apply on the companion object for scala.collection.immutable.Set, which returns an instance of a default, immutable Set
  • On both mutable and immutable sets, the + method will create and return a new set with the element added.
  • mutable sets offer an actual += method, immutable sets do not

If you want a mutable set, you’ll need to use an import,

import scala.collection.mutable
val movieSet = mutable.Set("Hitch", "Poltergeist")
movieSet += "Shrek"
println(movieSet)
  • += is an actual method defined on mutable sets
  • Had you wanted to, instead of writing movieSet += "Shrek", you could have written movieSet.+=("Shrek")

Occasionally you may want an explicit set class. Simply import that class you need, and use the factory method on its companion object.

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")

Because the movieSet is mutable, there is no need to reassign movieSet, which is why it can be a val. By contrast, using += with the immutable set in jetSet required reassigning jetSet, which is why it must be a var

Class hierarchy for Scala maps

Class hierarchy for Scala maps

import scala.collection.mutable
val treasureMap = mutable.Map[Int, String]()
treasureMap += (1 -> "Go to island.")
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (3 -> "Dig.")
println(treasureMap(2))

Creating, initializing, and using a mutable map

  • the Scala compiler transforms a binary operation expression like 1 -> "Go to island." into (1).->("Gotoisland.")
  • you are actually calling a method named -> on an integer with the value 1, passing in a string with the value "Go to island."
  • This -> method, which you can invoke on any object in a Scala program, returns a two-element tuple containing the key and value.
  • The explicit type parameterization, “[Int,String]”, is required in because without any values passed to the factory method, the compiler is unable to infer the map’s type parameters.

If you prefer an immutable map, no import is necessary, as immutable is the default map.

val romanNumeral = Map(
  1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
println(romanNumeral(4))

Creating, initializing, and using an immutable map

  • the compiler can infer the type parameters from the values passed to the map factory, thus no explicit type parameters are needed

Learn to recognize the functional style

  • One telltale sign is that if code contains any vars, it is probably in an imperative style.
  • If the code contains no vars at all—i.e., it contains only vals—it is probably in a functional style.
  • One way to move towards a functional style, therefore, is to try to program without vars.
  • Scala encourages you to lean towards vals.
  • The telltale sign of a function with side effects is that its result type is Unit.
  • If a function isn’t returning any interesting value, which is what a result type of Unit means, the only way that function can make a difference in the world is through some kind of side effect.

Preferring methods without side effects encourages you to design programs where side- effecting code is minimized. One benefit of this approach is that it can help make your programs easier to test.

  • You may find that in some situations an imperative style is a better fit for the problem at hand, and in such cases you should not hesitate to use it.

A balanced attitude for Scala programmers
Prefer vals, immutable objects, and methods without side effects. Reachforthemfirst. Usevars,mutableobjects,andmethodswithside effects when you have a specific need and justification for them.

Read lines from a file

import scala.io.Source
   def widthOfLength(s: String) = s.length.toString.length
   if (args.length > 0) {
     val lines = Source.fromFile(args(0)).getLines().toList
     val longestLine = lines.reduceLeft(
       (a, b) => if (a.length > b.length) a else b
     )
     val maxWidth = widthOfLength(longestLine)
     for (line <- lines) {
       val numSpaces = maxWidth - widthOfLength(line)
       val padding = " " * numSpaces
       println(padding + line.length + " | " + line)
} }
else
  Console.err.println("Please enter filename")

Printing formatted character counts for the lines of a file

  • By transforming it into a list via the toList call, you gain the ability to iterate as many times as you wish, at the cost of storing all lines from the file in memory at once.
  • The max method, which you can invoke on any Int, returns the greater of the value on which it was invoked and the value passed to it.

Conclusion

With the knowledge you’ve gained in this chapter, you should be able to start using Scala for small tasks, especially scripts. In later chapters, we will dive further into these topics and introduce other topics that weren’t even hinted at here.