ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 3469

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Section 18.1

Chapter 18 · Stateful Objects

401

Note that the two final withdrawals in the previous interaction returned different results. The first withdraw operation returned true because the bank account contained sufficient funds to allow the withdrawal. The second operation, although the same as the first one, returned false, because the balance of the account had been reduced so that it no longer covered the requested funds. So, clearly bank accounts have mutable state, because the same operation can return different results at different times.

You might think that the statefulness of BankAccount is immediately apparent because it contains a var definition. State and vars usually go hand in hand, but things are not always so clear-cut. For instance, a class might be stateful without defining or inheriting any vars because it forwards method calls to other objects that have mutable state. The reverse is also possible: A class might contain vars and still be purely functional. An example would be a class that caches the result of an expensive operation in a field for optimization purposes. To pick an example, assume the following unoptimized class Keyed with an expensive operation computeKey:

class Keyed {

def computeKey: Int = ... // this will take some time

...

}

Provided that computeKey neither reads nor writes any vars, you can make Keyed more efficient by adding a cache:

class MemoKeyed extends Keyed {

private var keyCache: Option[Int] = None override def computeKey: Int = {

if (!keyCache.isDefined) keyCache = Some(super.computeKey) keyCache.get

}

}

Using MemoKeyed instead of Keyed can speed up things, because the second time the result of the computeKey operation is requested, the value stored in the keyCache field can be returned instead of running computeKey once again. But except for this speed gain, the behavior of class Keyed and MemoKeyed is exactly the same. Consequently, if Keyed is purely functional, then so is MemoKeyed, even though it contains a reassignable variable.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 18.2

Chapter 18 · Stateful Objects

402

18.2 Reassignable variables and properties

You can perform two fundamental operations on a reassignable variable: get its value or set it to a new value. In libraries such as JavaBeans, these operations are often encapsulated in separate getter and setter methods, which need to be defined explicitly. In Scala, every var that is a non-private member of some object implicitly defines a getter and a setter method with it. These getters and setters are named differently from the Java convention, however. The getter of a var x is just named “x”, while its setter is named “x_=”.

For example, if it appears in a class, the var definition:

var hour = 12

generates a getter, “hour”, and setter, “hour_=”, in addition to a reassignable field. The field is always marked private[this], which means it can be accessed only from the object that contains it. The getter and setter, on the other hand, get the same visibility as the original var. If the var definition is public, so are its getter and setter, if it is protected they are also protected, and so on.

For instance, consider the class Time shown in Listing 18.2, which defines two public vars named hour and minute:

class Time { var hour = 12 var minute = 0

}

Listing 18.2 · A class with public vars.

This implementation is exactly equivalent to the class definition shown in Listing 18.3. In the definitions shown in Listing 18.3, the names of the local fields h and m are arbitrarily chosen so as not to clash with any names already in use.

An interesting aspect about this expansion of vars into getters and setters is that you can also choose to define a getter and a setter directly instead of defining a var. By defining these access methods directly you can interpret the operations of variable access and variable assignment as you like. For in-

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 18.2

Chapter 18 · Stateful Objects

403

class Time {

private[this] var h = 12 private[this] var m = 0

def hour: Int = h

def hour_=(x: Int) { h = x }

def minute: Int = m

def minute_=(x: Int) { m = x }

}

Listing 18.3 · How public vars are expanded into getter and setter methods.

stance, the variant of class Time shown in Listing 18.4 contains requirements that catch all assignments to hour and minute with illegal values.

class

Time {

 

private[this] var h =

12

private[this] var m =

0

def

hour: Int = h

 

def

hour_= (x: Int) {

 

require(0 <= x && x

< 24)

h

= x

 

}

 

 

def

minute = m

 

def

minute_= (x: Int)

{

require(0 <= x && x

< 60)

m

= x

 

}

 

 

}

 

 

Listing 18.4 · Defining getter and setter methods directly.

Some languages have a special syntactic construct for these variablelike quantities that are not plain variables in that their getter or setter can be redefined. For instance, C# has properties, which fulfill this role. Scala’s convention of always interpreting a variable as a pair of setter and getter methods gives you in effect the same capabilities as C# properties without

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 18.2

Chapter 18 · Stateful Objects

404

