CHAPTER 13

image

Scala Best Practices

Thanks for hanging in and reading all this way. We’ve covered a lot of ground so far. We’ve discussed the Scala language and developed a collection of idioms for building applications using Scala. We’ve explored how Scala can be used by different team members in different ways. We’ve seen how Scala allows you to compose fine-grained pieces of code together into complex systems that work well together.

But no technology is an island. No matter how good Scala is in the abstract, it’s only valuable if it can help your organization produce better and more maintainable code, faster. The good news is that Scala compiles down to JVM bytecode and works great with existing Java libraries. If you’re working at a Java shop, the cost of using Scala on some projects is minimal. You can test Scala with your existing Java test tools. You store compiled Scala code in JAR and WAR files, so it looks and feels to the rest of your organization like what they’re used to—Java bytecode. The operational characteristic of Scala code on web servers is indistinguishable from the operational characteristics of Java code.

In this chapter, we’re going to explore some Scala idioms and Scala design patterns. Scala is not just a programming language; it is a new way of thinking and reasoning about programming. It will take you time to design code that fits into Scala paradigms and to discover and devise paradigms of your own. So, write that Java-style code in Scala and then apply the idioms and see how your code changes and how your thought patterns emerge. The first step is to recognize the functional style and the immediately obvious differences between imperative and functional styles.

Recognizing the Functional Style

Scala supports both functional and imperative style of programming. If you come from an imperative background, that is, if you are a Java programmer, Scala also allows you to program in an imperative style, but it encourages functional approach. The functional style of programming enables you to write concise and less error-prone code. However, programming in functional style proves to be a daunting task for a Java programmer. This section, gives few pointers to figure out how to program in the functional style. The first and foremost step is to recognize the difference between the imperative and functional styles in code.

A quick way to distinguish between functional and imperative style is that vars signify an imperative style and using vals is more akin to a functional approach. Therefore, transitioning from imperative to functional style means your program should be free of vars.

Listing 13-1 shows a simple string array that stores names.

Listing 13-1. A String Array

val strArray = Array("Vishal Layka", "David Pollak")

If you want to print the contents of the array shown in Listing 13-1, and if you were a Java programmer before you moved to Scala, you might write the while loop akin to the while loop show in Listing 13-2.

Listing 13-2. Print in an Imperative Style

def print(strArray: Array[String]): Unit = {
var i = 0
while (i <strArray.length) {
println(strArray (i))
i += 1
}
}

Listing 13-2 uses a var and is therefore in the imperative style. When you run Listing 13-2 in REPL, you see the following output:

scala> val strArray = Array("Vishal Layka", "David Pollak")
strArray: Array[String] = Array(Vishal Layka, David Pollak) scala> def print(strArray: Array[String]): Unit = {
     | var i = 0
     | while (i < strArray.length) {
     | println(strArray (i))
     | i += 1
     | }
     | }
print: (strArray: Array[String])Unit
scala> print(strArray)
Vishal Layka
David Pollak

As you can see Listing 13-2 uses var and therefore is of an imperative style. You can transform this bit of code into a more functional style by first getting rid of the var, as shown in Listing 13-3.

Listing 13-3. Moving Toward Functional Style

def print(strArray: Array[String]): Unit = {
strArray.foreach(println)
}

scala> def print(strArray: Array[String]): Unit = {
     | strArray.foreach(println)
     | }
print: (strArray: Array[String])Unit
scala> print(strArray)
Vishal Layka
David Pollak

As you can see in Listing 13-3, the refactored functional code is much clearer, more concise, and less error-prone than the original imperative code in Listing 13-2. Listing 13-3 is more functional but not purely functional. We will transform Listing 13-3 to purely functional in the next section.

Writing Pure Functions

We refactored print method in the Listing 13-2 which was in imperative style to Listing 13-3. Listing 13-3 is functional but not purely functional, because it causes side effects. The side effect caused by Listing 13-3 is that it prints the output to the output stream. The functional equivalent will be a more defining method that manipulates the passed args for printing. For example, it formats it, but does not print it and returns the formatted string for printing, as shown in Listing 13-4.

Listing 13-4. Function with No Side Effect

def formatArgs(strArray: Array[String]) = strArray.mkString(":")

Image Note  If a function’s result type is Unit, the function has side effects.

