Like many languages, Scala exhibits fairly typical constructs such as classes and methods.
These classes and methods operate nearly identically to those found in Java, for exam- ple, but with some additional sugar to make their use slightly nicer. Although having sugar for regular constructs is convenient, Scala also exhibits traits and functions.
These are language features that really allow you to build highly reusable pieces of code and they’re not found in many programming languages. The following sections cover these four Scala constructs and show you some basic usage principles, starting with the likely familiar class paradigm.
A.2.1 Classes
As Scala exhibits both object oriented and functional qualities, you can happily con- struct classes and Bextends A semantics. These classes can have instance variables, methods, and everything else that is common in object-oriented code.
Consider the following class definition:
class Foo(bar: Int){
def whatever =
if(bar > 3) "Awesome" else "Amazing"
}
377 Classes, methods, traits, and functions
This simple class Foo has a single constructor argument that takes an integer “value.”
The constructor argument, bar, is accessible for all the methods or properties of that class but it isn’t accessible for external callers: it is essentially not public.
If you wanted to make the bar property publicly available, the definition would need to add the val keyword before the constructor argument:
class Foo(val bar: Int){ ... }
This modification then allows the following interaction:
scala> class Foo(val bar: Int) defined class Foo
scala> new Foo(123).bar res0: Int = 123
By creating an instance of the class, an external caller can reference the bar property by name to obtain its value. Being defined as val, this property is immutable.
In addition to allowing you to define normal classes, Scala also supports single- tons as language constructs. These are created by way of the object keyword. Con- sider the following:
object Thing {
def now = new java.util.Date().toString }
The object keyword defines a lazy singleton object that won’t be instantiated until it’s touched by calling code. Because the Thing object is a singleton that Scala is managing for you, when you call the object, you can invoke it like a static member with some- thing like this:
scala> Thing.now
res1: java.lang.String = Sat Mar 26 15:23:49 GMT 2011
Objects can also be used to encapsulate other components of functionality, but that’s beyond the scope of this quick overview.
NOTE It is typically idiomatic within Scala to give methods that have zero arguments no trailing parenthesis if the function is referentially transparent.
For example Thing.foo is fine given a referentially transparent method, but if the method has side effects it would be idiomatic to write Thing.foo().
For more information on advanced Scala topics, you may find Joshua D. Suereth’s Scala in Depth useful.
Finally, both objects and classes can have the case modifier applied to their defini- tion to create what are known as case classes. Consider this example:
case class Foo(bar: Int, whiz: String)
By applying this keyword to the class definition, the Scala compiler will add certain conveniences to that class, such as providing a factory function that negates the need
to create instances with the new keyword. Additionally all the constructor arguments are automatically made available as immutable properties of that instance:
scala> val x = Foo(123,"Sample") x: Foo = Foo(123,Sample)
scala> x.bar res4: Int = 123 scala> x.whiz
res5: String = Sample
On top of these helpful conveniences, case classes also provide you with friendly and predictable implementations of the toString, equals, and hashCode methods. This means you can do handy comparisons of case class instances and receive sensible rep- resentations of instances when calling toString.
A.2.2 Traits
In addition to defining a single line of class inheritance, Scala also supports polymor- phism or multiple-inheritance via a construct known as traits. These traits allow you to break up your logic into defined blocks of functionality that can be composed together.
Consider the following example that models the components of sporting events:
trait Discipline { ... }
trait Run extends Discipline { ... } trait Cycle extends Discipline { ... } trait Swim extends Discipline { ... } trait Competition { ... }
case class Triathalon(name: String) extends Competition with Run with Cycle with Swim
With a model of traits configured such as this, it’s possible to create instances that compose together the desired functionality. If some change was required for all run- ning events, you would only need to make a single change to events that included the Run trait, and the change would be immediately propagated.
In addition, you can define the required composition for each trait without explicitly extending the trait itself. It’s essentially like telling the compiler “when this trait gets used, it must also be composed with trait ABC.” This is done via a self-type notation:
trait ABC { self: XYZ =>
...
}
In this example, the trait ABC can only ever be composed into another class when the XYZ type is also present in the mix. This is a powerful idiom within Scala that you don’t need to fully understand now, but as your system grows it can become exceedingly helpful.
Compose traits together
379 Classes, methods, traits, and functions
A.2.3 Methods
Methods are defined upon classes by using the def keyword, and in this case the resulting type is again inferred by the compiler to be a string. Class methods in Scala don’t need to be explicitly wrapped in curly braces unless the method requires some kind of internal assignment, because all control structures within Scala return a con- crete type. Consider the following two method definitions:
def whatever =
if(bar > 3) "Awesome" else "Amazing"
def another {
if(bar > 3) "Awesome" else "Amazing"
}
These two method implementations look exceedingly similar, and the unfamiliar eye might assume that they do the same thing. In fact, whatever returns a String whereas another returns a special Scala type called Unit. The reason for this is that the defini- tion of another lacks the equals sign (=) after the method name and immediately wraps the whole method in curly braces; this makes Scala assume that the method is side-effecting and thus returns nothing directly useful. If you’re coming from other statically typed languages, you can think of the Unit type as being analogous to the void construct.
Methods can also take arguments in exactly the same way that Java methods can, but within Scala it’s also possible have multiple argument groups. This can be useful, because it allows you to make flexible APIs that can have both arguments and pseudo- blocks. Here’s an example of the definition:
scala> def whatever(n: Int)(s: String) = "%s %s".format(n.toString, s) whatever: (n: Int)(s: String)String
And here’s an example of its usage:
scala> whatever(123){
| "asda"
| }
res10: String = 123 asda
These argument groups are separate, but both are available in the definition of the method body, so when you call the method you can pass arguments as either a single normal argument or as a block. You can even mix styles together to create whatever API suits your requirements.
The final thing to note about methods is that your operators can be called any- thing you want, even using Unicode operators. These operators can be called either directly or with what is known as infix notation. For example, calling the + operator with two integers and infix notation would give the familiar: 2+2. But in Scala, the same method call can be written as 2.+(2). For the most part, you’ll use the dot nota- tion to invoke methods, but often within Lift you’ll see infix notation when using List types. For example, List(1,2,3) is the exact same thing as 1::2::3::Nil, where the :: (cons) operator builds a list of the integer values.
A.2.4 Functions
With the blend of object oriented and functional styles, Scala sports the concept of general functions. That is to say, a specific instance of a function can be passed around and is in and of itself a specific type. Functions are first-class values in Scala, which essentially means that you can create generic functions that take A and return B. Such a relationship is typically expressed as A=> B and is referred to as a lambda function.
Consider the following example:
scala> val f = (s: String) => s.toLowerCase f: (String) => java.lang.String = <function1>
scala> f("SOMeThInG")
res8: java.lang.String = something
Here, a function that takes a String and returns a String is assigned as the value f and has the type Function1. With this function defined, it’s possible to pass a single argument to f and treat the value function like you would any other method; the only difference being that the function is itself an instance rather than being contained within a class.
In essence, this is the basis of all functional programming within Scala: functions can be any type to any type, and functions can even take other functions as arguments, resulting in what are known as higher-order functions. Functions themselves can have zero or more arguments and will have their appropriate type automatically applied by the compiler. For example, String => String would be equivalent to saying Function1[String,String].
The benefit of all this function madness is that you can conveniently encapsulate a piece of logic and only care about the type-level interfaces, essentially allowing parts of your system to replace whole bits of functionality simply by passing another func- tion that returns the same type but gets to the output via a different means.
This description really only scratches the surface of what is possible with function- based programming in Scala, but functions are heavily used within Lift for all manner of purposes, so it’s helpful to have a reasonable understanding of the concept.