Once classes
Background
Once classes represent a mechanism to specify unique values in a program. The values behave like any other objects, but preserve their identity at creation time. In other words, only a single distinguishable instance of a class is created regardless of the number of times the creation is used. This enables discrimination over the values of multi-branch constructs similarly to integers.
Declaration
A once class is declared with a keyword once
in front of the class declaration. All creation procedures listed in the declaration should be once procedures. As usual, they can be used to initialize some attributes:
once class DIRECTION
create
down,
left,
right,
up
feature {NONE} -- Creation
down once y_scroll := 3 end
left once x_scroll := -1 end
right once x_scroll := 1 end
up once y_scroll := -3 end
feature -- Access
x_scroll: INTEGER assign set_x_scroll
-- The number of columns to scroll when current direction is used.
-- Positive value is used for scrolling right, negative for scrolling left.
y_scroll: INTEGER
-- The number of lines to scroll when current direction is used.
-- Positive value is used for scrolling down, negative for scrolling up.
scroll (p: POINT): POINT
-- Compute the position of `p` after scrolling.
require
non_negative_x: p.x >= 0
non_negative_y: p.y >= 0
do
create Result.make ((p.x + x_scroll).max (0), (p.y + y_scroll).max (0))
ensure
non_negative_x: Result.x >= 0
non_negative_y: Result.y >= 0
end
set_x_scroll (new_x_scroll: like x_scroll)
-- Set `x_scroll` to the given value.
require
same_direction: x_scroll.sign = new_x_scroll.sign
do
x_scroll := new_x_scroll
ensure
x_scroll_set: x_scroll = new_x_scroll
end
end
Once classes are automatically frozen, i.e. cannot be used as parents of other classes.
Access
Objects of a once class can be created using the regular object creation syntax, for example,
create direction.up -- Creation instruction
foo (create {DIRECTION}.up) -- Creation expression
where the variable direction
is of type DIRECTION
.
There is also a simplified version for creation expressions: the keyword create
can be omitted:
foo ({DIRECTION}.up) -- Creation expression
Semantics
Uniqueness
For any creation instruction only a single object is created. Successive attempts to create an object with the same creation procedure yields the object obtained the first time.
create direction.up
print (direction = {DIRECTION}.up) -- Prints `True`: the same creation procedure is used.
create direction.down
print (direction = {DIRECTION}.up) -- Prints `False`: different creation procedures are used.
In particular, this allows for using equality tests in conditionals to check the value of the object
if direction = {DIRECTION}.up then
-- Do something.
else
-- Do something else.
end
Also, given that only one instance created with a given creation procedure exists, changing fields of the object updates the fields for any other access:
print ({DIRECTION}.left.x_scroll) -- Prints `-1`.
{DIRECTION}.left.x_scroll := -4
print ({DIRECTION}.left.x_scroll) -- Prints `-4`.
Multi-branch constructs
Single values
Expressions based on a once class can be used as inspect expressions in multi-branch constructs. Case values are discriminated by the once class creation procedures:
print (inspect direction
when {DIRECTION}.down then "Down"
when {DIRECTION}.left then "Left"
when {DIRECTION}.right then "Right"
when {DIRECTION}.up then "Up"
end)
Intervals
The order of creation procedures is essential when multi-branch constructs are using intervals. The multi-branch constructs rely on the relative order of creation procedures listed in the creation clause of the class. For example, if days of week are modelled using the following once class
once class DAY_OF_WEEK
create Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
...
then it could be used in the interval:
inspect day
when {DAY_OF_WEEK}.Monday .. {DAY_OF_WEEK}.Friday then
is_weekend := False
else
is_weekend := True
end
SCOOP
The mechanism of once classes is orthogonal to concurrency. Therefore, if no once keys are specified, they follow the default (once-per-thread) behavior, i.e. every thread in a SCOOP application has its own set of unique instances, and instances between two different threads are different. If instances have to be unique throughout the whole system regardless of threading, the corresponding once key ("PROCESS"
) should be specified. In this case the once value is instantiated in its own SCOOP region and only once.
Consider the following code snippet:
local
d1, d2: separate DIRECTION
do
create d1.up
create d2.up
print (d1 = d2)
end
With the declaration above, it prints False
because instances are created in different regions using the once-per-thread creation procedure up
.
If the declaration of this procedure is changed to look like
up once ("PROCESS") y_scroll := -3 end
the code prints True
instead, because the single instance of the class is created for all threads and SCOOP regions.
Other use cases
Iteration
The ability to iterate over all values of a once class can be added using manifest arrays:
instances: ITERABLE [DAY_OF_WEEK]
-- All days of week.
once
Result :=
<<{DAY_OF_WEEK}.Sunday,
{DAY_OF_WEEK}.Monday,
{DAY_OF_WEEK}.Tuesday,
{DAY_OF_WEEK}.Wednesday,
{DAY_OF_WEEK}.Thursday,
{DAY_OF_WEEK}.Friday,
{DAY_OF_WEEK}.Saturday>>
ensure
class
end
Then, assuming that there is a feature name
in the class DAY_OF_WEEK
that returns the name of the specified day, the following symbolic loop would print all days of week:
⟳ d: {DAY_OF_WEEK}.instances ¦ print (d.name + " ") ⟲
Singleton
A commonly known programming pattern called "singleton" can be coded using some other means of Eiffel, but with once classes it becomes straightforward: just use a once class with a single creation procedure.