Now the function in Listing 13-4 is purely functional, that is, it causes no side effect affected by var. The mkString method is defined on collections that is meant to return a string that results from calling toString on each element. You can call mkString on any iterable collection.

The overridden toString method is used to return the string representation of an object, as shown in Listing 13-5.

Listing 13-5. Using mkSting

val x = List("x", "y", "z")
println(x.mkString(" : "))

You get the following output:

scala> val x = List("x", "y", "z")
x: List[String] = List(x, y, z)
scala> println(x.mkString(" : "))
x : y : z

The function in Listing 13-4 doesn’t print anything like the print method in Listing 13-3 did. You can pass the result of formatArgs to println to print the output shown in Listing 13-6.

Listing 13-6. Using formatArgs

println(formatArgs(strArray))

You can run this in REPL as shown:

scala> val strArray = Array("Vishal Layka", "David Pollak")
strArray: Array[String] = Array(Vishal Layka, David Pollak)
scala> def formatArgs(strArray: Array[String]) = strArray.mkString(":")
formatArgs: (strArray: Array[String])String
scala> println(formatArgs(strArray))
Vishal Layka:David Pollak

That said, the essential useful feature of a program is to cause side effects; otherwise it purports no real application. Creating methods that do not cause side effects encourages you to minimize the code that would cause side effects, thus leading you to design robust programs.

Leverage Type Inferencing

Scala is a statically typed language. In a statically typed language the values and the variables have types. And also, Scala, is a type-inferred language, which means you do not have to write the boilerplate code because this boilerplate code is inferred by Scala. This type inference is the feature of a dynamic type language. In this manner, Scala brings best of the two worlds.

Image Note  In a dynamic type system, unlike static typing, only values have types, variables do not have types.

We now create an array of maps (as shown in Listing 13-7) to illustrate how type inferencing works.

Listing 13-7. How Type Inferencing Works

val books = Array(
Map("title" -> "Beginning Scala", "publisher" -> "Apress"),
Map("title" -> "Beginning Java", "publisher" -> "Apress")
)

If you run this Scala code in the Scala REPL, you’ll see the following output:

scala> val books = Array(
     | Map("title" -> "Beginning Scala", "publisher" -> "Apress"),
     | Map("title" -> "Beginning Java", "publisher" -> "Apress")
     | )
books: Array[scala.collection.immutable.Map[String,String]] = Array(Map(title -> Beginning Scala, publisher -> Apress), Map(title -> Beginning Java, publisher -> Apress))

As you might notice, only the array and maps were specified in Listing 13-7, not their types. As you can see in the output in REPL, the Scala compiler inferred the types of the array and the map. In this way you could let the type inferencer determine the type for you, which can help you trim down a lot of ceremonious code thus keep the code clean and lean and central to the business logic.

Image Tip  Let the type inferencer determine the type to trim down the ceremonious code.

Think Expressions

As you learned in Chapter 4, expressions evaluate to a value, so there’s no need of a return statement. While in Java, a return statement is common-place as illustrated Listing 13-8.

Listing 13-8. Return Statement in Java

def phoneBanking(key: Int) : String = {
var result : String = _
errorCode match {
case 1 =>
result = "Banking service"
case 2 =>
result = "Credit cards"
case _ =>
result = "Speak to the customer executive"
}
return result;
}

As you can see, the final result is stored in a result variable. The code, while flowing through a pattern match, assigns strings to the result variable. To improvise on this code you need to follow an expression-oriented approach, which is explained in detail in Chapter 4. This can be done in the following way.

  • As mentioned earlier, the first and foremost way to adopt a functional style is to use val instead of var. We first change the result variable to a val.
  • Instead of assigning through the case statements, use the last expression of the case statement for assigning the result, t.

Listing 13-9 shows the code refactored for an expression-oriented pattern match.

Listing 13-9. Listing 13-8 in an Expression-Oriented Style

def phoneBanking (key: Int) : String = {
val result = key match {
case 1 => "Banking service"
case 2 => "Credit cards"
case 3 => "Speak to the customer executive"
}
return result
}

Listing 13-9 looks a lot more concise, but it can still be improved further. We can remove the intermediate result variable altogether from the phoneBanking method. Listing 13-10 shows the purely expression-oriented style.

