Flexible Parameters and Arguments

Defining parameters and passing arguments are among the most common tasks we perform when programming in any language. Scala offers some nice facilities and conveniences to define variable numbers of arguments, declare default values for parameters, and define named arguments.

Passing Variable Arguments (Varargs)

Methods like println take a variable number of parameters. You can pass zero, one, or more arguments to such methods. In Scala you can create your own functions that can take a variable number of parameters.

We can design methods to take a variable number of arguments—varargs. However, if we have more than one parameter, only the trailing parameters can take a variable number of arguments. To indicate that a parameter can take a variable number of arguments, use an asterisk after the last parameter’s type. Here’s an example max function:

FromJavaToScala/Parameters.scala
 
def​ max(values: ​Int​*) = values.foldLeft(values(0)) { Math.max }

Here’s an example of invoking the max function:

FromJavaToScala/Parameters.scala
 
max(8, 2, 3)

We passed three arguments to the function. However, we can pass a different number of arguments—for example:

FromJavaToScala/Parameters.scala
 
max(2, 5, 3, 7, 1, 6)

When a parameter’s type is declared with a trailing *, Scala defines the parameter as an array of that type. Let’s confirm that with an example:

FromJavaToScala/ArgType.scala
 
def​ function(input: ​Int​*) = println(input.getClass)
 
 
function(1, 2, 3)

When we run the code, the output shows the type of the parameter:

 
class scala.collection.mutable.WrappedArray$ofInt

We invoked the max function with an arbitrary number of arguments. The parameter is of type array, so we can use iterators to process the collection of arguments received. It may be tempting to pass an array as an argument, instead of discrete values, but that won’t work, as we see in the next example:

 
val​ numbers = ​Array​(2, 5, 3, 7, 1, 6)
 
max(numbers) ​// type mismatch error

The previous code will produce the following compilation error:

 
CantSendArray.scala:5: error: type mismatch;
 
found : Array[Int]
 
required: Int
 
max(numbers) // type mismatch error
 
^
 
one error found

This error is mainly due to type incompatibility; the parameter is more array-like than literally being an array type. Nevertheless, if we have an array of values, we surely would like to pass that. We can, using the array explode notation, like this:

 
val​ numbers = ​Array​(2, 5, 3, 7, 1, 6)
 
max(numbers: _*)

The series of symbols after the argument name tells the compiler to explode the array into the necessary format, to send as a variable number of arguments.

Now that you know how to pass a variable number of arguments to methods, let’s look at another nice related feature—default values.

Providing Default Values for Parameters

Scala makes it convenient to skip most common or sensible default values when calling methods or constructors.

In real life, if we assume first-class postage is the most common postage option, we’d like to ask an assistant to “mail this, please” instead of “mail this, please, by first class.” A request to mail with no particular mention of postage would be inferred as a desire to send by first class.

In Java we can design the flexibility to skip one or more parameters using overloading. While it works quite well from the caller’s point of view, overloading takes more effort and more code, and may lead to duplication—hence, it is error prone. In Scala you can achieve that easily with default values for parameters.

Here’s an example of a method with default values for its parameters:

FromJavaToScala/DefaultValues.scala
 
def​ mail(destination: ​String​ = ​"head office"​, mailClass: ​String​ = ​"first"​) =
 
println(s​"sending to $destination by $mailClass class"​)

Both parameters of the mail method have default values. If a parameter is skipped in the call, the attached default value will kick in.

Here are the options to call the mail method:

FromJavaToScala/DefaultValues.scala
 
mail(​"Houston office"​, ​"Priority"​)
 
mail(​"Boston office"​)
 
mail()

In the first call we provided arguments for both parameters. In the second call we skipped the second parameter, and in the third we skipped both parameters. We can see the result of the compiler filling in values for the skipped parameters in the output:

 
sending to Houston office by Priority class
 
sending to Boston office by first class
 
sending to head office by first class

The substitution of the default value for a skipped parameter is done at compile time. Exercise caution when overriding methods. If a method in the base class uses one default and the corresponding overridden method in the derived class uses a different default, you can easily get confused as to which value will get used.

For multiparameter methods, if you choose to skip the value for one of the parameters, you have to use the default for all parameters that follow it in the parameter list. For instance, you can’t use a default value for the destination and provide an explicit value for the mailClass in the previous example. The reason for this restriction is that the value to substitute for the skipped parameters is determined based on the position. The flexibility of default values for parameters can interplay with another fluency that Scala offers to ease this restriction, as you’ll see next.

Using Named Arguments

Scala’s type checking will prevent you from sending the wrong type of arguments to methods. However, for methods that take multiple parameters of the same type, passing arguments—for example, power(2, 3)—may cause some involuntary head-scratching; is 2 the exponent or the base?

Thankfully, in cases like this, you can make the code expressive and fluent by naming the arguments in the call—for example, power(base = 2, exponent = 3).

Let’s use named arguments when calling the mail method we saw in the previous section:

FromJavaToScala/Named.scala
 
mail(mailClass = ​"Priority"​, destination = ​"Bahamas office"​)

In the call we explicitly identified the arguments using the names of the intended parameters. With named arguments, the order in which we specify the arguments does not matter—to make the point, in the previous example, we’ve first provided the value for the second parameter of the mail method.

You have to be aware of a few caveats when working with named arguments:

  • You’re required to provide values for all parameters with no default values.

  • For parameters with a default value, you can optionally provide a named argument.

  • You can provide a value for a parameter at most once.

  • When overriding methods of a base class, you should keep the parameter names consistent. If you don’t, the compiler may use the parameter names in the methods of the base class when your intention may be otherwise.

  • If there are overloaded methods with the same parameter names but different types, it’s possible that the call is ambiguous. In that case the compiler will give a stern error and you’d have to fall back on positional arguments.

Note that the order is not enforced. For methods with multiple parameters with default values, the restriction of using default values for trailing parameters is removed when naming arguments; for example, we can write this call:

 
mail(mailClass = ​"Priority"​)

In general, to invoke the mail function, the compiler will require the value for the destination parameter since we’re specifying the value for the second parameter mailClass. However, as in the previous code, we can get around that restriction by naming the argument. Thus, we get more flexibility when mixing default values with named arguments than when using default values with positional arguments.

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

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