left-icon

Scala Succinctly®
by Chris Rose

Previous
Chapter

of
A
A
A

CHAPTER 3

Expressions and Functions

Expressions and Functions


Expressions

An expression is a series of variables, values, operators, and literals we use to compute. For instance, a mathematical expression such as 100+1, or 99*(89+3). There are several different types of expressions in Scala—arithmetic expressions, Boolean or logical expressions, and string expressions. Code Listing 12 shows some examples of using different types of expression.

Code Listing 12: Basic Expressions

object MainObject {

      def main(args: Array[String]): Unit = {

            78 * 9 // Integer expression

            41.3 + 99.7 // Double expression

            78.5f * (2.1f - 7.9) // Float expression

            89 < 78 || ((29 & 1) == 0) // Boolean expression

            ("Hello" + " " + "world") * 3 // String expression

      }

}

In Code Listing 12, the expressions are not used for anything—they evaluate to some value, but we are not using the value, so the Scala IDE will give us warnings such as: “A pure expression does nothing...” Notice that we can use many of the common arithmetic operators, such as +, -, *, and /, for working with numerical values Int, Double, and Float.

Table 1 lists many of the available operators in Scala and provides some details as to how they are used. Many will look familiar if you are familiar with Java or other C-based languages, but Scala allows us to define arbitrary meanings to operator symbols, so that when we look into classes—and particularly lists and other collections—we will see that there are many more operators defined in Scala.

Table 1: Operators in Scala

Op

Name

Type

Example

Description

+

Addition

Arithmetic

someVar+3

Adds numerical values and concatenates strings

+

Unary positive

Arithmetic

+someVariable

Unary positive is useless, use 3 instead of +3

-

Subtraction

Arithmetic

someVar-10

Subtracts the second operand from the first

-

Unary Negative

Arithmetic

-myVariable

Negates numerical values: for int this means 2’s complement, for floats it flips the sign bit

*

Multiplication

Arithmetic

28.3*45.67

Multiplies two numerical values

/

Division

Arithmetic

28.3/45.67

Divides the first operand by the second, returns the quotient

%

Modulus

Arithmetic

45%3

Divides the first operand by the second, returns the remainder after division

==

Equal to

Relational

myVar==100

Determines if two operands are equal

!=

Not Equal to

Relational

myVar != 100

Determines if two operands are not equal

>

Greater than

Relational

someVar > 90

Determines if the first operand is greater than the second

<

Less than

Relational

someVar < 90

Determines if the first operand is less than the second

>=

Greater or Equal to

Relational

100.5 >= 90.3

Determines if the first operand is greater than or equal to the second

<=

Less or Equal to

Relational

100.05 <= 90.3

Determines if the first operand is less than or equal to the second

&

Bitwise AND

Bitwise

someVar & someMask

Performs the bitwise AND operation between corresponding bits of two operands

|

Bitwise OR

Bitwise

someVar | someOtherVar

Performs the bitwise OR operation between corresponding bits of two operands

^

Bitwise XOR

Bitwise

someVar ^ -1

Performs the bitwise exclusive OR between corresponding bits of two operands

~

Bitwise complement

Bitwise

~someVariable

Flips all the bits of the operand so 0’s become 1’s and vice versa

>>

Arithmetic Shift right

Bitwise

someInt>>2

Shifts all the bits of the input right by the amount specified in the second operand (i.e. divides the operand by 2 to the power of the second operand)

<<

Bitwise shift left

Bitwise

SomeInt<<2

Shifts all the bits of the left by the amount specified in the second operand (i.e. multiplies the operand by 2 to the power of the second operand)

>>>

Bitwise Shift Right

Bitwise

someInt>>>2

Same as shift right, except 0’s come in on the left instead of 1’s. Use >> for quick division of signed integers, and >>> for shifting nonsign values or bit fields

&&

Logical AND

Logical

(someExpression)&&(someOtherExpression)

Performs a logical AND between two Boolean expressions, used to form logical expressions

||

Logical OR

Logical

(someExpression)||(someOtherExpression)

