Names
We use names to refer to things. For example, "Professeur Emile Perrot" refers to a very fragrant variety of rose, while "Cherry Parfait" is a highly disease resistant rose variety that barely smells at all. Much ink has been spilled, and many a chin stroked, on how exactly this relationship works in spoken language. Programming languages are much more constrained, which allows us to be much more precise: names refer to values. If we have named a value, wherever we would write out the expression that creates the value we can instead use the name. In other words, a name evaluates to the value it refers to. We will sometimes say names are bound to values, or a name introduces a binding. This naturally raises the question: how do we give names to values? There are several ways to do this in Scala. Let's see a few.
Object Literals
One way to name a value is to use an object literal. Here is an example.
object Example {
Image.circle(100).fillColor(Color.paleGoldenrod).strokeColor(Color.indianRed).draw()
}
This is a literal expression, like other literals we've seen so far, but in this case it creates an object with the name Example
.
When we use the name Example
in a program it evaluates to that object.
Example
// Example.type = Example$@76c39258
Try writing Example
a few times in a worksheet.
Do you notice any difference in uses of the name?
You might have noticed that the first time you entered the name Example
a picture was drawn, but on subsequent uses this didn't happen.
The first time we use an object's name the body of the object is evaluated and the object is created.
On subsequent uses of the name the object already exists and is not evaluated again.
We can tell there is a difference in this case because the expression inside the object calls the draw
method.
If we replaced it with something like 1 + 1
(or just dropped the call to draw
) we would not be able to tell the difference.
We'll have plenty more to say about this in a later chapter.
Notice that the type of Example
is Example.type
, a unique type that no other value has.
val
Declarations
Declaring an object literal mixes together object creation and defining a name.
It would be useful if we could separate the two, so we could give a name to a pre-existing object.
(Remember that all values are objects.)
A val
declaration allows us to do this.
We use val
by writing
val <name> = <value>
replacing <name>
and <value>
with the name and the expression evaluating to the value respectively.
For example
val one = 1
val anImage = Image.circle(100).fillColor(Color.red)
These two declarations define the names one
and anImage
.
We can use these names to refer to the values in later code.
one
// res0: Int = 1
anImage
// res1: Image = FillColor(
// image = Circle(d = 100.0),
// color = RGBA(
// r = UnsignedByte(value = 127),
// g = UnsignedByte(value = -128),
// b = UnsignedByte(value = -128),
// a = Normalized(get = 1.0)
// )
// )
Declarations
We've talked about declarations and definitions above. It's now time to be precise about what these terms mean.
We already know about expressions.
They are a part of a program that evaluate to a value.
A declaration or definition is a different part of a program.
Declarations do not evaluate to a value.
Instead they give a name to something (not always to a value as we can declare types in Scala.)
Both object
and val
are declarations.
One consequence of declarations being separate from expressions is we can't write programs like
val one = ( val aNumber = 1 )
because val aNumber = 1
is not an expression and thus does not evaluate to a value.
We can however write
val aNumber = 1
// aNumber: Int = 1
val one = aNumber
// one: Int = 1
Declaration Syntax
A declaration gives a name to a value.
An object literal is a declaration with syntax
object name {
body
}
where name
is the name of the object, and body
is zero or more declarations or expressions.
A val
declaration has syntax
val name = expression
where expression
is an expression and name
is the name given to the value the expression evaluates to.
Exercise: Anonymous Objects
It seems a bit unsatisfactory to have both object
literals and val
declarations, as they both give names to values.
Can you declare an object literal without a name?
If we can, we could then use val
for declaring names, and use object
to create objects without naming them.
No, Scala doesn't allow us to do this. For example, we can't write
object {}
We have to give a name to any object literal we create.
Exercise: Objecting to Objects
If we can't have anonymous objects, why do we have val
?
If all values are objects, why not just use object
to give names to values of instead of val
?
Remember that every object
defines a unique type.
So we cannot define an object
that is also an Int
, for example.
It will get hard to get to write code if we never have two values of the same type.
Exercise: Values in Objects
Can we declare a val
inside an object literal?
If we can declare a val
inside an object literal, can we later refer to that name?
We sure can!
We can put a val
inside an object literal like so:
object Example {
val hi = "Hi!"
}
We can then refer to it using the .
syntax we've been using already.
Example.hi
// res4: String = "Hi!"
Note that we can't use hi
on it's own
hi
// error:
// Not found: hi
// error:
// Line is indented too far to the left, or a `}` is missing
// error:
// Line is indented too far to the left, or a `}` is missing
// error:
// Line is indented too far to the left, or a `}` is missing
// error:
// Line is indented too far to the left, or a `}` is missing
// error:
// A pure expression does nothing in statement position
// error:
// A pure expression does nothing in statement position
We have to tell Scala we want to refer to the name hi
defined inside the object Example
.
Scope
If you did the last exercise (and you did, didn't you?) you'll have seen that a name declared inside an object can't be used outside the object without also referring to the object that contains the name. Concretely, if we declare
object Example {
val hi = "Hi!"
}
we can't write
hi
We must tell Scala to look for hi
inside Example
.
Example.hi
// res8: String = "Hi!"
We say that a name is visible in the places where it can be used without qualification, and we call the places where a name is visible its scope.
So using our fancy-pants new terminology, hi
is not visible outside of Example
, or alternatively hi
is not in scope outside of Example
.
How do we work out the scope of a name?
The rule is fairly simple: a name is visible from the point it is declared to the end of the nearest enclosing braces (braces are {
and }
).
In the example above hi
is enclosed by the braces of Example
and so is visible there.
It's not visible elsewhere.
We can declare object literals inside object literals, which allows us to make finer distinctions about scope. For example in the code below
object Example1 {
val hi = "Hi!"
object Example2 {
val hello = "Hello!"
}
}
hi
is in scope in Example2
(Example2
is defined within the braces that enclose hi
).
However the scope of hello
is restricted to Example2
, and so it has a smaller scope than hi
.
What happens if we declare a name within a scope where it is already declared?
This is known as shadowing.
In the code below the definition of hi
within Example2
shadows the definition of hi
in Example1
object Example1 {
val hi = "Hi!"
object Example2 {
val hi = "Hello!"
}
}
Scala let's us do this, but it is generally a bad idea as it can make code very confusing.
We don't have to use object literals to create new scopes. Scala allows us to create a new scope just about anywhere by inserting braces. So we can write
object Example {
val good = "Good"
// Create a new scope
{
val morning = good ++ " morning"
val toYou = morning ++ " to you"
}
val day = good ++ " day, sir!"
}
morning
(and toYou
) is declared within a new scope. We have no way to refer to this scope from the outside (it has no name) so we cannot refer to morning
outside of the scope where it is declared.
If we had some secrets that we didn't want the rest of the program to know about this is one way we could hide them.
Braces on their own are known as a block expression in Scala.
Not only do they define a new scope,
but, as their name suggests, they are an expression and therefore evaluate to a value.
That value is the last expression in the block.
So in the following the block evaluates to 3
.
{
val one = 1
val two = 2
1 + 2
}
// res11: Int = 3
We can also give a name to this value.
val three = {
val one = 1
val two = 2
1 + 2
}
// three: Int = 3
The way nested scopes work in Scala is called lexical scoping. Not all languages have lexical scoping. For example, Ruby and Python do not, and Javascript has only recently acquired lexical scoping. It is the authors' opinion that creating a language without lexical scope is an idea on par with eating a bushel of Guatemalan insanity peppers and then going to the toilet without washing your hands.
Exercise: Scoping Out Scope
Test your understanding of names and scoping by working out the value of answer
in each case below.
val a = 1
val b = 2
val answer = a + b
A simple example to get started with. answer
is 1 + 2
, which is 3
.
object One {
val a = 1
object Two {
val a = 3
val b = 2
}
object Answer {
val answer = a + Two.b
}
}
Another simple example. answer
is 1 + 2
, which is 3
. Two.a
is not in scope where answer
is defined.
object One {
val a = 5
val b = 2
object Answer {
val a = 1
val answer = a + b
}
}
Here Answer.a
shadows One.a
so answer
is 1 + 2
, which is 3
.
object One {
val a = 1
val b = a + 1
val answer = a + b
}
This is perfectly fine. The expression a + 1
on the right hand side of the declaration of b
is an expression like any other so answer
is 3
again.
object One {
val a = 1
object Two {
val b = 2
}
val answer = a + b
}
This code doesn't compile as b
is not in scope where answer
is declared.
object One {
val a = b - 1
val b = a + 1
val answer = a + b
}
Trick question! This code doesn't work. Here a
and b
are defined in terms of each other which leads to a circular dependency that can't be resolved.