Listing 13-10. Purely Expression-Oriented Listing 13-8

def phoneBanking (key: Int) : String = keymatch{
case 1 => "Banking service"
case 2 => "Credit cards"
case 3 => "Speak to the customer executive"
}

Listing 13-10 follows the expression-oriented approach. You can run the code in the REPL as shown:

scala> def phoneBanking(key: Int) : String = key match{
     | case 1 => "Banking service"
     | case 2 => "Credit cards"
     | case 3 => "Speak to the customer executive"
     | }
phoneBanking: (key: Int)String
scala>phoneBanking (3)
res8: String = Speak to the customer executive

Image Note  The key to using expressions is realizing that there’s no need for a return statement.

Focus on Immutability

In Java, mutability is the default. Variables are mutable unless they’re marked final. JavaBeans have getters and setters. Data structures in Java are instantiated, set, and passed along to other methods. Try changing the paradigm in your Scala code.

The first thing to do is use immutable collections classes by default. If you choose to use a mutable collections class, make a comment in your code as to why you chose mutability. There are times when mutable collections make sense. For example, in a method where you are building a List, using ListBuffer is more efficient, but don’t return the ListBuffer, return the List. This is like using a StringBuilder in Java but ultimately returning a String. So, use immutable collections by default, and use mutable data structures with a justification.

Use vals by default, and only use vars if there is a good reason that is justified by a comment. In your method, use val unless there’s going to be a significant performance hit. Using val in methods often leads to thinking recursively. Listing 13-11 shows a mutable implementation of a method that consumes all the lines from a BufferedReader:

Listing 13-11. A Mutable Implementation of read

def read1(in: java.io.BufferedReader): List[String] = {
varret: List[String] = Nil
varline = in.readLine
while(line!=null) {
ret ::= line
line = in.readLine
}
ret.reverse
}

The code in Listing 13-11 is readable but uses a couple of vars. Let’s rewrite the code without vars and see how we can use tail recursion to give us a while loop (see Listing 13-12):

Listing 13-12. Immutable Implementation of read

def read2(in: java.io.BufferedReader): List[String] = {
defdoRead(acc: List[String]):List[String] = in.readLinematch{
case null=> acc

case s => doRead(s :: acc)
}
doRead(Nil).reverse
}

Look ma, no vars. We defined the doRead method, which reads a line of input. If the line is null, we return the accumulated List. If the line is non-null, we call doRead with the accumulated List. Because doRead is in the scope of read2, it has access to all of read2’s variables. doRead calls itself on the last line, which is a tail call. The Scala compiler optimizes the tail call into a while loop, and there will only be one stack frame created no matter how many lines are read. The last line of read2 calls doRead with Nil as the seed value for the accumulator.

Using vals in your code makes you think about alternative, immutable, functional code. This small example demonstrates that removing vars leads to refactoring. The refactoring leads to new coding patterns. The new coding patterns lead to a shift in your approach to coding. This shift in approach yields transformative code that has fewer defects and is easier to maintain.

Keep Methods Short

Keep methods short. See whether you can code methods in a single line. If not a single line, see whether you can code them in a single statement. If you keep methods short, then the logic in each method is more obvious when you or someone else looks at the code. Let’s see how the previous code can be made into single statements as illustrated in Listing 13-13.

Listing 13-13. A Shorter Implementation of read

privatedef readLines(in:java.io.BufferedReader,
acc:List[String]): List[String] =
in.readLinematch {
case null => acc
case s => readLines(in,s :: acc)
}
defread3(in: java.io.BufferedReader): List[String] =
readLines(in,Nil).reverse

When you code Scala, try not to have a curly brace around the method body. If you can’t write your code this way, you have to justify to yourself why your method should exceed a single statement. Keeping methods short allows you to encapsulate a single piece of logic in a method and have methods that build upon each other. It also allows you to easily understand the logic in the method.

Use Options Instead of Null Testing

The first thing to do is ban null from any of your code. You should never return null from a method—ever, ever, ever. If you are calling Java libraries that may return null or throw an exception because of input problems, convert these to Options. We did this for parsing Strings to Ints. The pattern is basic: no nulls.

