程式扎記: [Scala IA] The Basics : Ch3. OOP in Scala - Named and default arguments and copy constructors

標籤

2016年8月28日 星期日

[Scala IA] The Basics : Ch3. OOP in Scala - Named and default arguments and copy constructors

Named and default arguments and copy constructors (p83) 
Scala lets you specify method arguments using a named style. When you have methods or class constructors taking similar types of arguments, it becomes difficult to detect errors if you swap them by mistake. Let’s take the example of Person again. Instead of passing in an order of first name, last name, if we swap the order, Scala won’t complain: 
scala> case class Person(firstName:String, lastName:String)
defined class Person

scala> val p = Person("lastName", "firstName")
p: Person = Person(lastName,firstName)

Unfortunately, both parameters are of the same type, and the compiler can’t detect the mistake at compile time. But now, using named style arguments, you can avoid the problem: 
scala> val p = Person(lastName = "lastName", firstName = "firstName")
p: Person = Person(firstName,lastName)

The named arguments use the same syntax as a variable assignment, and when you use named arguments, the order doesn’t matter. You can mix the named arguments with positional arguments, but that’s usually a bad idea. When going for named arguments, always try to use a named style for all the arguments. The following example uses a named style for the first argument but not for the second. As mentioned earlier, it’s good practice to avoid this: 
scala> val p = Person(firstName = "firstName", "lastName")
p: Person = Person(firstName,lastName)

When using a named style, if the parameter name doesn’t match, the Scala compiler will complain about the value not being found. But when you override a method from a superclass, the parameters’ names don’t have to match the names in the superclass method. In this case, the static type of the method determines which names have to be used. Consider this example, where you have the Person trait and SalesPerson overriding the grade method and changing the parameter name in the process from years to yrs
scala> trait Person { def grade(years:Int): String }
defined trait Person
warning: previously defined object Person is not a companion to trait Person.
Companions must be defined together; you may wish to use :paste mode for this.


scala> class SalesPerson extends Person { def grade(yrs:Int) = "Senior" }
defined class SalesPerson

scala> val s = new SalesPerson
s: SalesPerson = SalesPerson@51776d39

scala> s.grade(yrs = 1)
res1: String = Senior

scala> s.grade(years = 1)
:15: error: not found: value years

Here years won’t work because the type of the s instance is SalesPerson. If you force the type variable to Person, then you can use years as a named argument. I know this is a little tricky to remember, so watch out for errors like this: 
scala> val s:Person = new SalesPerson
s: Person = SalesPerson@6514451b

scala> s.grade(years = 1)
res3: String = Senior

scala> s.grade(yrs = 1) // Currently variable s is as pointer of Person
:15: error: not found: value yrs

The value of the named argument could be an expression like a method or block of code, and every time the method is called, the expression is evaluated: 
scala> s.grade(years = {val x = 10; x + 1})
res4: String = Senior

The complementing feature to named arguments is default arguments. You’ve already seen one example of a default argument in the query example, where the last argument of the case class defaulted to NoOption
  1. case class Query(q: DBObject, option: QueryOption = NoOption) {  
  2.     def sort(sorting: DBObject) = Query(q, Sort(sorting, option))  
  3.     def skip(skip: Int) = Query(q, Skip(skip, option))  
  4.     def limit(limit: Int) = Query(q, Limit(limit, option))  
  5. }  
The default argument has the form argType = expression, and the expression part is evaluated every time the method uses the default parameter. If you create a Query instance using Skip, the default won’t be used: 
  1. val skipOption = Skip (10, NoOption)  
  2. val newQuery = Query(new BasicDBObject(), skipOption)  
One of the interesting uses of default arguments in Scala is in the copy method of case classes. Starting from Scala 2.8 on, along with the usual goodies, every case class has an additional method called copy to create a modified instance of the class. This method isn’t generated if any member exists with the same name in the class or in one of its parent classes. The following example uses the copy method to create another instance of the skipquery option, but with a Limit option instead of NoOption
scala> val skipOption = Skip(10, NoOption)
skipOption: Skip = Skip(10,NoOption())

scala> val skipWithLimit = skipOption.copy(anotherOption = Limit(10, NoOption))
skipWithLimit: Skip = Skip(10,Limit(10,NoOption))

The copy method is using a named argument to specify the parameter that you’d like to change. The copy method generated by the Scala compiler for the Skip case class looks like the following: 
  1. case class Skip(number: Int, anotherOption: QueryOption) extends QueryOption {  
  2.     def copy(number: Int = number, anotherOption: QueryOption = anotherOption) = {  
  3.         Skip(number, anotherOption)  
  4.     }  
  5. }  
As you can see, in the generated method all the parameters are defaulted to the value provided to the constructor of the class, and you can pick and choose the parameter value you want to change during the copy. If no parameters are specified, copy will create another instance with the same values: 
scala> Skip(10, NoOption) == Skip(10, NoOption).copy()
res22: Boolean = true

In Scala, invoking the == method is the same as calling the equals method. The == method is defined in the scala.Any class, which is the parent class for all classes in Scala.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!