O'Reilly logo

Learning Scala by Jason Swartz

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Functions

Functions are the core building blocks of reusable logic. Of course, you probably already knew that, because nearly all other languages also have functions (or methods, the object-oriented version of functions). Devoting an entire chapter to a concept common across languages may thus seem odd, but to Scala and other functional programming languages functions are very important.

Functional programming languages are geared to support the creation of highly reusable and composable functions and to help developers organize their code base around them. Much like a Unix power user will compose multiple single-purpose tools into a complex piped command, a functional programmer will combine single-purpose function invocations into chains of operations (think Map/Reduce). A function that was written with a simple purpose (e.g., to double a number) may be picked up and applied across a 50,000-node list, or given to an actor to be executed locally or in a remote server.

In Scala, functions are named, reusable expressions. They may be parameterized and they may return a value, but neither of these features are required. These features are, however, useful for ensuring maximum reusability and composability. They will also help you write shorter, more readable, and more stable applications. Using parameterized functions you can normalize duplicated code, simplifying your logic and making it more discoverable. Testing your code becomes easier, because normalized and parameterized logic is easier to test than denormalized logic repeated throughout your code.

Even greater benefits may come from following standard functional programming methodology and building pure functions when possible. In functional programming a pure function is one that:

  • Has one or more input parameters
  • Performs calculations using only the input parameters
  • Returns a value
  • Always returns the same value for the same input
  • Does not use or affect any data outside the function
  • Is not affected by any data outside the function

Pure functions are essentially equivalent to functions in mathematics, where the definition is a calculation derived only from the input parameters, and are the building blocks for programs in functional programming. They are more stable than functions that do not meet these requirements because they are stateless and orthogonal to external data such as files, databases, sockets, global variables, or other shared data. In essence, they are uncorruptible and noncorrupting expressions of pure logic.

On the other hand, it can be really hard to write useful applications that don’t affect files, databases, or sockets, so it is rare to write one that only contains pure functions. Instead of trying to find a way to exlusively use pure functions in their applications, Scala developers will generally compromise and seek ways to reduce the number of unpure functions. Keeping unpure functions clearly named and organized in such a way that they can be easily identified versus pure functions is a common goal of modularizing and organizing Scala applications.

With this in mind, let’s learn how to write functions in Scala. Because Scala’s function definitions are flexible, with several optional components, we’ll start with the most basic type first.

Syntax: Defining an Input-less Function

def <identifier> = <expression>

At its most basic, a Scala function is a named wrapper for an expression. When you need a function to format the current data, check a remote service for new data, or just to return a fixed value, this is the format for you. Here is an example of defining and invoking input-less functions:

scala> def hi = "hi"
hi: String

scala> hi
res0: String = hi

The return type of functions, as with values and variables, are present even if they are not explicitly defined. And like values and variables, functions are easier to read with explicit types.

Syntax: Defining a Function with a Return Type

def <identifier>: <type> = <expression>

This function definition is also input-less, but it demonstrates the “colon-and-type” format from value and variable definitions for function definitions. Here’s the “hi” function again with an explicit type for better readability:

scala> def hi: String = "hi"
hi: String

Now we’re ready to look at a full function definition.

Syntax: Defining a Function

def <identifier>(<identifier>: <type>[, ... ]): <type> = <expression>

Let’s try creating a function that performs an essential mathematical operation:

scala> def multiplier(x: Int, y: Int): Int = { x * y }
multiplier: (x: Int, y: Int)Int

scala> multiplier(6, 7)
res0: Int = 42

The body of these functions consists essentially of expressions or expression blocks, where the final line becomes the return value of the expression and thus the function. While I do recommend continuing this practice for functions, there are times when you need to exit and return a value before the end of the function’s expression block. You can use the return keyword to specify a function’s return value explicitly and exit the function.

A common use of an early function exit is to stop further execution in the case of invalid or abnormal input values. For example, this “trim” function validates that the input value is nonnull before calling the JVM String’s “trim” method:

scala> def safeTrim(s: String): String = {
     |   if (s == null) return null
     |   s.trim()
     | }
safeTrim: (s: String)String

You should now have a basic understanding of how to define and invoke functions in Scala.

Tip