When you write code, ban null from your code. In the case of uninitialized instance variables, either assign a default value that is not null or, if there’s a code path where the variable could be used prior to initialization, use Option, and the default value becomes None. If there’s no logical value that can be returned from a method given legal input, the return type should be Option. The get method should never be called on an Option. Instead, Options should be unpacked using map/flatMap, the for comprehension, or pattern matching.

The first benefit using Option is the obvious avoidance of null pointer exceptions. The second benefit is a little more subtle. The use of Option and the transformative nature of mapping Options leads to a different style of approaching your code. The style is more transformative, more functional. The impact of repeatedly using immutable data structures will move your brain toward the functional side. You should familiar with null pointer exception in Java. For example, consider the Java method in Listing 13-14.

Listing 13-14. Java Method that Returns an Int

publicIntcomputeArea() { ... }

This computeArea method returns, as you might expect, the area of type Int, but it might return null, and you cannot tell just by looking at the method that it might return null. Because of this, the caller of the Java method is obliged to put null checks in his code and, if the caller is lucky and the method never actually returns null, the null checks merely clutter the caller’s code. Scala solves this problem by getting rid of null altogether and providing a new type for optional values, that is, values that may or may not be present by means of the Option class. Now we will show you how you can write computeArea method in Scala that may or may not return a value as shown in Listing 13-15.

Listing 13-15. ScalaMethod that May or May Not Returnan Int

defcomputeArea: Option[Int] = { ... }

The return type of the computeArea method is Option[Int] and merely by looking at this return type, the caller of computeArea method will know that it may not always return an Int. And to complete the picture, the computeArea method uses Some and None types to determine what to return as illustrated in Listing 13-16, for example, in one implementation fragment example of computeArea method.

Listing 13-16. Using Some and None

computeArea match {
  case Some(area) => ...
  case None => ...
}

Option, Some, and None used in this way is one of the several unique features of Scala that’s how that Scala is a state-of-the-art language. This also means that when a Scala function always returns a value, its return type is not an Option type but the type of the object that the method returns. If the Scala function never returns null, why is there a Null type in Scala? We take a brief pause here to let you envisage the answer. Good, that’s correct. Scala supports Null type for compatibility with Java.None is the counterpart to Some, used when you’re using Scala’s Option class to help avoid null references.

Refactor Mercilessly

In the beginning, you can write your Scala code as you would your Java code. It’s a great place to start. Then, start applying the idioms you learned in the previous sections in this chapter. In Listing 13-17 we start with the imperative code.

Listing 13-17. Imperative Code

def validByAge(in: List[Person]): List[String] = {
varvalid:List[Person] = Nil
for(p<- in){
if (p.valid) valid = p :: valid
}
deflocalSortFunction(a: Person,b:Person) = a.age < b.age
val people = valid.sort(localSortFunction _)
varret: List[String] = Nil

for(p<- people){
ret = ret ::: List(p.first)
}

returnret
}

Turn your vars into vals as illustrated in Listing 13-18.

Listing 13-18. Transforming vars to vals

def validByAge(in: List[Person]): List[String] = {
val valid:ListBuffer[Person] = newListBuffer // displacedmutability
for(p<- in){
if (p.valid) valid += p
}
deflocalSortFunction(a: Person,b:Person) = a.age < b.age
val people = valid.toList.sort(localSortFunction _)
val ret:ListBuffer[String] = newListBuffer
for(p<- people){
ret += p.first
}
ret.toList
}

Turn your mutable data structures into immutable data structures as illustrated in Listing 13-19.

Listing 13-19. Transforming Mutable Data Structures into Immutable Data Structures

def validByAge(in: List[Person]): List[String] = {
val valid = for (p<- in if p.valid) yieldp
deflocalSortFunction(a: Person,b:Person) = a.age < b.age
val people = valid.sort(localSortFunction _)
for(p<- people)yield p.first
}

Make your method into a single statement as illustrated in Listing 13-20.

Listing 13-20. Making a Method into a Single Statement

def validByAge(in: List[Person]): List[String] =
in.filter(_.valid).
sort(_.age < _.age).
map(_.first)

While you can argue that this is too terse, we can refactor another way as seen in Listing 13-21.

Listing 13-21. Refactoring in Another Way

def filterValid(in: List[Person]) = in.filter(p=> p.valid)
def sortPeopleByAge(in: List[Person]) = in.sort(_.age < _.age)
def validByAge(in: List[Person]): List[String] =
(filterValid_ andThen sortPeopleByAge_)(in).map(_.name)