Performs a logical OR between two Boolean expressions, used to form logical expressions

!

Logical NOT

Logical

!someBoolExpression

Complements a Boolean value, used to form logical expressions

=

Assignment Equals

Assignment

someVar = 100

Used to assign a value to a variable

+=

Addition Assignment

Assignment

someVar += 10

Used to add, then assign a value, someVar+=10 means add 10 to someVar

-=

Subtraction Assignment

Assignment

someVar -= 10

Used to subtract, then assign a value, someVar -= 10 means subtract 10 from someVar

*=

Multiplication Assignment

Assignment

someVar *= 10

Used to multiply, then assign a value, someVar *= 10 means multiply someVar by 10

/=

Division Assignment

Assignment

someVar /= 10

Used to divide, then assign a value, someVar /= 10 means divide someVar by 10

%=

Modulus Assignment

Assignment

someVar %= 10

Used to get the remainder after division of someVar and 10

>>=

Shift Right Assignment

Assignment

someVar >>= 2

Used to shift then assign a value, someVar >>= 2 means shift someVar right two bits

<<=

Shift Left Assignment

Assignment

someVar <<= 2

Used to shift then assign a value, someVar <<= 2 means shift someVar left by two bits

>>>=

Shift Right Zero Fill Assignment

Assignment

someVar >>>2

Same as >>, only fills the high-order bits with zeros

&=

AND Assignment

Assignment

myVariable &= 7

Used to perform bitwise AND then assign a value, someVar &= 7 means AND the bits of someVar with 7

|=

OR Assignment

Assignment

myVariable |= 7

Used to perform bitwise OR then assign a value, someVar |= 7 means OR the bits of someVar with 7

^=

XOR Assignment

Assignment

myVariable ^= 7

Used to perform exclusive OR then assign a value, myVariable |= 7 means XOR the bits of myVariable with 7

When we form expressions, we can do so using parentheses to override the precedence of the operators. Scala is aware of the normal precedence of arithmetic operators, and employing parentheses is often necessary, especially when we are not sure of the exact precedence or when we want to write clear code. All expressions within parentheses are evaluated first. Brackets can be used in any type of expression, which means we can use them when concatenating strings and when joining Boolean expressions, logical statements, and arithmetic expressions.

The arithmetic that computers perform is always finite. That means addition will only give the correct answer so long as there is no overflow. For example, a byte storing 127+1 does not equal 128 because 128 is outside the range of a byte—it actually wraps around to -128. And, as we’ve seen, floating-point values are not able to store many exact fractions. They often rely on rounding.

Boolean operators are things like <, >, ||, and ==. They allow us to form logical statements. The example of a Boolean expression in Code Listing 12 means “89 is less than 78 OR 29 ends in a 0.” Neither of these statements is true, which means this line will evaluate to false.

The final example in Code Listing 12 is a string expression. We can add strings together using the + operator. We can also use the multiplication operation *, which will add the same string multiple times. The line “("Hello" + " " + "world") * 3” will evaluate to “Hello worldHello worldHello world,” because we multiply the string “Hello”+ “ ” + “world” by three.

Creating and calling functions

Functions begin with the def keyword, which is short for define. A function is similar to any other data type, except that it is evaluated when it is used rather than when it is defined. Functions return values and take parameters, and we can use them to enable code reuse when we have some expression of sequence of statements that we need to execute many times.

After the def keyword, we supply an identifier for the function followed by the parameter list in brackets—for example, def someFunction(parameters). The parameter list consists of a comma-separated list of variables that are to be passed to the function in order for it to compute. For instance, def someFunction(myInt: Int) and def someTwoParameterFunction(a: Float, b: Double). If there are no parameters, we use () as the parameter list or leave the parameters off, such as with def someFunction() and def someFunction. Code Listing 13 shows a simple Hello World function that takes no parameters and returns nothing.

Code Listing 13: Function with No Params or Return

object MainObject {

      def main(args: Array[String]): Unit = {

      

            // Define a function with no parameters:

            def helloFunction {

                  println("Hello world!")

            }

            // Calling a function with no parameters:

            helloFunction

      

      }

}