To become more familiar with Scala’s functions, try rewriting some code examples from Chapter 2 and Chapter 3 as functions. When possible, move fixed values from the example expressions into input parameters of your new functions.

Procedures

A procedure is a function that doesn’t have a return value. Any function that ends with a statement, such as a println() call, is also a procedure. If you have a simple function without an explicit return type that ends with a statement, the Scala compiler will infer the return type of the function to be Unit, the lack of a value. For procedures greater than a single line, an explicit unit type of Unit will clearly indicate to readers that there is no return value.

Here is a simple logging procedure, defined with an implicit return type and then with an explict return type:

scala> def log(d: Double) = println(f"Got value $d%.2f")
log: (d: Double)Unit

scala> def log(d: Double): Unit = println(f"Got value $d%.2f")
log: (d: Double)Unit

scala> log(2.23535)
Got value 2.24

An alternate but now unofficially deprecated syntax you will see for procedures is to define them without the Unit return type and without an equals sign before the procedure body. With this syntax the example log() method would be written like this:

scala> def log(d: Double) { println(f"Got value $d%.2f") }
log: (d: Double)Unit

As just noted, this syntax is unofficially deprecated by the maintainers of the Scala language. The problem with this syntax is that too many developers accidentally wrote procedures with return values, expecting the return value to be actually returned to the caller. With this procedure syntax, any return value (or final expression) will be discarded. To address this problem, it is recommended that developers stick to regular function definitions with an equals sign to reduce the possibility that valid return values will be ignored.

Functions with Empty Parentheses

An alternate way to define and invoke an input-less function (one which has no input parameters) is with empty parentheses. You might find this style preferable because it clearly distinguishes the function from a value.

Syntax: Defining a Function with Empty Parentheses

def <identifier>()[: <type>] = <expression>

You can invoke such a function using empty parentheses as well, or choose to leave them off:

scala> def hi(): String = "hi"
hi: ()String

scala> hi()
res1: String = hi

scala> hi
res2: String = hi

The reverse is not true, however. Scala does not allow a function that was defined without parentheses to be invoked with them. This rule prevents confusion from invoking a function without parentheses versus invoking the return value of that function as a function.

Functions with Side Effects Should Use Parentheses

A Scala convention for input-less functions is that they should be defined with empty parentheses if they have side effects (i.e., if the function modifies data outside its scope). For example, an input-less function that writes a message to the console should be defined with empty parentheses.

Function Invocation with Expression Blocks

When invoking functions using a single parameter, you can choose to use an expression block surrounded with curly braces to send the parameter instead of surrounding the value with parentheses. Using an expression block to invoke a function makes it possible to handle calculations or other actions and then call the function with the return value of the block.

Syntax: Invoking a Function with an Expression Block

<function identifier> <expression block>

One example where using an expression block to invoke a function may be preferable is when you have to send a calculated value to the function. Instead of calculating the amount and storing it in local values to be passed to the function, you can do the calculations inside the expression block. The expression block will be evaluated before the function is called and the block’s return value will be used as the function argument.

Here is an example of calculating values inside a function block used for invoking a function:

scala> def formatEuro(amt: Double) = f"€$amt%.2f"
formatEuro: (amt: Double)String

scala> formatEuro(3.4645)
res4: String = €3.46

scala> formatEuro { val rate = 1.32; 0.235 + 0.7123 + rate * 5.32 }
res5: String = €7.97

If the value we want to pass to the function is already calculated, using parentheses to specify the function parameter is the natural way to go. But if you have calculations that will only be used for the function, and you can keep the code readable to others, a function invocation with an expression block may be a good choice.

Recursive Functions

A recursive function is one that may invoke itself, preferably with some type of parameter or external condition that will be checked to avoid an infinite loop of function invocation. Recursive functions are very popular in functional programming because they offer a way to iterate over data structures or calculations without using mutable data, because each function call has its own stack for storing function parameters.

Here’s an example of a recursive function that raises an integer by a given positive exponent:

scala> def power(x: Int, n: Int): Long = {
     |   if (n >= 1) x * power(x, n-1)
     |   else 1
     | }
power: (x: Int, n: Int)Long

scala> power(2, 8)
res6: Long = 256

scala> power(2, 1)
res7: Long = 2

scala> power(2, 0)
res8: Long = 1