Either of the refactoring choices you make, the business logic of your code is a lot more visible. The refactoring also moves you toward thinking about the transformations in your code rather than the looping constructs in your code.

Compose Functions and Compose Classes

In the previous example, we composed filter Valid and sortPeopleByAge into a single function. This function is the same as shown in Listing 13-22.

Listing 13-22. Function Composition

(in: List[Person]) =>sortPeopleByAge(filterValid(in))

However, the composition of the two functions results in code that reads like what it does. We started by turning our methods into single statements. This makes testing easier and makes the code more readable. Next we compose a new function by chaining together the two functions. Functional composition is a later stage Scala-ism, but it results naturally from making methods into single statements.

In Chapter 7, we explored how Scala’s traits can be composed into powerful, flexible classes that are more type-safe than Java classes. As you evolve your Scala coding skills and begin to refactor classes rather than methods, start looking for common methods across your interfaces and traits. Move methods from concrete classes into traits. Soon, you’ll likely find that many of your classes have little in them other than the logic that is specific to that class and the vals that are needed to evaluate that logic. Once you reach this level in your coding, you will likely find that your traits are polymorphic, that your traits represent logic that can be applied to a contained type, and then you can feel secure that your mind has completely warped into thinking Scala.

Once you’re thinking Scala or thinking that you’re thinking Scala, you might want to take the next advanced steps toward the goals of best practices. The next section provides some constructs for design patterns in Scala.

Scala Design Patterns

The design pattern is regarded as a reusable solution to a commonly occurring design problem in object-oriented paradigm, traditionally popularized in Java world. Design patterns, however, are not programming language agnostic, that is to say, a design pattern in one programming language could be a language primitive in another programming language. The programming language influences and determines the need of using design patterns and even has a native support for the concept embodied in a pattern. One classic example of this is the way the Singleton design pattern is implemented in Java and the way in which the Singleton design pattern is a native feature of Scala.

Singleton

The Singleton pattern ensures that a class has only one instance, and provides a global point of access to it. Listing 13-23 shows, a typical Java implementation that involves a private static field, a private constructor, and a factory method.

Listing 13-23. A Singleton in Java

public  class  JavaSingleton{
private staticJavaSingleton  instance  = null ;
private JavaSingleton() { }
public staticgetInstance ( ) {
if( instance == null) {
instance = new JavaSingleton();
}
return instance ;
}
}

In Listing 13-23, instead of constructing the object with a new expression, the method getInstance is invoked. Listing 13-24 shows how a language construct in Scala provides direct support for the concept of a Singleton pattern.

Listing 13-24. A Singleton in Scala

object ScalaSingleton{}

In Scala, an object is basically a singleton object. There is only instance of this object in any given program and it can be passed around.

Factory Method

The Factory Method pattern is used to encapsulate the required functionality for selection and instantiation of the implementation class, into a method called Factory Method. A Factory Method pattern removes the tight coupling between the client class and the service provider implementation classes by instantiating a needed class and returning its super type.

Listing 13-25. A Factory Method Pattern in Java

public interface Vehicle {}

private class Car implements Vehicle {}

private class Bike implements Vehicle {}

public class VehcileFactory {
    public static Vehicle createVehicle(String type) {
        if ("bike".equals(type)) return new Bike();
        if ("car".equals(type)) return new Car();
        throw new IllegalArgumentException();
    }
}

VehicleFactory.createVehicle("car");

Listing 13-26 illustrates the Factory Method Pattern in Scala.

Listing 13-26. A Factory Method Pattern in Scala

trait Vehcile
private class Car extends Vehcile
private class Bike extends Vehicle

object Vehicle
  def apply(type: String) = kind match {
    case "car" => new Car()
    case "bike" => new Bike()
  }
}
Vehicle("car")

The Factory Method shown in Listing 13-26 is defined in the companion object—a singleton object with the same name, defined in the same source file.

Strategy

The Strategy pattern allows an algorithm to be selected at runtime. The Strategy pattern defines a family of encapsulated algorithms and allows the algorithm to vary loosely coupled from clients that use the algorithm. In Java, the Strategy pattern is implemented by creating a hierarchy of classes that inherit from a base interface as shown in the Listing 13-27.

