In the second installment of this three-part tutorial, I explained the difference between mutable and immutable variables in Scala, and I showed the unusual way in which Scala operators can be modified. In this final article of the series, I focus on the way Scala works with expressions and pattern matching, the powerful implicit conversions, and Scala's smart way of avoiding problematic nulls. Finally, I dive into the powerful ways in which you can use Scala traits.
Working with Expressions Instead of Statements
Programming languages provide statements that execute code and don't return a value. Programming languages can also evaluate expressions that return values. In order to evaluate an expression, code must be executed; therefore, both statements and expressions require the execution of code. So the real difference between a statement and an expression is that the latter returns a value.
Scala provides an if
clause, and you can build if
blocks. In C#, the if
clause allows you to execute different statements according to the two possible results of evaluating a Boolean expression: true
or false
. In Scala, an if
block is an expression, so the if
clause evaluates a Boolean expression and returns one expression or another depending on the result of the evaluations: true
or false
. An if
block in Scala evaluates to a value.
As I explained in my previous article, the Scala Console in the Scala IDE makes it really easy to evaluate expressions and check the results. Let's move straight to the code and run some examples in the Scala Console. Enter the following expressions after the scala>
prompt, and the Scala Console will provide the results.
scala> val myResult = 300 if (myResult > 300) "More than 300" else "Less than or equal to 300" myResult: Int = 300 scala> res0: String = Less than or equal to 300
As you might notice, the Scala if
expression works the same way as the ?:
syntax in C#. The following lines of code use the ?:
syntax to produce a similar result in C#. In addition, it would be necessary to return myString
in the method in which the code is inserted.
const int myResult = 300; var res0 = myResult > 300 ? "More than 300" : "Less than or equal to 300";
In Scala, there is no need for the ?:
syntax because of the way the if
expression works; therefore, you will always use the if
expression whenever you want to evaluate a Boolean expression. This makes code easier to read and reduces unnecessary boilerplate.
Working with Pattern Matching
The following C# code shows a simple example of a method that performs a pattern match and returns a message string based on the received parameter:
public string CreateMessage(int code) { var message = string.Empty; switch (code) { case 100: message = "One hundred"; break; case 200: message = "Two hundred"; break; case 300: message = "Three hundred"; break; default: message = "Unknown value"; break; } return message; }
The following Scala code defines a CreateMessage
method for an ErrorHandler
object that includes a simple pattern match. The pattern match uses the match
keyword and defines the only expression for the method that returns a String
. Notice that the code is simpler, easier to read, and doesn't require a temporary variable to return the result of the pattern match. As explained with the if
expression, the pattern match is an expression and returns a String
.
object ErrorHandler { def CreateMessage(code: Int): String = code match { case 100 => "One hundred" case 200 => "Two hundred" case 300 => "Three hundred" case _ => "Unknown value" } }
The code is concise and expressive. The underscore works as a wildcard, so if the other case expressions don't match the value for code
, the pattern match will reach the case _
expression and will return "Unknown value."
The case _
is the equivalent to default
in C#. In Scala, you must use =>
for pattern match as in the example code, but you can also use =>
for defining closures.
The following lines show different ways of calling the CreateMessage
method and the results in the Scala Console. Notice that you can call the method without using either the dot (.) or the parenthesis.
scala> ErrorHandler.CreateMessage(100) res0: String = One hundred scala> ErrorHandler CreateMessage 200 res1: String = Two hundred scala> ErrorHandler CreateMessage 450 res2: String = Unknown value
Working with the scala.Option Class Instead of null
Scala provides the scala.Option
class in the standard library as a container of something or nothing, thereby, discouraging the use of null. Two elements in Scala extend scala.Option
:
Some[T]
is a class that represents some existing values of typeT
.None
: is acase
object that represents nonexistent values. It's an empty container.
The following lines of code show a new version of the sample CreateMessage
method defining the code parameter as an Option[Int]
; thus, the code might have either a Some[Int]
container or the None
empty container. The pattern match works with the result of code.getOrElse(0)
. Because the code
parameter is defined as an Option[Int]
, it provides the getOrElse
method defined in Option
, which retrieves the existing value in the container or returns the value received as a parameter when the Option
is representing an empty container (None
).
object ErrorHandler { def CreateMessage(code: Option[Int]): String = code.getOrElse(0) match { case 100 => "One hundred" case 200 => "Two hundred" case 300 => "Three hundred" case _ => "Unknown value" } }
The following lines show how you can define a mutableCode
variable as an Option[Int]
and initialize it with an empty container (None
). When you call the CreateMessage
method with mutableCode
initialized to None
, code.getOrElse(0)
will return 0
, so the result will be "Unknown value"
:
scala> var mutableCode : Option[Int] = None mutableCode: Option[Int] = None scala> ErrorHandler CreateMessage mutableCode res0: String = Unknown value
The next lines change the value of mutableCode
to a container that includes the Int
value 100
, Some(100)
. This time, when you call the CreateMessage
method with mutableCode
initialized to Some(100)
, code.getOrElse(0)
will return the contained Int
value, 100
; therefore, the result will be One hundred
.
scala> mutableCode = Some(100) mutableCode: Option[Int] = Some(100) scala> ErrorHandler CreateMessage mutableCode res1: String = One hundred
Option
also provides a get
method that attempts to access the value stored in the container and doesn't require any parameters. If you call get
for an empty container (None
), it throws an exception. According to your needs, you can use call get
or getOrElse
to retrieve the value in the container.
Named and Default Arguments
Scala 2.8 introduced two new features related to arguments that are similar to their counterparts in C#:
- Named arguments, also known as named parameters. You can specify the arguments based on the parameter names instead of the parameters positions.
- Default arguments, also known as default parameters. You can specify default values for method parameters.
A simple example will be enough to show you the Scala syntax for default arguments. The following lines define default values for the Point3D
class parameters and for the move
method.
class Point3D(val x: Double = 0, val y: Double = 0, val z: Double = 0) { def move(dx: Double = 2, dy: Double = 1, dz: Double = 0.5) : Point3D = new Point3D(x + dx, y + dy, z + dz) }
This way, you can create a new instance of Point3D
without parameters, and the initial values for x
, y,
and z
will be 0:
val myPoint3D = new Point3D()
You can use named parameters to specify the value for just the z
parameter, and the values for the variables will be 0 for x
, 0 for y
, and 10 for z
.
val myPoint3D = new Point3D(z = 10)
You can call the move
method without parameters and it will use the default values:
myPoint3D move()
Implicit Conversions with Tuples
Scala supports tuples that combine a fixed number of elements, which you can pass around as a whole. A tuple can include elements with different types. The following code defines a tuple with an Int
, and two strings using the shortcut that enables you to create a tuple by enclosing the elements in parentheses.
val tupleWith3Elements = (10, "String1", "String2")
Because the previous line defines a tuple with three elements, the actual type of the tuple will be Tuple3[Int, String, String]
. The following line is equivalent to the previous one, but it does not use the nice syntactic sugar:
val tupleWith3Elements = new Tuple3(10, "String1", "String2")
To access the elements of the tuple, you can use the different methods that start with an underscore (_) and continue with the element number, starting in 1. For example, tupleWith3Elements._1
returns 10,
and tupleWith3Elements._2
returns "String1
," and so on.
As you might expect, if you create a tuple with two String
elements, the type of the tuple will be Tuple2[String, String]
. If you create a tuple with four Int
elements, the actual type of the tuple will be Tuple4[Int, Int, Int, Int]
.
A Tuple3[Double, Double, Double]
would be good for holding the three values (x
, y
, and z
) of Point3D
. In addition, a Tuple3[Int, Int, Int]
would be useful when the three values are integers. If someone asks you to allow the sum (+) method defined in the Point3D
class to work with a Point3D
, a Tuple3[Double, Double, Double]
, or a Tuple3[Int, Int, Int]
, your first idea might be to create three sum (+
) methods each with a different type in the parameter. However, if you do this, you will be creating three blobs of very similar code. Scala provides an elegant feature that enables you to use a single method and automatic type conversions to reduce this duplicate code: implicit conversions. With the usage of implicit conversions, you need only define a single piece of code for the sum (+
) method in the Point3D
class, and then declare the implicit conversions from the different tuples to Point3D
. The following code shows the sum (+
) method that receives a Point3D
as a parameter and returns a new Point3D
with the result of the sum operation:
class Point3D(val x: Double = 0, val y: Double = 0, val z: Double = 0) { def +(that: Point3D) : Point3D = new Point3D(x + that.x, y + that.y, z + that.z) }
The following code defines a new Point3DImplicits
object with two implicit conversion methods that start with the implicit
keyword and convert from Tuple3[Double, Double, Double]
to Point3D
and from Tuple3[Int, Int, Int]
to Point3D
.
object Point3DImplicits { implicit def Tuple2Point3D(value : Tuple3[Double,Double, Double]) = new Point3D(value._1,value._2, value._3) implicit def IntTuple2Point3D(value : Tuple3[Int, Int, Int]) = new Point3D(value._1,value._2, value._3) }
Now, you just have to make sure you add the following line to import the Point3DImplicits
methods so that Scala can perform the necessary type conversions:
import Point3DImplicits._
If you write the following lines, Scala won't find a sum (+
) method that accepts a Tuple3[Int, Int, Int]
, but it will find the method defined in Point3DImplicits
that can perform the conversion from Tuple3[Int, Int, Int]
to Point3D
. Scala will use that conversion method and, once it has the Tuple3[Int, Int, Int]
converted to a Point3D
, Scala will use it to call the sum (+
) method. The same thing happens when you call the sum (+
) method with a Tuple[Double, Double, Double]
.
val myIntTuple = (5, 10, 15) val myPoint3D = new Point3D(10, 8, 3) val myNewPoint3D = myPoint3D + myIntTuple val myDoubleTuple = (5.2, 10.1, 15.4) val myNewPoint3D2 = myPoint3D + myDoubleTuple
Obviously, there is an overhead introduced when Scala has to apply the implicit conversion, so it isn't a good idea to abuse the definition of implicit methods. However, implicit conversions are great for reducing the unnecessary duplication of code that performs the same action.