One problem with using recursive functions is running into the dreaded “Stack Overflow” error, where invoking a recursive function too many times eventually uses up all of the allocated stack space.

To prevent this scenario, the Scala compiler can optimize some recursive functions with tail-recursion so that recursive calls do not use additional stack space. With tail-recursion–optimized functions, recursive invocation doesn’t create new stack space but instead uses the current function’s stack space. Only functions whose last statement is the recursive invocation can be optimized for tail-recursion by the Scala compiler. If the result of invoking itself is used for anything but the direct return value, a function can’t be optimized.

Fortunately there is a function annotation available to mark a function as being intended to be optimized for tail-recursion. A function annotation is special syntax carried over from the Java programming language where the “at” sign (@) and an annotation type is placed before a function definition to mark it for special use. A function marked with the tail-recursion function annotation will cause an error at compilation time if it cannot be optimized for tail-recursion.

To mark a function as intended for tail-recursion, add the text @annotation.tailrec before the function definition or on the previous line.

Here’s the same example again only marked with the “tailrec” annotation, to let the Scala compiler know we expect it to be optimized for tail-recursion and that if it cannot be, the compiler should treat it as an error:

scala> @annotation.tailrec
     | def power(x: Int, n: Int): Long = {
     |   if (n >= 1) x * power(x, n-1)
     |   else 1
     | }
<console>:9: error: could not optimize @tailrec annotated method power:
it contains a recursive call not in tail position
         if (n >= 1) x * power(x, n-1)

Ah, the function couldn’t be optimized because the recursive call is not the last statement in the function. This is understandable. I’ll switch the “if” and “else” conditions and try again:

scala> @annotation.tailrec
     | def power(x: Int, n: Int): Long = {
     |   if (n < 1) 1
     |   else x * power(x, n-1)
     | }
<console>:11: error: could not optimize @tailrec annotated method power:
it contains a recursive call not in tail position
         else x * power(x, n-1)
                ^

Hmm, the recursive call is the last item in the function. Oh I see, we’re taking the result of the recursive call and multiplying it by a value, so that multiplication is actually the last statement in the function, not the recursive call.

A good way to fix this is to move the multiplication into the beginning of the invoked function instead of multiplying its result. Now the end of the function is a simple untouched result from the recursive call:

scala> @annotation.tailrec
     | def power(x: Int, n: Int, t: Int = 1): Int = {
     |   if (n < 1) t
     |   else power(x, n-1, x*t)
     | }
power: (x: Int, n: Int, t: Int)Int

scala> power(2,8)
res9: Int = 256

Success! The “tailrec” annotation and successful compile guarantees that the function will be optimized with tail-recursion, so that each successive call will not add more stack frames.

Although this example may seem challenging to get through, recursion and tail-recursion are still valuable methods for iterating without using mutable data. You’ll find that many of the data structures we’ll explore later in the book will be rich with functions that are implemented with tail-recursion.

Nested Functions

Functions are named, parameterized expression blocks and expression blocks are nestable, so it should be no great surprise that functions are themselves nestable.

There are times when you have logic that needs to be repeated inside a method, but would not benefit from being extrapolated to an external method. In these cases defining an internal function inside another function, to only be used in that function, may be worthwhile.

Let’s have a look at a method that takes three integers and returns the one with the highest value:

scala> def max(a: Int, b: Int, c: Int) = {
     |   def max(x: Int, y: Int) = if (x > y) x else y
     |   max(a, max(b, c))
     | }
max: (a: Int, b: Int, c: Int)Int

scala> max(42, 181, 19)
res10: Int = 181

The logic inside the max(Int, Int) nested function was defined once but used twice inside the outer function, making it possible to reduce duplicated logic and simplify the overall function.

The nested function here has the same name as its outer function, but because their parameters are different (the nested one only takes two integers) there is no conflict between them. Scala functions are differentiated by their name and the list of their parameter types. However, even if the names and parameter types were the same there would be no confict because the local (nested) one takes precedence over the outer one.

Calling Functions with Named Parameters

The convention for calling functions is that the parameters are specified in the order in which they are originally defined. However, in Scala you can call parameters by name, making it possible to specify them out of order.

Syntax: Specifying a Parameter by Name

<function name>(<parameter> = <value>)

