The journey of discovering the intriguing Scala language continues. Previously we’ve looked at how to execute Scala code, create classes and how constructors work in Scala. In this post we’ll take a closer look at how methods work in Scala. There are a few pretty interesting things concerning methods in Scala if you’re used to working with Java or C# so I have good hopes that this will be interesting!
Creating methods
To have something to dissect, let’s build a simple string helper class that has a method to crop and ellipse (add “…” at the end) text that is longer than a specified length. We do so in a class named TextHelper that has an immutable variable with the suffix that we’ll add to strings that have been cropped.
class TextHelper { val suffix = "..." }
In Scala methods are declared by writing “def methodName(param1: param1Type, param2: param2Type) : returnType = { method body }”. Our method, named “ellipse” requires two parameters, the original string to crop and the maximum allowed length of the string. Naturally the return type is string. A dummy implementation looks like this:
def ellipse(original: String, maxLength: Int) : String = { return "Not implemented yet"; }
We can actually let the compiler infer the return type and remove that.
def ellipse(original: String, maxLength: Int) = { return "Not implemented yet"; }
The compiler can infer the return type from what is being returned but it can’t infer the types of the parameters so we’ll have to leave those explicit specifications in. We can however remove the return keyword to further reduce the amount of ceremony in the method.
def ellipse(original: String, maxLength: Int) = { "Not implemented yet"; }
If we don’t use the return statement, as above, the compiler will just assume that we want to return the value of the last statement. However, if we want to return from the middle of a method the return keyword is needed. In our case that’s actually something that we would like to do when the string doesn’t need to be cropped given that we add a guard clause for that.
Let’s implement the method.
def ellipse(original: String, maxLength: Int) = { if(original.length <= maxLength) return original; original.substring(0, maxLength - suffix.length) + suffix; }
If the string isn’t longer than the passed in maxLength the method just returns the string. If it is it will instead return a cropped version of the string with three dots as a suffix.
The below script shows the method in it’s class and an example of using it.
class TextHelper { val suffix = "..." def ellipse(original: String, maxLength: Int) = { if(original.length <= maxLength) return original; original.substring(0, maxLength - suffix.length) + suffix; } } val helper = new TextHelper() println(helper.ellipse("Hello world!", 10))
Running this script will output “Hello w…”.
Naming methods
The Scala reference specifies how we can name methods (and identifiers in general) pretty clearly:
“There are three ways to form an identifier. First, an identifier can start with a letter which can be followed by an arbitrary sequence of letters and digits. This may be followed by underscore ‘_’ characters and another string composed of either letters and digits or of operator characters. Second, an identifier can start with an operator character followed by an arbitrary sequence of operator characters. The preceding two forms are called plain identifiers. Finally, an identifier may also be formed by an arbitrary string between back-quotes (host systems may impose some restrictions on which strings are legal for identifiers). The identifier then is composed of all characters excluding the backquotes themselves.”
As you can see there are three ways that are valid when naming methods (and classes, variables etc). To make it easier to refer to the three valid formats let’s list them each and assign a number to them:
- Start with a letter followed by an arbitrary number of letters and digits, optionally followed by underscore characters and either more letters and numbers or by operator characters.
- Start with an operator character followed by an arbitrary number of operator characters.
- An arbitrary string enclosed in backqoutes.
To clarify what this means let’s have a look at a few example of valid and invalid method names. The valid ones are followed by a comment about which of the naming ways that makes them valid:
//Valid someString //#1 SomeString //#1 a123 //#1 someString_? //#1 a_b_? //#1 ? //#2 ?+-<>:|!&%#\^@~*_ //#2 `lorem ipsum 123 []` //#3 //Invalid 123 //Doesn't start with a letter someString? //Ends with an operator char without preceding underscore someString_a? //Same as above ?a //Doesn't match #2 as it contains a letter a_?_b //To match #1 any operator char must come last
As we can see with the example above Scala is far from as restrictive as Java or C# when it comes to naming. This in one of the reasons why Scala is a good language for creating internal DSLs in. The fact that we can use operator characters in method names also opens up some interesting possibilities. For instance, let’s say that we would want to create a class that wraps a string and has a multiplication method. When the multiplication method is called with an integer parameter it should return the wrapped string concatenated with itself the number of times specified by the parameter. We could actually name this method “*”. An implementation could look like this:
class MultiplicationString(val value: String) { def *(times: Int) = { var result = ""; for(i <- 1 to times) result += value; result; } } val original = new MultiplicationString("Hello world!") println(original.*(3))
The above script prints “Hello world!Hello world!Hello world!” to the console when run.
Calling methods – infix notation
In the multiplication example above it’s almost looks like we’ve defined the star operator (*) for the MultiplicationString class with our method named *. And it turns out that that’s even closer to the truth than we might think. In Scala the multiplication operator as well as other operators are actually implemented as methods. This works as the dot after the variable name and the parenthesis after the method name is optional when calling methods that have either none or only a single parameter and can then be replaced with a whitespace. This is called infix notation.
In other words all of the three lines listed below are valid and functionally equivalent to each other.
original.*(3) original *(3) original * 3
Optional parameters and named arguments
Similarly to C# 4 and many other languages Scala 2.8 supports optional parameters and named arguments. Optional parameters means that you can define a default value for a parameter in the method declaration and thereby a user of the the method isn’t forced to explicitly specify that parameters value. Named arguments mean that you can explicitly specify the name of the argument for which you are providing a value when calling a method. It’s then the name of the argument instead of it’s position that determines for which argument you’re specifying the value. This comes in handy when there are multiple optional parameters and it can also make your code more readable and self-documenting.
For instance we could change the call to the ellipse method in our earlier example to be more self-documenting by explicitly specifying the name of the maxLength parameter.
val helper = new TextHelper() //Old version println(helper.ellipse("Hello world!", 10)) //New version println(helper.ellipse("Hello world!", maxLength = 10))
We could also make the maxLength parameter optional by specifying a default value for it.
Parenthesis or no parenthesis
We saw that we could call methods without using parenthesis in certain conditions. It turns out we can also omit the parenthesis's when declaring methods that doesn’t have any parameters. It’s however important to note that when we call such methods we can’t do that with parenthesis's while with methods that are declared with parenthesis’s we can choose whether we want to include them or not when calling the method. As I’ve understood it the convention in the Scala community is to omit the parenthesis when calling methods that don’t have any side effects.
A silly but fun example
To illustrate the power of what we’ve learned let’s create a simply and silly internal with which we can model conversations in which people say hi to each other. We begin by creating a simple Person class with a single field, name, and a method named say.
class Person(val name: String) { def say() = { new VoiceOf(this) } }
The say method returns a new instance of a class named VoiceOf which we’ll create next passing itself as a constructor argument. That is, if we create a Person named Joe calling the say method on that object will return “the voice of Joe”. The VoiceOf class looks like this:
class VoiceOf(speaker: Person) { def hi() = { println("Hello!") } def hi(to: Person) = { println("Hi " + to.name) } def yourName() = { println("My name is " + speaker.name) } }
The class has three methods, one named hi that is overloaded with one expecting a parameter with another Person to which it should say hi to. Using these classes and utilizing what we’ve learned about calling methods and named arguments we can create a conversation between to Persons, Jane and Joe.
joe.say hi; joe.say yourName; jane.say hi(to = joe); jane.say yourName
Running this script produces the following output to the console:
Hello!
My name is Joe
Hi Joe
My name is Jane
In case you’re wondering about the dot before the calls to the say method I found that I had to put it there or otherwise the compiler would think that the next word (hi or yourName) was meant as a parameter to it. I could fix that by adding parenthesis to the method call but I though it looked better with the dot. Here’s how it looked using parenthesis instead:
joe say() hi; joe say() yourName; jane say() hi(to = joe); jane say() yourName
Recap and a look at what’s next
In this part we looked at creating and calling methods. We especially looked at Scala’s rather flexible naming rules and conventions for writing calls to methods which is great for creating internal DSLs.
In the next part we’ll look at the if statements and different ways to create loops. While the type of constructs that Scala offers for those operations are pretty much the same as in many other languages it turns out they have some features that makes them stand out compared to Java and C#.
About this post and the Learning Scala series
This post is a part of a series of posts in which I describe my experiences while trying to learn Scala. I try to do it in the form of a tutorial as I find doing so is an excellent way of consolidating knowledge and hopefully I can also help others. However, keep in mind that I’m in no way an expert on Scala. This is just a way to document what I’ve learned so far and there might be some things that I’ve misunderstood. If you’ve found such a thing and would like to help me correct that misunderstanding, or if you would like to leave any other kind of feedback don’t hesitate to leave a comment!
You can find links to all posts in this series at the bottom of the first post.
PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.
Similar articles
- Learning Scala part eight – Scala’s type hierarchy and object equality
- Learning Scala part six – If statements and Loops
- Learning Scala part four – Classes and Constructors
- Learning Scala part nine – Uniform Access
- Learning Scala part three – Executing Scala code
- Learning Scala part seven - Traits
- Learning Scala
- Learning Scala part two – Installation and tools
Comments
comments powered by Disqus