After the parameter list (if there is one), we specify the return type. The return type begins with a colon and, if we are using object-oriented programming, is followed by a data type such as Int or Double or some class. This is the value that the function computes and returns to the caller.

In Scala, we can nest functions. In Code Listing 13, the function called helloFunction is actually nested inside the body of the main method. This means that it is local to the main method and cannot be called outside of main.

Finally, we can supply an assignment operator, =, and specify the body of the function. If the body of the function is only a single statement, we do not need to enclose the body of the function with { and }. Code Listing 14 shows a complete example of defining and calling a function that takes parameters and returns a value.

Code Listing 14: Creating and Calling a Function

object MainObject {

      // Compute the square root of x using

      // the Babylonian method:

      def sqrt(x: Double): Double = {

            if(x < 0)

                  -1 // Return -1 as an error value if x < 0

            var q: Double = x / 2

            for(i <- 1 to 20) {

                  q = (q + x / q) / 2

            }

             // return q // We can return using the 'return' keyword

             q // Placing q by itself is the same as return q.

      }

      def main(args: Array[String]): Unit = {

            println("Square root of 61 is " + sqrt(61))

      }

}

Code Listing 14 shows the code used to compute the square root of a Double using the Babylonian method. There is a built-in square root function that will compute the root much faster, but Code Listing 14 illustrates of how to create and call a simple function. The code also uses a for loop, which is a control structure, and we will look at control structures in a moment.

Note: When we return from a function, Scala will assume that the final evaluated statement is meant to be returned. Code Listing 14 shows that the final statement to be evaluated is “q,” therefore q will be returned from sqrt. If you prefer, you can supply a return statement to explicitly return q, but there is often no requirement for the return keyword.

Note: The return type of Unit means the same as void in other languages. It means that the function does not return a value.

Calling a function in Scala is similar to doing so in other languages. We supply the name of the function followed by the parameters enclosed in brackets, such as sqrt(56).

Named arguments

We can use named arguments as well. Named arguments allow us to supply the arguments in a different order than the function specifies, or we can specify the arguments by name if that makes the code clearer. Code Listing 15 shows an example of using named arguments. Notice that in the final example, printInfo(age = 51, name = “Claire”), the arguments do not appear in the same order as they appear in the definition of the function printInfo. If you name one argument when calling, you must name them all.

Code Listing 15: Named Arguments

object MainObject {

      def main(args: Array[String]): Unit = {

            

            // Specify a function which takes several parameters:

            def printInfo(name: String, age: Int) {

                  println("Patient Name: " + name + " Age: " + age)

            }

            

            // Call function without named arguments:

            printInfo("Chris", 35)

            

            // Some examples using named arguments:

            printInfo(name = "Dennis", age = 190)

            printInfo(age = 51, name = "Claire")

      }

}

Default parameters

We can specify default parameters in our function definitions, which means that if the caller does not supply a value for the parameter, it will be set to the default value.

Code Listing 16: Default Values

object MainObject {

      def main(args: Array[String]): Unit = {

            

            def printInfo(name: String = "No name", age: Int = 0) {

                  println("Patient Name: " + name + " Age: " + age)

            }

            // Both parameters will take default values:

            printInfo()

            // Age will take default value:

            printInfo("Simpson")

            

            // Name will take default value:

            printInfo(age = 65)

            

      }

}

Code Listing 16 shows some examples of using default values for function parameters. First, in the function’s parameters list, we supply the default values for any or all of the parameters by specifying some literal after an equals. Then, when we call the function, we can supply any or all of the default values of some specific value or we can leave them to default. Notice that we can use named arguments in conjunction with default values, as in the function call printInfo(age=65), which will call the function with the name argument defaulting to “No name”.

Functions as data

Functions are just data, too. This point is not often clear when programming high-level languages, but it is literally true—everything the computer does is just a bunch of 1’s and 0’s. We can easily create a variable, point it to a function, then call that function by using our variable (see Code Listing 17). This is something like a function pointer, but the syntax in Scala is intuitive and easy to read.