In this example, a simple two-parameter function is invoked twice, first using the convention of specifying parameters by their order and then by assigning values by parameter name:

scala> def greet(prefix: String, name: String) = s"$prefix $name"
greet: (prefix: String, name: String)String

scala> val greeting1 = greet("Ms", "Brown")
greeting1: String = Ms Brown

scala> val greeting2 = greet(name = "Brown", prefix = "Mr")
greeting2: String = Mr Brown

Read the next section on default values to see how calling parameters by name can be very useful.

Parameters with Default Values

A common problem when defining functions is deciding which input parameters they should take to maximize reuse. In Scala, Java, and other languages, a common solution is to provide multiple versions of the same function with the same name but different lists of input parameters. This practice is known as function overloading due to the function’s name being reused for different inputs. The common practice is to copy a function with x number of parameters to a new function with x–1 parameters that invokes the original function using a default value for the missing parameter.

Scala provides a cleaner solution for this problem: specifying default values for any parameter, making the use of that parameter optional for callers.

Syntax: Specifying a Default Value for a Function Parameter

def <identifier>(<identifier>: <type> = <value>): <type>

Here is the greeting example from the previous section again with a default value for the “prefix” parameter. Because the “name” parameter is still required we will call the function with only this parameter, calling it by name because we can’t call it in order (because the “prefix” parameter comes first!):

scala> def greet(prefix: String = "", name: String) = s"$prefix$name"
greet: (prefix: String, name: String)String

scala> val greeting1 = greet(name = "Paul")
greeting1: String = Paul

This is pretty useful, except it would be better to be able to call the function without having to specify the parameter name. By reorganizing the function so that the required parameter comes first, we can call it without using a parameter name:

scala> def greet(name: String, prefix: String = "") = s"$prefix$name"
greet: (name: String, prefix: String)String

scala> val greeting2 = greet("Ola")
greeting2: String = Ola

As a matter of style it’s better to organize function parameters so that those with default values follow required parameters. This emphasizes the importance of the required parameters as well as making it possible to call the function without specifying the default parameters and not require the use of parameter names.

Vararg Parameters

Java and C developers will recognize the term vararg, a function parameter that can match zero or more arguments from the caller. Its most popular usage is in string interpolation functions such as C’s printf() and Java’s String.format().

Scala also supports vararg parameters, so you can define a function with a variable number of input arguments. The vararg parameter cannot be followed by a nonvararg parameter because there would be no way to distinguish them. Inside the function, the vararg parameter, implemented as a collection (which we’ll study in Chapter 6), can be used as an iterator in for loops.

To mark a function parameter as matching one or more input arguments, add an asterisk symbol (*) after the parameter’s type in the function definition.

Here is an example of using a vararg parameter to create a summing function that returns a sum of all of its input integers:

scala> def sum(items: Int*): Int = {
     |   var total = 0
     |   for (i <- items) total += i
     |   total
     | }
sum: (items: Int*)Int

scala> sum(10, 20, 30)
res11: Int = 60

scala> sum()
res12: Int = 0

Parameter Groups

So far we have looked at parameterized function definitions as a list of parameters surrounded by parentheses. Scala provides the option to break these into groups of parameters, each separated with their own parentheses.

Here is an example of the “max” function where the two input parameters have been split into their own parameter groups:

scala> def max(x: Int)(y: Int) = if (x > y) x else y
max: (x: Int)(y: Int)Int

scala> val larger = max(20)(39)
larger: Int = 39

Given this example, parameter groups may appear to provide little benefit. After all, why not just keep all of the parameters together in one group? The real benefits come when you use them with function literals, which we will investigate in Invoking Higher-Order Functions with Function Literal Blocks.

Type Parameters

Until this point, the only parameters to functions we have discussed are “value” parameters, the input data passed to functions. In Scala, to complement the value parameters, you can also pass type parameters, which dictate the types used for the value parameters or for the return value. Using type parameters can increase the flexibility and reusability of functions as they transition these types from being fixed to being set by the caller of the function.

Here is the syntax for defining a function with a type parameter. I’ve removed everything after the return type to keep the syntax simple, and changed the typical “identifier” notation to denote its actual purpose (because otherwise every item after “def” would be an identifier).

Syntax: Defining a Function’s Type Parameters

def <function-name>[type-name](parameter-name>: <type-name>): <type-name>...