Listing 13-27. Strategy Pattern in Java

public interface Strategy {
    int operation(int a, int b);
}

public class Add implements Strategy {
    public int operation(int a, int b) { return a + b; }
}

public class Multiply implements Strategy {
    public int operation(int a, int b) { return a * b; }
}

public class Context  {
    private final Strategy strategy;

    public Context(Strategy strategy) { this.strategy = strategy; }

    public void execute(int a, int b) { strategy.operation(a, b); }
}

new Context(new Multiply()).execute(5, 5);

Listing 13-28 illustrates Strategy pattern in Scala.

Listing 13-28. Strategy Pattern in Scala

type Strategy = (Int, Int) => Int

class Context(operation: Strategy) {
  def execute(a: Int, b: Int) { operation(a, b) }
}

val add: Strategy = _ + _
val multiply: Strategy = _ * _

new Context(multiply).execute(5, 5)

As you can see in Listing 13-28, we use first-class functions in Scala to design the strategy pattern.

Template Method

The Template Method pattern consists of an abstract class that defines some process in terms of abstract sub-methods. Listing 13-29 illustrates a Template Method pattern in Java.

Listing 13-29. Template Method Pattern in Java

public abstract class Template{
public void process(){
subMethodA();
subMethodB();
}
protected abstract void subMethodA();
protected abstract void subMethodB();
}

To use it, extend the Template and implement the abstract subMethods.

Listing 13-30 illustrates the Template Method pattern in Scala.

Listing 13-30. Template Method Pattern in Scala

def process( operation
subMethodA: () => Unit,
subMethodB: () => Unit) =() => {
subMethodA()
subMethodB ()
}

As you can see, the Listing 13-30 passes the subMethods into a Function Builder. To use the Template Method pattern in Scala, we no longer need to define subMethods and subclasses. Instead of defining subMethods we use higher order functions; and instead of defining subclasses, we use function composition.

Adapter

The Adapter pattern allows the classes with incompatible interfaces to work together by converting the interface of a class into an expected interface. The Adapter pattern does this by defining a wrapper class around the object with the incompatible interface. Listing 13-31 illustrates the Adapter pattern using Java.

Listing 13-31. Adapter Pattern Using Java

public interface ServiceProviderInterface {
    void service(String property);
}
public final class ServiceProviderImplementation{
    void service(Stringtype, String property) { /* ... */ }
}

public class Adapter implements ServiceProviderInterface {
    private final ServiceProviderImplementation impl;

    public Adapter (ServiceProviderImplementation impl) { this.impl = impl; }

    public void service(String property) {
impl.service(TYPEA, property);
    }
}
ServiceProviderInterface service = new Adapter(new ServiceProviderImplementation ());

Scala provides a built-in concept of interface adapters, expressed as implicit classes. Listing 13-32 shows the Adapter pattern using Scala.

Listing 13-32. Adapter Pattern Using Scala

trait ServiceProviderInterface {
  def service(message: String)
}

final class ServiceProviderImplementation {
  def service(type: String, property: String) { /* ... */ }
}

implicit class Adapter(impl: ServiceProviderImplementation) extends ServiceProviderInterface {
  def service(property: String) { impl.service(TYPEA, property) }
}

val service: ServiceProviderInterface = new ServiceProviderImplementation ()

When the expected type of expression is ServiceProviderInterface, but a ServiceProviderImplementation instance is used, Scala compiler automatically wraps the ServiceProviderImplementation instance in the adapter class.

Summary

Designing and building complex computer software is a serious business. Our livelihoods, and increasingly our whole society, depend on the stability and flexibility of our interconnected computer systems. Our cars and our banks and our grocery stores and our hospitals and our police departments all work better because they are interconnected by computer systems. Those systems run on the software that we write.

We hope that you have enjoyed the journey and are already thinking about new ways to reason about designing software and writing code. We want to end this journey by talking a bit about architecture.

Architecture is very important in overall system performance and team performance. Scala has a lot of the tools that allow for much better architectural decisions. It’s kind of a Zen and the Art of Motorcycle Maintenance thing—you use the patterns that your language and its libraries make easiest. Scala makes it easier than Java or Ruby for coders to implement architecturally solid designs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.80.36