Names
In the previous section we introduced a lot of new concepts. In this section we'll explore one of those concepts: naming values.
We use names to refer to things. For example, "Professeur Emile Perrot" refers to a very fragrant rose variety, while "Cherry Parfait" is a highly disease resistant variety but 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. We will sometimes say names are bound to values, or a name introduces a binding. Wherever we would write out a value we can instead use its name, if the value has a name. In other words, a name evaluates to the value it refers to. 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
We have already seen an example of declaring an object literal.
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 this in the console a few times.
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.
We might wonder about the type of the object we've just created. We can ask the console about this.
:type Example
// Example.type
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.
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, and to look in a bit more depth at the differences between object
and val
.
We already know about expressions.
They are a part of a program that evaluates to a value.
A declaration or definition is another part of a program, but do not evaluate to a value.
Instead they give a name to something---not always to a value as you can declare types in Scala, though we won't spend much time on this.
Both object
and val
are declarations.
One consequence of declarations being separate from expressions is we can't write program 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
The Top-Level
It seems a bit unsatisfactory to have both object
and val
declarations, as they both give names to values.
Why not just have val
for declaring names, and make object
just create objects without naming them?
Can you declare an object literal without a name?
<div class="solution"> 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. </div>
Scala distinguishes between what is called the top-level and other code.
Code at the top-level is code that doesn't have any other code wrapped around.
In other words it is something we can write in a file and Scala will compile without having to wrap it in an object
.
We've seen that expressions aren't allowed at the top-level.
Neither are val
definitions.
Object literals, however, are.
This distinction is a bit annoying. Some other languages don't have this restriction. In Scala's case it comes about because Scala builds on top of the Java Virtual Machine (JVM), which was designed to run Java code. Java makes a distinction between top-level and other code, and Scala is forced to make this distinction to work with the JVM. The Scala console doesn't make this top-level distinction (we can think of everything written in the console being wrapped in some object) which can lead to confusion when we first start using Scala.
If an object literal is allowed at the top-level, but a val
definition is not, does this mean we can declare a val
inside an object literal?
If we can declare a val
inside an object literal, can we later refer to that name?
<div class="solution"> 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
// val b = a + 1
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// val hi = "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
// object Two {
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// }
// ^
// error:
// A pure expression does nothing in statement position; you may be omitting necessary parentheses
// val one = aNumber
// ^^^
// error:
// A pure expression does nothing in statement position; you may be omitting necessary parentheses
// val hi = "Hi!"
// ^
We have to tell Scala we want to refer to the name hi
defined inside the object Example
.
</div>
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
// error:
// Not found: hi
// val a = 1
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// val hi = "Hi!"
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// object Example1 {
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// object Example2 {
// ^
// 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
// val answer = a + Two.b
// ^
// error:
// Line is indented too far to the left, or a `}` is missing
// object One {
// ^
// error:
// A pure expression does nothing in statement position; you may be omitting necessary parentheses
// val one = aNumber
// ^^^
// error:
// A pure expression does nothing in statement position; you may be omitting necessary parentheses
// object Example {
// ^
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.
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.
Exercises {-}
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
<div class="solution">
A simple example to get started with. answer
is 1 + 2
, which is 3
.
</div>
object One {
val a = 1
object Two {
val a = 3
val b = 2
}
object Answer {
val answer = a + Two.b
}
}
<div class="solution">
Another simple example. answer
is 1 + 2
, which is 3
. Two.a
is not in scope where answer
is defined.
</div>
object One {
val a = 5
val b = 2
object Answer {
val a = 1
val answer = a + b
}
}
<div class="solution">
Here Answer.a
shadows One.a
so answer
is 1 + 2
, which is 3
.
</div>
object One {
val a = 1
val b = a + 1
val answer = a + b
}
<div class="solution">
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.
</div>
object One {
val a = 1
object Two {
val b = 2
}
val answer = a + b
}
<div class="solution">
This code doesn't compile as b
is not in scope where answer
is declared.
</div>
object One {
val a = b - 1
val b = a + 1
val answer = a + b
}
<div class="solution">
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.
</div>