This is where I normally put an example of a new feature, but because type parameters can be a tricky subject to learn I’ll change the formula here to show how this feature solves a given problem.

Caution

Here is one of the times when I’m showing the wrong way to do something. It is a useful exercise for demonstrating the usefulness of type parameters, but be forewarned that some of the example code exercises will not be correct.

Let’s say I want to have a simple function that only returns its input (known as an identity function), in this case one defined for a String:

def identity(s: String): String = s

Well, that could be useful, but I can only call it for a String. There is no way to call it for, say, an Int unless I define a separate function:

def identity(i: Int): Int = i

Now I have defined this for Ints, but it will be a pain to have to redefine this for every type I want to use. What if I just use the root type, Any, which will work for all types? I’ll try that out and pass it a new String, then store the return value:

scala> def identity(a: Any): Any = a
identity: (a: Any)Any

scala> val s: String = identity("Hello")
<console>:8: error: type mismatch;
 found   : Any
 required: String
       val s: String = identity("Hello")
                               ^

This example didn’t work out. I had hoped to assign the result to a String but because the function’s return type is Any there was no way to do this, thus resulting in a Scala compilation error.

The solution? Instead of defining the function to use a specific type (e.g., String or Int) or a generic “root” type (e.g., Any), parameterize the type so it will suit whatever callers want to use.

Here is the identity function defined with a type parameter, making it usable with any type you give it:

scala> def identity[A](a: A): A = a
identity: [A](a: A)A

scala> val s: String = identity[String]("Hello")
s: String = Hello

scala> val d: Double = identity[Double](2.717)
d: Double = 2.717

The identity function’s type parameter is A, which like the value parameter a is simply a unique identifier. It is used to define the type of the value parameter a and the return type of the function.

Now that the identity function has been defined with a type parameter, I can call it with [String] to convert the value parameter type and return type into a String for the scope of my function call. I can then call it with [Double] to convert it to work with Double values for the scope of the function call.

Of course, another excellent feature that we know Scala provides is type inference. In the preceding example it wasn’t really necessary to pass the [String] type parameter to the “identity” method because the compiler could have inferred this from either the String literal we passed it or the String value to which we assigned the function’s return value.

Let’s take the two function calls from the previous example and remove their type parameters, demonstrating that type parameters can be inferred by the compiler:

scala> val s: String = identity("Hello")
s: String = Hello

scala> val d: Double = identity(2.717)
d: Double = 2.717

This looks great. There is just one remaining explicit type we can remove, the types of the values. With input values of a String and a Double, the Scala compiler can infer the types of the type parameters and of the values to which the return values are assigned:

scala> val s = identity("Hello")
s: String = Hello

scala> val d = identity(2.717)
d: Double = 2.717

Here you have witnessed a triumph of type parameters and type inference. The literals passed to a function are enough to change its value parameter type, return value type, and the type of the values to which its return value is assigned.

In regular practice this may not be the most readable way to define values, because a reader of the code would need to check the function definition carefully to figure out what the values assigned to its return value would become. It does, however, serve as a successful demonstration of the flexibility and functionality of Scala’s type system and support for highly reusable functions.

Methods and Operators

Until this point we have been discussing the use of functions without reference to where they will actually be used. Functions on their own, as defined in the REPL, are helpful for learning the core concepts. However, in practice they will typically exist in objects and act on data from the object, so a more appropriate term for them will often be “methods.”

A method is a function defined in a class and available from any instance of the class. The standard way to invoke methods in Scala (as in Java and Ruby) is with infix dot notation, where the method name is prefixed by the name of its instance and the dot (.) separator.

Syntax: Invoking a Method with Infix Dot Notation

<class instance>.<method>[(<parameters>)]

Let’s try this out by calling one of the many useful methods on the String type:

scala> val s = "vacation.jpg"
s: String = vacation.jpg

scala> val isJPEG = s.endsWith(".jpg")
isJPEG: Boolean = true

If it isn’t clear, the value s is an instance of type String, and the String class has a method called endsWith(). In the future we’ll refer to methods using the full class name, like String.endsWith(), even though you typically invoke them with the instance name, not the type name.

