The following is the Aldwych code which defines a simple object that just keeps a count from which values may be added and subtracted:
#count(sum)~ { add(n) | sum+=n; sub(n) | sum-=n; val- |>sum }With this definition,
A<-count(100)
would create
a count
object, named A
, which initially stores
100. Then A.add(n)
is an Aldwych statement which causes the value in variable n
to
be added to the value in the object referred to by A
, while
A.sub(n)
causes the value in variable n
to be
subtracted from it. A.val->m
causes the value in the
object referred to by A
to be copied into variable
m
. The semi-colons here are clause separators, so one
is not needed at the end of the last rule, but it would not cause an
error to put one in as a completely empty rule is ignored.
If the code for count
was combined with the following:
#main == Screen<-aio\stdio(), Screen.fwrite("Enter initial value: ") .readInt->n .fwrite("Enter amount to add: ") .readInt->a .fwrite("Enter amount to subtract: ") .readInt->b .fwrite("Final amount is: ">+c) .nl, Acc<-count(n), Acc.add(a).sub(b).val->c;in a file which was compiled as in part 1, the result would be a program which prompts for a number and then creates a
count
object initially storing that number from which
a further number is added, another subtracted and the resulting value
in the object printed.
An object descriptor consists of a header, and a set of rules enclosed by braces. Semicolons separate the rules. Each rule has a left-hand side (lhs) and a right-hand side (rhs) separated by a bar.
In this simple form of object descriptor, a header consists of the symbol
#
followed by a list of arguments enclosed by brackets. The
arguments are separated by commas (but in this case there is only one
argument) followed by the symbol ~
. The arguments coincide
with fields for the object and when a new object is constructed each of them
is given a value by the construction call.
The lhs of a rule consists of a message name followed optionally by
some arguments. If there are arguments they are enclosed by brackets
and separated by commas. If the message has a return value, it is
followed by the -
symbol. If the message returns an
object rather than a value, it is followed by a ~
symbol
on the lhs of the rule rather than a -
symbol.
On the rhs of a rule, a+=b
is shorthand for
a<-a+b
. The values of fields in the object may be
changed in this way. Arguments to the message on the lhs may be used as
variables on the rhs, but the variables may not be assigned values.
If a message expects a return value or object, the rhs must contain a
statement consisting of the >
symbol followed by an
expression which gives the return value.
A field may be given a default value. Here is a version of count
in which the sum
field has the default value 0
:
#count(sum<-0)~ { add(n) | sum+=n; sub(n) | sum-=n; val- |>sum }With this, a statement
A<-count()
creates a count
object whose sum
field is initialised to 0
.
A default may be overridden by an object creation call which contains
within it <name><-<value>
, where
<name>
is the name of the field whose default value
is to be overwritten, and <value>
is the value
which overwrites it. Here is a version of the above main
which would accomplish the same as the previous one, but deal with
the version of count
which has a default value for
sum
:
Screen<-aio\stdio(), Screen.fwrite("Enter initial value: ") .readInt->n .fwrite("Enter amount to add: ") .readInt->a .fwrite("Enter amount to subtract: ") .readInt->b .fwrite("Final amount is: ">+c) .nl, Acc<-count(sum<-n), Acc.add(a).sub(b).val->c;An object may have fields that are initialised in its own descriptor and whose initialisation may not be overridden. These fields are listed after the
~
in the header, in a list opening
with <(
and closing with )
, with commas
as separators. Each of these fields must be given a value by an
assignment, the value may be an expression containing other fields.
Here is a version of count
in which the sum
field is initialised to 0
which may not be overwritten:
#count()~ <(sum<-0) { add(n) | sum+=n; sub(n) | sum-=n; val- |>sum }In fact, if the argument list in an Aldwych header is empty, the brackets can be omitted, so the above could be written:
#count~ <(sum<-0) { add(n) | sum+=n; sub(n) | sum-=n; val- |>sum }
If a value of an argument is needed only to create an object, but is not
after that to be used as a field, it should be preceded by a
-
symbol in the header. For example, the following is an
object descriptor for time
objects which take hour and minute
values when they are created but store their time internally purely in
minutes:
#time(-hrs,-mins)~ <(totmins<-hrs*60+mins) { add(n) | totmins+=n; sub(n) | totmins-=n; hours -|>totmins/60; minutes-|>totmins//60; toString-|>"">+totmins//60>":">+totmins/60; clone~ |>time(0,totmins) }Note this is not quite correct, as the
toString
method
which returns the time as a string in "hh:mm" format does not correctly
handle cases where the number of minutes is less than ten. To do that
requires features covered later. The objects created by this descriptor
can take an add(n)
message to advance the time they store
by n
minutes and a sub(n)
to take it back by
n
minutes. They can also take hours
messages
which return the number of hours in the time they store, rounding down
the minutes, and minutes
messages, giving the number of minutes
past the hour of the time they store.
The clone
message which time
objects can take
returns a copy of the object itself (showing the notation for a
message that returns an object). Assignment of object variables creates
multiple references to the same object, so A<-B
, for
example, causes A
to refer to the same object that B
refers to. Therefore a method such as clone
is needed to
copy objects; A<-B.clone
would cause A
to refer to an object that is a copy of that referred to by B
,
assuming B
is of a type that takes a clone
message which behaves similarly to clone
in time
above.
That object assignment causes aliasing could be demonstrated by the
following main
method combined with time
:
#main == Screen<-aio\stdio(), Screen.fwrite("Enter initial hours: ") .readInt->hrs .fwrite("Enter initial minutes: ") .readInt->mins .fwrite("Enter minutes to add: ") .readInt->advance .fwrite("Final time (1) is: ">s1) .nl .fwrite("Final time (2) is: ">s2) .nl , Time1<-time(hrs,mins), Time2<-Time1, Time1.add(advance).toString->s1, Time2.toString->s2;It is possible that both times printed will be the same, since
Time1
and Time2
refer to the same object.
However, it is not inevitable, due to Aldwych being a concurrent language.
It is possible for Time2.toString->s2
to be fully
evaluated before Time1.add(advance).toString->s1
. Therefore
the second time printed could be the initial time. The first time printed
will always be the time after the advance(n)
message is
evaluated however, since the order in which messages are dealt with is
fixed if one follows another in a sequence separated by the .
symbol.