Code Listing 17: Functions as Data

object MainObject {

      // Simple function that doubles the input:

      def doubleInput(i: Int): Int = {

            i + i

      }

     

      def main(args: Array[String]) {

            // Point val c to doubleInput, the _ means any

            val c = doubleInput _

           

            // Call the function doubleInput:

            println("Double 6 is " + c(6))

      }

}

In Code Listing 17, note the assignation of the function doubleInput to the val called c. We use the name of the function followed by the underscore. The underscore means all inputs; it is a wild card symbol. Normally, when we assign a value, we would need to specify something like doubleInput(8), but if we use the underscore to mean any input, we’ll get the variable c pointing to the function itself, and we can call c using the standard syntax for calling doubleInput.

Variable parameters

With the final parameter to a function, we can indicate that there might be a variable number of arguments. This is useful when we want to sum a number of values and we do not necessarily know how many there will be. When we use a variable number of arguments, the argument must be the final value in the parameter list.

Code Listing 18: Variable Arguments

object MainObject {

      def main(args: Array[String]) {

            

            // Function with variable argument list:

            def minimum(args: Int*): Int = {

                  

                  // Base case, return 0 if there's no

                  // arguments:

                  if(args.length == 0)

                        return 0

                  // Otherwise, find the minimum:

                  var min = args(0)

                  for(i <- args) {

                        if(i < min)

                              min = i

                  }

                  

                  // Return the smallest number from the list:

                  min

            }

            

            // Call minimum

            println("Minimum: " + minimum(13, -30, 2, -17, 37))

      }

}

In Code Listing 18, the function called minimum can take any number of Int parameters. The syntax used for this mechanism defines the final parameter of the function’s parameter list with a *, such as (args: Int*). Notice we call the function in the usual way, but the number of Int parameters can be anything at all. Code Listing 18 uses if statements. For loops, we’ll look at control structures in the next section.

The args parameter becomes an Array. We will look at arrays later. Note also that there is a built-in function for lists called min that we could have called to get the minimum with less code.

Evaluation of functions

Another way to think about functions in Scala is to consider that a function is an expression that is evaluated when it is used rather than when it is initially set. Imagine we have a variable called n set to some number. If we define a second variable, x, and we point it to n, the value of x will be determined when the assignment occurs.

But, imagine if we have another variable, y. In that case, we can use def to assign the value of n to the variable y. The difference is that the value of y will be evaluated when it is used rather than when y is defined. So, if the value of n changes, the value of y will also change. See Code Listing 19 for a basic example of this mechanism.

Code Listing 19: Def Evaluation

object MainObject {

      def main(args: Array[String]): Unit = {

            // Define a variable n:

            var n = 90

            // Assign the value of n to a new variable, x

            var x = n

            

            // Assign the expression 'n' to a function, y

            def y = n

            

            // Print out the values of our variables:

            println("Before changing 'n':")

            println("The value of e is " + n)

            println("The value of x is " + x)

            println("The value of y is " + y)

            

            // Change the value of the 'n' variable:

            n += 1

            

            // Print out the values of our variables again:

            println("After changing 'n':")

            println("The value of e is " + n)

            println("The value of x is " + x)

            println("The value of y is " + y)

      }

}

In Code Listing 19, the output shows that after the variable e has changed, the value that x points to is still the original value of e, even though x is a var and e has changed. This means that the value of a variable is evaluated when the variable is defined—x points to the val of e when it is defined, and changing e has no effect on x after x’s definition. However, the value of y changes when we change the value of e because we used def to define y as a function. This means that the value of a function is evaluated when we use the function. The output from running the program from Code Listing 19 is presented in Code Listing 20.

Code Listing 20: Def Evaluation Output

Before changing 'e':

The value of e is 90

The value of x is 90

The value of y is 90

After changing 'e':

The value of e is 91

The value of x is 90

The value of y is 91

Code Listing 20 shows only a very basic use of a function, and in this example our variable (or function) called y is little more than pointer to the value of e. But we can also define our function, y, to include a complex expression or even a code block with many lines of code.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.