You’ll find that most of the types in Scala have a wide variety of methods available for use with them. Part of the process of becoming a proficient Scala developer is learning the Scala library well enough to be familiar with many of its types and their methods. The official Scala API Documention has a full list of the available types and their methods. I highly recommend taking the time to learn the types you are using and try out new methods on them.

Finding Documentation for the String Type

The documentation for the String type is split between the StringOps page in the Scala documentation and the java.lang.String Javadocs, because Scala wraps Java’s String, providing complementary functionality.

Let’s continue exploring new methods by trying out some of the useful ones in the Double type:

scala> val d = 65.642
d: Double = 65.642

scala> d.round
res13: Long = 66

scala> d.floor
res14: Double = 65.0

scala> d.compare(18.0)
res15: Int = 1

scala> d.+(2.721)
res16: Double = 68.363

The round and floor methods are relatively simple. They have no parameters and only return a modified version of the value in the object (where the object in question is a Double with the value 65.642). The compare method takes a single parameter and returns either 1, 0, or –1 if the given parameter is less, equal to, or greater than the value of d.

The last method has a single character as its name, the addition operator (+), but is still a valid function that takes a single parameter and returns the sum of d and the parameter. It may seem odd to provide a method for handling addition when one could just use the addition operator, but this method is actually the implementation of the addition operator.

Let me explain that in plainer terms. There actually is no addition operator in Scala, nor are there any other arithmetic operators. All of the arithmetic operators we have used in Scala are actually methods, written as simple functions using their operator symbol as the function name and bound to a specific type.

This is possible because of an alternate form of invoking methods on objects known as operator notation, which forsakes the traditional dot notation and uses spaces to separate the object, its operator method, and the single parameter to the method. Every time we write 2 + 3, the Scala compiler recognizes this as operator notation and treats it as if you had written 2.+(3), where the addition method of an Int with the value 2 is called with the parameter 3 and the value 5 is returned.

To invoke an object’s methods with operator notation, pick a method that takes a single parameter and separate the object, method, and single parameter with spaces. No other puncuation is necessary.

Syntax: Invoking a Method with Operator Notation

<object> <method> <parameter>

A more precise term for this notation would be infix operator notation, because the operator (the object’s method) is located in between the two operands.

Let’s repeat the last two method calls in the previous example, but rewrite them using operator notation. The first two methods in the previous example aren’t eligible for infix operator notation because they lack a parameter:

scala> d compare 18.0
res17: Int = 1

scala> d + 2.721
res18: Double = 68.363

The results are equivalent to the results in the previous example, which we should expect because they are calling the same functions with the same input values.

What About Methods with More Than One Parameter?

Operator notation is meant for single-parameter methods, akin to simple mathematical operations, but can be used on methods with more than one parameter. To do so, wrap the list of parameters in parentheses and treat it as a single (but wrapped) parameter. For example, you can invoke String.substring(start,end) as "staring" substring (1,4).

Here is an addition of three numbers. In terms of operators, there are two additions taking place. How would you expect this gets converted into method calls?

scala> 1 + 2 + 3
res19: Int = 6

The answer is that the first operation is one method call, 1 + 2. The second operation is a separate method call applied to the result from the first call, or 3 + 3. You can use the same technique to chain regular method calls as long as each method’s result is an object on which you can call the next operator method.

Scala’s support for infix operator notation for invoking object methods has multiple benefits for the language. Operators, instead of being a part of the syntax and implemented in hidden ways, are just methods implemented in their objects that can be viewed or called directly. The syntax is thus reduced and simplified. Developers are free to implement their own operators because every single-parameter method can be used as an operator, and are motivated to simplify methods to only take a single parameter to make them eligible for operator notation. Finally, the code readability may be improved, removing otherwise necessary punctuation to focus on the simple object, method, and parameter components.

About the only drawback to using operator notation is when it reduces code readability instead of improving it. For example, a chain of 10 method calls separated only by spaces may be a bit harder to read than with regular dot notation, because the operators and operands may be hard to discern. Or an overeager developer may define his own type with an addition operator that, unknowingly to callers, performs a completely different type of operation.

Make sure to use care to invoke operator notation only when it can be clearly read, and you may find yourself using it regularly.

Writing Readable Functions

We’ll wrap up this chapter with a more general discussion of how to write functions.

