1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
|
$Id: README,v 1.8 1996/01/23 16:54:28 tiggr Exp $
TOM
SPEED
Speed optimizations may only be contemplated when they do not
hamper the elegance or usability of the language.
For instance, static method binding, which is a popular and
effective speed optimization, can not be allowed since this
hampers the usability of NSLs (Non Source Libraries, see below).
On the other hand, static instance variable binding is an
allowable optimization, since allowing it in the language
definition does not refrain the programmer from always using
dynamically bound methods to reference or set instance variables
(not that this trick solves the actual problem).
Also in favor of speed is static binding of operations on basic
types. If such static binding is undesirable, one can define
objects containing such static types. Such objects provide the
programmer with the desired flexibility. Of course, all
occurences of the basic types not inside such a wrapper are still
statically bound and not overridable. However, since the builtin
operations on basic types only involve rather strictly defined
arithmetic and logic operations the decrease in the usability is
not considered a issue.
STATIC BINDING
As has been explained, instance variables and the builtin
operations on the basic types are statically bound. Especially in
the first case, this introduces the problem that if the number of
instance variables of a class changes (or is only increased, in
case all instance variables are private to the class), all
subclasses need to be recompiled. This is a problem known as the
fragile superclass. However, it is not considered to be important
enough to use dynamic instance variable binding despite the speed
penalty.
USABILITY
A popular OO term is reusability. A (related) term, which is just
as important, is _usability_, i.e. the way in which classes,
especially those written by others, can be used. Usability is
severely hampered in case of bugs in classes provided in a Non
Source Library (NSL), i.e. a library of which the source code is
unavailable. Even under such circumstances, usability can still
reach an acceptable level if all methods are dynamically bound and
class definitions can be adjusted, meaning that method
implementations can be added.
As an example, a very interesting method to override is the
allocation of new instances of a class. If a class X from an NSL
is unusable due to bugs or a bad design, one can replace its
allocation method to return something different from an instance
of X, for instance an object over which one does have source
control. (Obviously, due to type matching restrictions, the
object being returned must be an instance of a subclass of X.)
Since all methods are dynamically bound, _all_ usage of the class
X, also within the library proper, will effectively use the
replacement class set up by overriding the allocation method.
Replacing the alloc method of a class is not sufficient to fix
bugs in classes which are also subclassed, since the overriding
allocation only works for instances of the class, not of its
proper subclasses. Objective-C provides class posing as a runtime
hack, which must be explicitly done by the imposter as early as
possible, for instance in `+load'. However, in a compile time
type checked system, this solution implies that the imposter can
not add functionality which can actually be used, unless the
definition of the class being posed as is also adjusted. However,
this is troublesome, to say the least. Combining these two
observations leads one to believe that class posing should be
possible through a simple keyword in the language.
Due to the statically bound instance variables, a posing class can
not add instance variables.
BASIC TYPES
The basic types are:
name bitsize kind
----------------------------------------------------------------------
bool basic, builtin, bool
char 8 basic, builtin, numeric, integer
short 16 basic, builtin, numeric, integer
int 32 basic, builtin, numeric, integer
long 64 basic, builtin, numeric, integer
float 32 basic, builtin, numeric, fp
double 64 basic, builtin, numeric, fp
object basic, builtin, object
----------------------------------------------------------------------
Clearly, all basic values are unboxed; all objects are boxed.
The derived types are:
name kind
----------------------------------------------------------------------
tuple derived, tuple
array derived, array, generic, object
----------------------------------------------------------------------
Semantically, tuples are unboxed. Arrays, which are objects, are
boxed.
OPERATORS
The statically bound operations are easily recognizable as they
come with special syntax.
- minus numeric -> same
~ bitwise invert integer -> top
! not builtin|object -> bool
* multiply numeric * numeric -> top
/ divide id
% modulo integer * integer -> top
+ add numeric * numeric -> top
- subtract id
<< shift left integer * integer -> top
>> shift right id
& bitwise and integer * integer -> top
| bitwise or id
^ bitwise eor id
< lt numeric * numeric -> bool
<= le id
== eq any * same -> bool
!= ne id
=> ge numeric * numeric -> bool
> gt id
&& s.c. and bool * bool -> bool
|| s.c. or id
-> s.c. implies id
?: ite bool * any * same -> same
Integer arithmetic is performed using ints, unless one of the
operands is a long, in which case the computation is performed
using longs, and the result will be a long. Otherwise, the result
is an int. Floating point arithmetic is performed using and
producing floats, unless one of the arguments is a double, in
which case both computation and result are doubles.
Casting a numeric type to another numeric type can only be
accomplished through assignment, or to promotion due to arithmetic
rules. Thus, explicit narrowing casts are only possible using
assignment.
TUPLES
A tuple is a product type, much like a struct in C. However, the
operations on tuple typed values are limited, since it is not
allowed (i.e. impossible) to declare variables of a tuple type.
This is deliberate; one should use objects to store complex state.
Examples of things that can be done with tuples are:
The tuple can be the return type of a method. E.g.,
`(int) foo' is a method returning an int; `(int, int) foo'
is a method returning two ints.
A tuple can be the type of arguments to a method. For
instance, `void foo (int, int) (a, b)' declares a method
accepting a tuple with two integers as its argument.
Tuples can be used in assignment statements to stand for
simultaneous assignment (and a swap can be written as `(a,
b) = (b, a)').
If a method returns a tuple, like `(int, float) foo' and
the value of the int is not necessary, partial assignment
can be performed, as in `(, a) = [rcv foo]'.
Two tuples have the same type if they have the same number of
fields and corresponding fields have the same type.
The notation used here for tuples seems to imply that a tuple with
a single component is not tuple-typed but has the type of the
component. Actually, the tuple has both types, whereas the
component itself only has its own type. This is consistent with
normal use of parenthesized expressions.
[Aside: The first version of this writing suggested the
possibility of the existence of tuple-typed variables, with the
following note: One other operation (it's not really a distinct
operation) is possible on a tuple: it can be assigned to a
tuple-typed variable. However, in a first implementation of the
language, the tuple typed values will probably not be first class
values, and tuple-typed variables can not be declared. End aside.]
ARRAYS
An array is an indexable collection of values. Such values can be
of any type. Basic operations like querying, replacing elements
and extending the array are statically bound. In other respects,
an array behaves like an object. There are, however, some subtle
issues involved.
`array (int) a' declares A to be a reference to an array
containing integers. (XXX I'm not sure of this syntax.)
An array is a generic type, in that it its elements can have any
type, be it int, double, object or what else.
An array can have fixed bounds, a fixed lower and variable higher
bound, or variable lower and higher bounds. An array which is
created with fixed bounds initially has all elements set to the
default element value, with is 0 for numbers, false for booleans
and void for object references. The default value for tuples is a
tuple filled with the default values of the types of its fields.
Since an array is an object, it can be subclassed. However, only
a concrete array can be subclassed. The generic array (which is
parameterized in its type) can not be subclassed. The reason for
this has to do with the issue of usability and
recompilation-for-a-different-type-parameter. (XXX Yeah, sure.)
Given the restrictions on array subclassing, array subclassing is
much like creating a class which has an array as one of its
instance variables.
OVERLOADING
The method `(int) value' is different from `(double) value'.
Similarly, `(void) setValue: (int)' is different from `(void)
setValue: (double)'. This distinction on argument and return
types can be made at compile-time.
Overloading on object type is not allowed, since, at compile time,
it is not known how a class may be adapted in the final program.
For instance, suppose the existence of a class A: C, D, a class B:
A, E, and two methods, `(void) foo: (C)' and `(void) foo: (E)'.
Apart from the question how to decide which method to call for an
instance of B, the method to invoke for an A can not be fixed at
the first foo, because A can be adapted later on to also conform
to `E', and either method can be chosen, just as for B.
Since it is considered too expensive to match on argument types at
run time, overloading on object type is not possible.
TYPE DEFINITIONS
Types can be given a name, by using the `typedef' keyword. Thus,
just like `int a' declares a value named a of type int, `typedef
int a' declares a type named a being the type int. This is most
useful in giving names to tuple types, as in `typedef (float,
float) NXPoint'.
Type definition only gives a (different) name to an already
existing type. Hence, type equality is defined in terms of the
basic types. Thus, given `typedef int a; typedef int b;', the
types `a' and `b' are type-equal. However, the compiler could
issue warnings about the possibly unintended passing of an `a'
typed value where a `b' typed value is expected.
Also note that, given the previous definitions of `a' and `b',
`a', `b' and `int' can not be discerned for overloading method
arguments. Only one method may be defined to act on the integer
typed value, be it `a', `b' or `int'.
Each typedef implicitly introduces a set of functions by the same
name, which can be used to convert the type of an expression to
the indicated type. Of course, the two types involved in type
conversion must be compatible. (XXX Define.)
TYPE CONVERSION
Numeric types are convertible to each other.
Object types are convertible to each other, if there exists an
instantiable subclass common to the source type and the
destination type. The reason for this is depicted below.
Suppose the existence of an object of which some instance variable
can be read and set through the methods `(A) value' and `(void)
setValue: (A)', respectively. This ivar can be set to any B, as
long as B is a subclass of A. If the value is then retrieved, the
object is still a B, and must be allowed to be a B. The B can
then used as an argument to another method, which expects an
object of type C, as long as B is a subclass of C. In the same
situation without an explicit assignment to B, the A typed value
retrieved is passed as a C typed argument. This can only be
possible if the B class exists.
TYPES OF ARGUMENTS TO METHODS
If a class P declares a method `(A) foo: (B)', which is redeclared
by a subclass Q to be `(C) foo: (D)', then there are some
restrictions on the types C and D which I yet have to settle
(XXX).
SCOPING
Scoping is lexical. The global scope (within a project) contains
only type and class names. Inheriting from a class brings the
definitions of that class into scope. Within an instance
definition, the global scope is first extended by all class scopes
of the classes inherited, followed by the instance scopes of the
classes inherited. (Thus, the innermost enclosing scope is the
instance scope of the first superclass.)
BINDING
There are a lot of syntactical ways to invoke a method. Some
allow the receiver to be specified implicitly to be the class of
the receiver of the current method.
STATEMENTS
ASSIGNMENT
Assignment is a statement, as in `a = b', which says the the value
of `a' after execution will be the value of `b'. A and B must
have the same type. Tuple assignment, as in `(a_1, a_2, ...) = b'
assigns to each a_i the value of the corresponding field in the
tuple-typed value of b. Any a_i may be empty (an empty string),
indicating that the value of the corresponding field in b is not
retrieved.
RETURN
The return statement looks like an assignment with an empty lhs.
For instance, the statement `= 0' will exit the current method
with a value of 0.
|