requiring special syntax. Properties can serve many different purposes. In the example shown in Listing 18.4, the setters enforced an invariant, thus protecting the variable from being assigned illegal values. You could also use a property to log all accesses to getters or setters of a variable. Or you could integrate variables with events, for instance by notifying some subscriber methods each time a variable is modified (you’ll see examples of this in Chapter 35).

It is also possible, and sometimes useful, to define a getter and a setter without an associated field. An example is the following class Thermometer, which encapsulates a temperature variable that can be read and updated. Temperatures can be expressed in Celsius or Fahrenheit degrees. The class below allows you to get and set the temperature in either measure.

class Thermometer {

var celsius: Float = _

def fahrenheit = celsius * 9 / 5 + 32 def fahrenheit_= (f: Float) {

celsius = (f - 32) * 5 / 9

}

override def toString = fahrenheit +"F/"+ celsius +"C"

}

Listing 18.5 · Defining a getter and setter without an associated field.

The first line in the body of this class defines a var, celsius, which will contain the temperature in degrees Celsius. The celsius variable is initially set to a default value by specifying ‘_’ as the “initializing value” of the variable. More precisely, an initializer “= _” of a field assigns a zero value to that field. The zero value depends on the field’s type. It is 0 for numeric types, false for booleans, and null for reference types. This is the same as if the same variable was defined in Java without an initializer.

Note that you cannot simply leave off the “= _” initializer in Scala. If you had written:

var celsius: Float

this would declare an abstract variable, not an uninitialized one.1

1Abstract variables will be explained in Chapter 20.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Section 18.3

Chapter 18 · Stateful Objects

405

The celsius variable definition is followed by a getter, “fahrenheit”, and a setter, “fahrenheit_=”, which access the same temperature, but in degrees Fahrenheit. There is no separate field that contains the current temperature value in Fahrenheit. Instead the getter and setter methods for Fahrenheit values automatically convert from and to degrees Celsius, respectively. Here’s an example of interacting with a Thermometer object:

scala> val t = new Thermometer t: Thermometer = 32.0F/0.0C

scala> t.celsius = 100

scala> t

res3: Thermometer = 212.0F/100.0C scala> t.fahrenheit = -40

scala> t

res4: Thermometer = -40.0F/-40.0C

18.3 Case study: Discrete event simulation

The rest of this chapter shows by way of an extended example how stateful objects can be combined with first-class function values in interesting ways. You’ll see the design and implementation of a simulator for digital circuits. This task is decomposed into several subproblems, each of which is interesting individually: First, you’ll see a little language for digital circuits. The definition of this language will highlight a general method for embedding domain-specific languages in a host language like Scala. Second, we’ll present a simple but general framework for discrete event simulation. The main task of this framework will be to keep track of actions that are performed in simulated time. Finally, we’ll show how discrete simulation programs can be structured and built. The idea of such simulations is to model physical objects by simulated objects, and to use the simulation framework to model physical time.

The example is taken from the classic textbook Structure and Interpretation of Computer Programs by Abelson and Sussman [Abe96]. What’s different here is that the implementation language is Scala instead of Scheme, and that the various aspects of the example are structured into four software

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 18.4

 

 

 

Chapter 18 · Stateful Objects

406

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

inverter

and-gate

or-gate

Figure 18.1 · Basic gates.

layers: one for the simulation framework, another for the basic circuit simulation package, a third for a library of user-defined circuits, and the last layer for each simulated circuit itself. Each layer is expressed as a class, and more specific layers inherit from more general ones.

The fast track

Understanding the discrete event simulation example presented in this chapter will take some time. If you feel you want to get on with learning more Scala instead, it’s safe to skip ahead to the next chapter.

18.4 A language for digital circuits

We’ll start with a “little language” to describe digital circuits. A digital circuit is built from wires and function boxes. Wires carry signals, which are transformed by function boxes. Signals are represented by booleans: true for signal-on and false for signal-off.

Figure 18.1 shows three basic function boxes (or gates):

An inverter, which negates its signal.

An and-gate, which sets its output to the conjunction of its inputs.

An or-gate, which sets its output to the disjunction of its inputs.

These gates are sufficient to build all other function boxes. Gates have delays, so an output of a gate will change only some time after its inputs change.

We’ll describe the elements of a digital circuit by the following set of Scala classes and functions. First, there is a class Wire for wires. We can construct wires like this:

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index