The entire point of writing functions is to reuse them (because otherwise you would have left them as single-use expressions). And the best way to ensure your functions will get reused is to make them readable by other developers. Readable functions are clear, understandable, and simple.

There are two ways to make sure your functions are readable. First, keep them short, well-named, and obvious. Break your complex functions into simpler functions that are shorter than the height of a standard visible page of text (say, 40 lines), so a reader won’t need to scroll up and down to see the entire function. Use a name that reasonably sums up what your function is trying to accomplish. If you have these two right, your function’s intent and implementation should be fairly obvious to developers.

The other way to make your functions readable is to add comments where appropriate. Scala supports the same commenting syntax as Java and C++ do. A double-slash (//) starts a line comment, continuing until the end of the line it starts on. A slash-and-star (/*) starts a range comment that continues until a closing star-and-slash (*/). Use them within your function to point out details and context that may be missed by readers, as well as to indicate potential problems or future work to be done.

An additional type of commenting is adding Scaladoc headers to your function. The Scaladoc tool, included with your Scala package, can generate API documentation based on these function headers. Scaladoc headers follow the same format as Javadoc headers, with a starting range comment of two stars (e.g., /**), an indented start prefixing every following line, and a regular ending range comment (*/). Parameters can be called out with a @param keyword followed by the parameter name and its description.

Scaladoc (or Javadoc, if you prefer) headers are a standard format for function comments. Adding them to your functions is a good practice to follow even if you don’t plan on generating API documentation. Developers reading your function will likely start with the Scaladoc header before reading the function’s code, so make sure to keep it accurate and concise.

Here is an example Scaladoc header for a function:

scala> /**
     |  * Returns the input string without leading or trailing
     |  * whitespace, or null if the input string is null.
     |  * @param s the input string to trim, or null.
     |  */
     | def safeTrim(s: String): String = {
     |   if (s == null) return null
     |   s.trim()
     | }
safeTrim: (s: String)String

An additional benefit of using Scaladoc headers for your function is the support from IDEs such as Eclipse and IntelliJ IDEA. They allow developers to read the documentation for your function without even reading your source code. Developers who invoke your function (or method) can view the Scaladoc header’s contents with a mouse-over of a function invocation, or by browsing a list of functions.

Summary

Because most of the language’s logical structures were covered in Chapter 3, it made sense to focus this, the chapter following expressions, on how to organize and reuse them as functions. Indeed, while a function’s name, input parameters, and return value type are important parts of a function’s definition, the actual contents of a function are one big expression.

An entire chapter about functions should be no less than expected from a book itself devoted to a functional programming language. However, though you just finished an entire chapter about functions, you have not yet learned everything there is to know about functions. Namely, that in Scala you can treat your functions as data and pass them into other functions to invoke.

This concept of functions as data, with their own literals and types, brings functions up to par with other forms as data. You’ll learn all about how functions can receive the same treatment as other data types, making them “first-class” citizens of the language, in the next chapter.

Exercises

  1. Write a function that computes the area of a circle given its radius.
  2. Provide an alternate form of the function in exercise 1 that takes the radius as a String. What happens if your function is invoked with an empty String ?
  3. Write a recursive function that prints the values from 5 to 50 by fives, without using for or while loops. Can you make it tail-recursive?
  4. Write a function that takes a milliseconds value and returns a string describing the value in days, hours, minutes, and seconds. What’s the optimal type for the input value?
  5. Write a function that calculates the first value raised to the exponent of the second value. Try writing this first using math.pow, then with your own calculation. Did you implement it with variables? Is there a solution available that only uses immutable data? Did you choose a numeric type that is large enough for your uses?
  6. Write a function that calculates the difference between a pair of 2D points (x and y) and returns the result as a point. Hint: this would be a good use for tuples (see Tuples).
  7. Write a function that takes a 2-sized tuple and returns it with the Int value (if included) in the first position. Hint: this would be a good use for type parameters and the isInstanceOf type operation.
  8. Write a function that takes a 3-sized tuple and returns a 6-sized tuple, with each original parameter followed by its String representation. For example, invoking the function with (true, 22.25, "yes") should return (true, "true", 22.5, "22.5", "yes", "yes"). Can you ensure that tuples of all possible types are compatible with your function? When you invoke this function, can you do so with explicit types not only in the function result but in the value that you use to store the result?

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required