Entering Code
All the examples in this tutorial can be run simply by clicking on them.
You can enter your own code by typing it into the box above. The code will be sent and run when you type Enter. You can type a lot of one-liners this way. However, if you open a code block with any form of braces the code will not be sent until the braces are matched and the cursor is positioned at the end of text. This allows you to write multi-line code and freely edit the code. You can always click on the Evaluate button or Ctrl Enter to force a send. Examples from other places can be entered using the regular cut and paste. The maximum text size is limited to about 2000 characters.
Already sent code can be recalled by using Ctrl with the up or down arrows. These you can then edit using the up/down arrows and send again. So you can try an example, then call it back, make changes, try different ideas and check how the syntax works.
Reset Button: This button resets your user space on the server. The server saves all the intermediate results you create so you may use them in later calculations. This takes some time. If you no longer need these results click on Reset. The server response to your requests will be improved. Previously entered text can still be recalled with the arrow keys.
Expressions
Simple arithmetic expressions work much as you would expect. The usual operators and precedence apply. Parentheses can be used to control the order of application.
1+2
3+4*(2-3)
23%5 // Modulus or Division Remainder
3.5*9.4+6/4
Scala understands different number types. 3.5 is a "double" while 6 is an "integer". Notice in this last case that integer division results in a truncation to an integer value. Scala will coerce values to the appropriate types in a mixed expression where possible.
The result of an expression may be stored in a variable. Variable names start with a character and can be followed by numbers or characters. "Golf1", "helpLine" and "Res4" are all examples of variable names. The result of an expression can be associated with a variable name. This association is signalled by a "var" or "val" keyword. "val" is used when the association is to be made once and not changed. For the time being you can use "var". Why are there "val" and "var"? "val" defines an immutable value and as you will learn later, is essential for functional style programming.
val pixel=34+5
var height=pixel+4
println(height)
Now try,
pixel=10 // this gives an error, trying to change a val
height+=4
println(height)
You can access previous results by using "res1", "res2", etc. Comments can be added to code at the end of a line with // or over several lines using a /* and */ pair. All comments will be ignored.
/*
Example using res, and these comments
*/
res0 + " Scala" // more comments on the line
There are a full set of bit manipulation operators too. These allow you to do bitwise operations.
3&2 // logical and
1|2 // logical or
1^2 // logical xor
1<<2 // shift left
-24>>2 // shift right and preserve sign
-14>>>2 // shift right and zero left bits
So now you already have quite a powerful on-line calculator but program flow control and functions for calculations that are to be repeated would be useful.
Base Types
Scala has a set of base types that are predefined. They are summarized as follows.
| Byte | 8-bit signed 2's complement integer (-128 to 127 inclusive) |
| Short | 16-bit signed 2's complement integer (-32,768 to 32,767 inclusive) |
| Int | 32-bit signed 2's complement integer (-2,147,483,648 to 2,147,483,647 inclusive) |
| Long | 64-bit signed 2's complement integer (-2^63 to 2^63-1, inclusive) |
| Float | 32-bit IEEE 754 single-precision float |
| Double | 64-bit IEEE 754 double-precision float |
| Char | 16-bit unsigned Unicode character |
| String | a sequence of Unicode characters |
| Boolean | true/false |
if else
"if (cond) else" is the first of several flow control structures, the means of choosing to do one calculation rather than another based on some condition. If the condition is true one set of actions will be taken while if it is false another set will be done. The condition must be an expression that yields a boolean result, namely true or false. There are a number of comparison operators that do just that. Here are some of them that are useful with numbers. Later you will learn about others that are appropriate for other types of things.
1>2 // greater than
1<2 // less than
1==2 // equals
1>=2 // greater than or equal
1!=2 // not equal
1<=2 // less than or equal
With the if statement if the condition is true then the expression before the else is evaluated otherwise the expression after it is evaluated. Unlike in some languages the if else evaluates to a value.
if(1>2) 4 else 5 // greater than
if (1<2) 6 else 7 // less than
val try1=if (1==2) 8 else 9 // equals
val isBook = 6>=3
val price=16
val vol=10
val sale=if (isBook)price*vol else price/vol
sale
You may need to combine individual conditions in some way. There are two operators which do this for you. && meaning And. || meaning Or. These combine boolean values and are not equivalent to & | which combines bit values.
val isBook = 6>=3
val price=16
val vol=10
val sale=if (((isBook)&&(price>5))||(vol>30))price*vol else price/vol
sale
while
"while (cond) block/exp" allows you to repeat a block of code or an expression while the condition is true. First an expression.
var total=18
while(total < 17) total+=3
"do block/exp while (cond)" allows you to repeat a block of code or an expression while the condition is true. The condition is evaluated after doing each iteration.
var total=18
do{
total+=3
}while (total < 17)
Notice in this case that total end up as 21 rather than 18 in the previous example with the while. Here is while being used to calculate the Greatest Common Divisor or GCD.
// find the greatest common divisor
var x = 36
var y = 99
while (x != 0) {
val temp = x
x = y % x
y = temp
}
println("gcd is",y)
for
"for (range) block/exp" allows you to repeat a block of code for all the values in a range or iterate through the members of a collection.
for(i <- 1 to 4) println("hi five")
The value of i takes all the values from 1 to 4. If you want the end range value not to be included the until version should be used.
for(i <- 1 until 4) println(i)
Multi-dimensional iterations are elegantly handled using multiple ranges. Notice that the two ranges are separated by a semi-colon.
for(i <- 1 until 4 ; j <- 1 to 3) println(i,j)
"for" may also be used to iterate through collections. A string is a collection of characters so "for" may be used to iterate through it.
for(c<-"hello")println(c)
Literals
Literals allow you to define the value of one of the basic types in your code. They are pretty much the same as those you find in Java
Integers
There are four types of integer namely Int, Long, Short, and Byte. You can use literals expressed in different bases, they are decimal, hexadecimal, and octal. You signal which form you are using by the first characters.
decimal(base 10): Any number starting with a non-zero digit.
17
298
Hexadecimal(base 16): starts with a 0x or 0X and is followed by the hex digits 0 to 9, a to f or A to F
0x23 //hex = 35 dec
0x01FF //hex = 511 dec
0xcb17 //hex = 51991 dec
Octal(base 8): starts with a 0 and is followed by the octal digits 0 to 7
023 // octal = 19 dec
0777 // octal = 511 dec
0373 // octal = 251 dec
By default these will be created as type Int. You can force them to type Long by adding the letter "l" or "L".
0XFAF1L // hex long = 64241
035L
You can assign literals to Short or Byte variables. However, the value must be in the appropriate range for that type.
val abyte: Byte = 27
val ashort: Short = 1024
val errbyte: Byte = 128 // Error - not in range -128 to 127
Floating point
Floating point literals are numbers containing a decimal point. They must start with a non-zero digit and can be followed by E or e that prefixes an exponent indicating the power of 10 to use. Some examples are:-
9.876
val tiny= 1.2345e-5
val large = 9.87E45
By default floating literals are created as type Double but you can force them to type Float by adding the letter "f" or "F". Optionally "d" or "D" can be appended to a floating literal.
val sma = 1.5324F
val ams = 3e5f
Character
Character literals are specified by any Unicode character in single quotes.
val chr = 'A'
You may also specify its value in several other ways.
Octal: An octal number between '\0' and '\377'.
val chr = '\101' // code for A
Unicode:A hexidecimal number between '\\u0000' and '\\uFFFF'
val chra = "\\u0041 is an A"
val chre = "\\u0045 is a E"
Finally, there are also a few character literals represented by special escape sequences. These all start with a back slash. See reference for complete list.
Strings
A string literal is a sequence of characters enclosed in double quotes:
val helloW = "hello world"
val someEsc = "\\\"\'"
Scala includes a special syntax to avoid these multiple escape characters. if you start and end a string with triple quotes (""") then all the characters such as newlines, quotation marks, and special characters are treated just like others.
println("""Welcome to Simply Scala.
Click 'About' for more information.""")
Boolean
The Boolean type has two possible values and the literals are true or false:
val isBig = true
val isFool = false
Functions
Functions give you the capability to define calculations that you wish to repeat. A function is defined using the "def" key word. The example that follows creates a function that returns the max value for two integer values. Typically a function will return a value of some type. However, for some functions no return value is expected and in this case the return will be of type "unit" meaning no type.
def max(x: Int, y: Int): Int = {
if (x > y) x
else y
}
The name of the function,"max" in this case, follows the "def" then the parameters with their associated types within parentheses. Type annotation is added after the parameter name and preceded by a colon.This function has two parameters of type Int. Then the return type is defined following the colon, again Int in this case. Finally there is an equal sign and the function body enclosed in curly brackets. Once you have defined a function you can use it by calling it with the appropriate parameters.
max(6,7)
Functions can make recursive calls to themselves. Recursive functions form an alternative way of controlling iterations. In the following function that computes the Greatest Common Divisor no variables are required for intermediate values.
def gcd(x: Long, y: Long): Long =
if (y == 0) x else gcd(y, x % y)
Compare this to the earlier version written with a "while" loop.
gcd(96,128)
Everything is an Object
Scala is an Object Oriented language. The underlying premis, like other OO languages, is that there are objects that contain state and this state is manipulated or accessed by means of Methods. Method is the name given to functions that form the programmers interface to Objects. Objects are defined by the means of a class hierarchy. When you define a class you are also defining a new type of object. This new type and those already defined such as Int or Double are treated in a uniform way. The benefits of this uniformity, everything is an object, will soon become apparent. You can start by defining an object that represents a point.
class Point {
var x=0
var y=0
}
This is an abstract definition for the object. An instance of of the object can be created by using the "new" keyword.
val p=new Point
The variables within an Object can be accessed by using "."
p.x=3
p.y=4
You can retrieve the state in the same way.
println(p.x,p.y)
Setting the variables individually each time an instance of a new point is created is time consuming. By adding parameters to the class definition then the instance will be constructed with the desired values.
class Point( ix:Int,iy:Int){
var x=ix
var y=iy
}
And then create a point. Test it with println as before
val p=new Point(3,4)
Now suppose you would like to add two points together to create a new point. The equivalent of vector addition. Then you may add an appropriate method to do so.
class Point( ix:Int,iy:Int){
var x=ix
var y=iy
def vectorAdd(newpt:Point):Point={
new Point(x+newpt.x,y+newpt.y)
}
}
Given this definition two points can be created and their vector addition made.
val p1=new Point(3,4)
val p2=new Point(7,2)
val p3=p1.vectorAdd(p2)
println(p3.x,p3.y)
So far this looks pretty much as a Java programmer would expect. However,it would be more natural to write "p1+p2". In Scala you can do so. Method names can be composed using almost of the non-alphanumeric symbols. A few combinations are reserved and you will get an error if you try to use them. So the class can be rewritten to use "+" and a method for "-" created too.
class Point( ix:Int,iy:Int){
var x=ix
var y=iy
def +(newpt:Point):Point={
new Point(x+newpt.x,y+newpt.y)
}
def -(newpt:Point):Point={
new Point(x-newpt.x,y-newpt.y)
}
override def toString="Point("+x+","+y+")"
}
val p1=new Point(3,4)
val p2=new Point(7,2)
val p3=new Point(-2,2)
val p4=p1+p2-p3
println(p4.x,p4.y)
With this arrangement you can create a very natural looking vector calculus and a whole lot more readable than the traditional equivalent.
In Scala there is a further simplification of the class creation syntax with the introduction of "case classes". Taking the Point class above it can be expressed as a case class.
case class Point(x:Int,y:Int){
def +(newpt:Point)=Point(x+newpt.x,y+newpt.y)
def -(newpt:Point)=Point(x-newpt.x,y-newpt.y)
override def toString="Point("+x+","+y+")"
}
val p1=Point(3,4)
val p2=Point(7,2)
val p3=Point(-2,2)
p1+p2-p3
You notice that the explicit definition of the class fields are no longer required. You also see that the "new" is not required to create a new instance. The Scala compiler recognises that the new instance is required and creates it for you. Too note that in this case the curly brackets have been dropped in the "def". They are not required as the right hand side of the "def" is a simple expression and not statements. This is a general property of "def" and not just limited to case classes. Lastly see that the return type has been dropped in the function. Scala can infer what this is from the function definition.
This is just a small taste of why Scala is much more concisely written than the equivalent Java code. As you learn more Scala you will find many more code patterns that can be expressed in concise syntax too.
Everything is an object. As such, you may have wondered why the example above was not written more in Java style.
p1.+(p2.-(p3))
This is one of the nice syntactic features of Scala that helps to give clarity and uniformity to your code. You may leave out the parentheses and dots as Scala can infer were they belong. It is this carefully thought out syntax that allows you to implement Domain Specific Languages (DSLs). So all objects, including numbers are just objects with methods. For example you can perform the + method on the number 1 with the extended syntax too.
(1).+(2)
All the base types are in fact objects too that can be used and extended just like any other object.
Note the first set of parentheses around the "1" are required here to remove an ambiguity. "1." is a type Double and the result would be type Double rather than Int. Try making the change.
switch - Pattern match
You may already be familiar with the 'switch' with 'case' form used in many languages to allow multi-way branching on value. In Scala this concept is extended to provide full algebraic pattern matching using 'match'. However, the simple switch on value can also be represented easily with match.
def decode(n:Int){
n match {
case 1 => println("One")
case 2 => println("Two")
case 5 => println("Five")
case _ => println("Error")
}
}
decode(2)
The '=>' symbol is used to separate the match pattern from the expression or block to be evaluated. The '_' symbol is used in Scala to mean wild-card or in this case match anything. The last case statement behaves like default in the classical switch. 'match', like most other functions returns a value so the above function could be written more concisely.
def decode(n:Int){
println(n match {
case 1 => "One"
case 2 => "Two"
case 5 => "Five"
case _ => "Error"
}
)
}
decode(3)
Unlike the traditional Java 'switch' the above mapping can easily be reversed.
def encode(s:String){
println(s match {
case "One" => 1
case "Two" => 2
case "Five" => 5
case _ => 0
}
)
}
encode("Five")
For the next example a binary tree is defined with internal nodes and values stored on leaf nodes. A function is created to find the value in the tree associated with the given key. Now see how the case pattern matching is used to determine the node type and bind names to the parameters, the lower case letters k,l,r and v. These are called pattern variables. They may also be constants, indicated by a starting uppercase letters or a literal. In which case the constant value is matched directly with the instances field value.
abstract class TreeN
case class InterN(key:String,left:TreeN,right:TreeN) extends TreeN
case class LeafN(key:String,value:Int) extends TreeN
def find(t:TreeN,key:String):Int={
t match {
case InterN(k,l,r) => find((if(k>=key)l else r),key)
case LeafN(k,v) => if(k==key) v else 0
}
}
// create a binary tree
val t=InterN("b",InterN("a",LeafN("a",1),LeafN("b",2)),LeafN("c",3))
/* [b]
/ \
[a] c,3
/ \
a,1 b,2
*/
Note the use of the case class constructor to efficiently create a test binary tree. Now you can try the find.
find(t,"a")
find(t,"c")
You may like to try wrapping this up into a Binary tree class, including member methods for adding, finding and deleting entries.
Suppose, for some reason, you would like to hide the key 'c' during the find. A simple modification to the find function does this nicely and illustrates the use of a constant match.
def find(t:TreeN,key:String):Int={
t match {
case InterN(k,l,r) => find((if(k>=key)l else r),key)
case LeafN("c",_) => 0
case LeafN(k,v) => if(k==key) v else 0
}
}
Notice the use of '_' as a wild card to match any value and remember that the case statements are evaluated in order.
Static Typing and Inferencing
Scala is a statically typed language, all the variables and functions have types that are fully defined at compile time. Using a variable or a function in a way inappropriate for the type will give compiler type errors. This means that the compiler can find many unintended programming errors for you before execution. However, you will have noticed that in the examples there are few type definitions. This is because Scala can usually infer what type a variable must be from the way you have used it.
For example, if you write 'val x=3' then the compiler infers that 'x' must be type Int as '3' is a integer literal. In a few situations the compiler will not be able to decide what you intended and will generate a type ambiguity error. In these cases you simply add the intended type annotation.
In general you must define function parameter types, however the compiler can usually infer the return type so it can usually be omitted. The exception to this rule is if you define recursive functions, ones that call themselves, you must define the return type.
Type inferencing dramatically reduces the amount of typing you must do and gives a great deal more clarity to the code. It is this type inferencing that gives Scala the feel of being dynamically typed.
Learning Scala and this tutorial
The aim of this tutorial is to give you a rapid overview of the basic features of Scala in a "hands-on" manner. It focuses on the essential things you need to know to get started but avoids going into to much depth. To fully understand Scala and discover more features click on 'Learn More Scala'. There you will find books and many other excellent learning materials to extend your knowledge. Then come back to Simply Scala to try the examples.
Or continue with the next part of the Tutorial by clicking on the Tab.
This tutorial may be downloaded and printed (one machine readable copy and one print copy per page) for personal, noncommercial use only, provided that any material downloaded or printed remains intact and that all copies include the following notice in a clearly visible position: "Copyright © 2009 A.J.Bagwell All rights reserved."