NOTE - where to put stuff about "is natural a type, is non-zero a type (for div)" Objects and polymorphism, and some standard chalenges Some definitions: Object a record of operations Operation a function specific to, and depending on, and part of, an obect Expectation what an operation (in high level, abstract, programmer based sense) expects of its operands Behaviour a specification of what an object ('s operations) does '' vs State behaviour can be modeled as procedure plus state Types subset of behaviour that satisfies expectation also a set of object which share this subset of a behaviour sum of types of operations Subtypes relation between behaviours co variance/ contra-variance Polymorphism two objects in the same type conform to same expectation and so can equally be arguments to an operation Examples.... Behaviours(?) that don't fit the model stack total ordering Parameterised behaviours stack of plates ordering of numbers Class: this that creates object - the set of those object - hence like a type Other Issues infix (mult, add) declared in parent (field) refined in various children e.g. float: float a mult int b -> a.mult(floatfromint(b)) Matching of infix to operator done at compile time. thus not usable in very generic functions Subjects an object may have multiple orderings. i.e. subtype of ordered(thing) for various things. may even be subtype of ordered(X) for specific X twice. This subtyping is not so much what-it-is, but how-it-behaves and, like people, it may behave differently in different circumstances This is almost a HAS-A link - it HAS-A behaviour. So how do we tell a routine to sort an array of things based on a particular behaviour. One could make an object with a compare fn which takes to things and compares them, and have a different object (class) for each behaviour. But that is rather gross. What is really required is to tell the sort routine what message to send. Object creation This is a really interesting issue. Who creates an object: presumably the class... when we convert an int to a float do we 1/ anint.convert-to-float - i.e. call an operation on the int object or 2/ float.createfromint(anint) - i.e. call a create operation passing the int as arg. For funcitonal languages, where objects are immutable, create must able to create all objects. Extending a class it may be desirable to define new functionality for a given type of object, function which are defined in terms of the objects behaviour, not its implementation. Adding functions normally requires subtyping, but what we are saying that that the new function are a logical extension of the object. we are just elaborating what was already there. e.g. int.isprime to do this we define a new type which is a subtype and provide the implementation as a statement that the new type elobarates, rather than refines, the old. e.g. stat-real elaborates real this would (almost) imply that GLB(stat-real, float) elaborates float Example: integer: integer is unimplementable as it is infinite, and some values will not fit in virtual memory. However, many useful subsets of integer are implimentable. We want a supertype which means "integer, or useful subtype thereof" As some ops will overflow, we define the set of possible value to be the integers plus "overflow". define e.g. Z(m) a+b -> overflow if a or b is overflow -> (a+b) if a and b and a+b are < m in abs value -> (a+b) or overflow if a or b or a+b > m in abs value then if m > n then Z(m)+ is more specific then Z(n)+, so Z(m) is a subtype of Z(n) yet we normally consider a smaller set to be the subtype... integers and type subsumption: one view of subtyping is that an object of a particular type can be used when an object of a higher type is expected (because it IS of that type too). If an 8bit integer is considered to be a subtype of a 16 bit integer, then we would expect "if x.lessthan(200) then x.add(200)" to not overflow But no.... If 16bit is subtype of 8bit, then will work. anything an 8bit can do, a 16bit can do.. But while the object may be capable, the value might not be suitable. ..but with "overflow" in our model, any value is suitable, though it might be overflow... alternately, overflow might be modelled as an exception. i.e. the op does not return. Z(m) a+b -> (a+b) if a+b < m in abs value -> exception otherwise then if 8bit is subtype of 16bit... no real difference Want to say: 16bit.add(a,b) where a,b are subtypes of 16bit, result is 16bit may still overflow, but there wont be any surprises. So: Z(n) is the type/set of integers less than n in abs value. operations are limited - maybe decimalise, isprime, isodd Various classes exist which can create Z(n) by adding, multiplying etc. these require arguments which are of a subtype. To facilitate this, we want to be able, given a variable of a type, automagically choose a class which is of that type. If V is Z(100), want to choose (maybe) class Z(127) == signed(8). ouch - we are upside down again... values subset one way, types subtype the other Challenges: output formatting point/colour point?? min - find that paper about open/closed self - binary operators - arithmetic. ... output/printing This is often one of the hairiest parts of a language because various types want to be treated in similar ways, and people like to avoid extra syntax. There seem to be three inputs to a formatting request: 1/ the objects (values) to be formatted 2/ the detailed shape of the formats for each object (hex/decimal field width etc) 3/ the constant padding, and the ordering of the formatted values (which is only almost always constant) some parts of 2 are closely tied to 1, some to 3. e.g. field width is probably 3's domain. but is useful with 1 to print part of a string. keeping the inputs separate hazards consistant update keeping them together hazards abstraction and maybe readability. Thus a choice is probably appropriate. Let the programmer decide. maybe stdout.print("now is the ", time, " for ", all," ", good, " men to come to that aid of the ", party) where time = now.asciitime(), all = people.count.decimal, good = if (random > 0.5) "good" else "bad", party = "labour"; But: hard to distill this format into a per-language file. leaves too much un answered. different formats of a value are a bit like different subjects looking at an object. "you think it is an integer, but I think it is 32 bit hex, uppercase" so I might define "Address" which is an elaboration of int which defines tostr as .hex(8,"0").toupper The numerical paradox n-bit integers seem to subtype 16bit is a subtype of 8bit subranges subtype 0-255 is a subtype of 0-65536 why? A distinction between objects (concrete) and values (abstract). look at casting in C - (int)float preserves value, (int)point doesn't really. (int)char certainly doesn't. or 16bit type defn from above somewhere includes all 32bit and 64bit objects. an 8bit 5 is different from a 32bit 5 etc. but 5 IS 5.0 IS 5.00 if we start treating objects differently from values, we are losing abstraction. So we want the compiler/language to determine which representation is appropriate and how to perform an operation. Probably the most difficult to specify is dealing with loss of precision, as it is expected. overflow, and to lesser extent underflow, can be deliberately avoided. Intrinsic types. What types are INTRINSIC to a language. 1/ Boolean essential for decisions. 2/ Code - may be third class, but must exist. 3/ lamba- again, often of a low class 4/ sequence - as in args to a procedure, statements in code, subobjects in an object nice to make this first class - very parameterised, anonymous type. also bits in an integer? Hmm <'cart,x,y> <'polar,r,theta> or imaginary.cart(x,y) imaginary.polar(r,theta) Precision in types To what extent should type of variables be declared? for very local variables which are assigned once, e.g. for loop variable, it seems unnecesary to declare type. just keep scope local and deduce type from usage. even at larger level, is "int" enough, or do we want "count" "label" "area" ... Multi-threading. problem of locking - protecting against unwanted concurrent access. 2 senarios - within and without. within the methods of an object, the object may occasionally violate the invarant. While the invariant is violated, the object must be single threaded. Dangerous code segments could be bracketed somehow. without an object, we may want to use an object assuming conf->mirrors[i].that we are only user. i.e. singlethread is a behaviour we want, so we include it in required type or argument objects. How to specify fork/join. don't want it to be tooo automatic or we cannot have fine control. maybe model threads of control as objects - have arrays of them etc?? Parsing. Parsing is a very common sort of problem which must be very configurable. wan't some wway to specify a parser - as a list of parsing objects? const-string might be "extended" to have a parse method which matches the string. Executables as objects? for uniformity, as well as expressivity, it would be nice if executable code were treated like an object - was sufficiently first class to be passed around. Then IF a THEN b ELSE c FI would be a.choose(b,c) where true.choose(x,y) == x.execute and false.choose(x,y) == y.execute or maybe it would be a.choose(b,c).execute with obvious redefinition of true and false. a;b would be a sequence object. {sequence}.execute would be {sequence}.head.execute, then {sequence}.tail.execute need some input to .execute, and some output. This is looking very functional... "normal" lang have code elements which access/modify "local variable". such code elements would have limited scope and so could not be stored in variables of greater scope. if scope is a part of a variables/values "type" then this can be a type constraint. naturally, code elements could be created which have global scope. This needs a lamba abstraction. methods have the scope of an object, and are assigned to fields in that object. Is each procedure (block?) call a creation of an object? like Beta? Calling a method creates an object with fields which are the arguements and local vars, enstacks the object, and proceeds to execute the .code sequence. The objects in a .code sequence are method calls. Each identifies a target object, a target method of that object, and a list of objects as arguments. OR an object maps a methodname to a method. a method maps an object to a (more specific) method. eventually (After all args collected) a method maps an object to an object. Hmmmm. an object maps a methodname to a method which is a new scoped execution object. given an arguement, this executes. That sees procedure parameters as a single sequence. This is a bit wishy washy, but seems to tend toward a nice simple execution model... What do I particularly want: scope executable which can be passed down/around and optionally executed later. unscoped lambda expression - i.e. an independant object! combination - a scoped lambda expression. BUT: What are the semantics of an atomic execution. is it value transformation ala functional lang, or state change (of some scoped/stacked object) or both? Subtyping objects is an illusion. Subtyping of values makes lots of sense. Values don't change. Subtyping of mutable objects just doesn't work. The objects of the sub type must behave like objects of the supertype, and hence be subject to any change that the supertype may be subject to. Thus the invarient can only become less restrictive, thus allowing more value. This is the integer subrange vs n-bit int distinction again. So with typing, we focus on sub-types of VALUES of objects, and type function to express types of objects. The value of an array of ints is a subtype of the value of an array of objects, but an array-of-ints object is a restriction of an array-of-objects object. Clearly parameterised classes will play a bit role here. SubObject references. It is often desirable to have a pointer into a bag object such as a linked list, a skip list, etc. This is more than just a handle on a subobject as it may be used to navigate around the structure. Particularly in a multi-thread envronment, the bag object needs to keep track of what active pointers there are too its sub objects, so it can guard against deleting/changing an active object, or maybe an object on an active path. Requiring the client to "release" a reference is as bad as using malloc/free. It is better to have language support - e.g. though reference tracking. References must be released when pointers are destroyed, and it is best if language system guarentees this. This is like memmory allocation problem, except that a garbage collect approach won't cut it. Could possibly have several classes of references e.g. readonly and read-write and ways to promote/demote them. There is also a question of cutting a object up (e.g. double linked list) and how that is type checked..... Also, I like having a pointer to a thing that could be in one of several doubly linked lists, and I don't care which one. If I don't know, the compiler wont know, so how is concurrency guarded... Objects without inheritance. I have a feeling that when separated from subtyping, object inheritance is purely an implementation convenience. This convenience is possibly confusing and might be better discarded. We can simply use object inclusion (has-a) and let the compiler determine that a subobject can be inlined. methods for the new class can be equated to methods on the sub object or, quite naturally, a new method with calls a method on the subobject. This discards super. calls and removes any name confusion with multiple inheritance. A question is self. calls. This is (after all) the most interesting thing in oo programming. The routine in the superclass calls a method in the subclass (which may, or may not be the same class). One answer is my idea of extension classes which implement new methods on objects of some known type interms of the external interface to that object. e.g. sine(x) in terms of plus/mult/div etc. Q. to what extend with the object be mutable?? Question. are safe downcasts needed. Are they like typecase in modular-3? but there may be subthings you don't know about. Should not you extend each possible subclass with the necessary method. can this be done post-hoc. Is this just needed because programmers don't really understand subtyping?? Java has an example (probably many) of safe downcasting.. A Hashtable is defined which maps objects to objects. When you get an object out, the programmer probably knows the type from the content of the key, but the language doesn't. Hence a safe downcast is needed. Two ideas present for making this more typesafe. [ note safedown cast is not "typesafe" because it can through an exception] 1/ The key to the hash table could be (key,type-of-content) So store(key:object, ctype:type, content:ctype) and fetch(key:object, ctype:type) : ctype 2/ The type can be explicitly stored with the content (instead of implicitly) Then the table entry would be It would be in either case. This requires that types, or at least classes, exist as objects. Q. are classes enough, or are all types potentially needed as objects? we would really want a partial ordering, so all types are needed. The next question is: who owns an operation like addition? Addition of integers is a function of the set of integers (Z) more than of any particular integer. 3.plus(4) always looked terribly asymetric. If we think of objects more like values (which is pleasing for numbers), then there are possibly many things we could mean by "plus", modular arith for example. Real-plus is the *same* op as integer-plus (as an integer IS a real), but what about float-plus? float is a REAL problem (joke!) because people want to treat it like a real, but it simply ISN'T real (more-so than int isn't integer). Possibly the semantics could be that a+b yields "real" addition, but this value - which cannot be stored - gets converted to whatever it gets stored in. e.g. a float or an int, which an implicit but clearly acknowledged possible loss of information. This doesn't quite work with nested expressions, as a difference of two very close large numbers looses a lot of precision. We simply cannot keep infinite precision (or even adequate precision) intermediates. I think I've lost the thread a bit though. That is a question of overloading '+' and how to resolve that overloading. ... but is it the same problem? A problem with associating plus with "int" rather than "3", is that if we have an object that we don't know is an int, how do we add to it. Well, we must know that it is of a type that supports add (is that ring? - probably ring-of-X) i.e. generic addition must be through parameterised genericity rather than subtype genericity. (actually, it's probably additive-group rather than ring). Lets practive that: We say "X == Additive-group of type X" is a type which supports the binary operation "plus", thus is A and B are both of type X, then "plus(A,B)" is of type X. (Additive-group "extends" X) Then we can define an additive group of type "int", "float", "matrix(n,m,scalar)", "0..6", where for each we must specify how addition works in each case. We can say that "X is an Additive-group(X)" But if we see "plus(3,4)", what type is that? is it int.plus or float.plus or "0..6".plus? If we want 3 to be simply the value "3", equal in types 0..6 and int and float, then the plus must be decorated, or the type must be propergated down from the receiving method(????) If we use primarily one additive-group at a time, something like a with-clause would suffice for briefness. But we may well be mixing ints with floats, or anything else, and an accidental expectation of int rather than modulo semantics would be confusing. Maybe a low-cost with-clause on a per-expression basis: int(3+4 div b) float(3/b+4/f) Zseven(4+5) Without such clause, the least surprising would be used - int.plus For non-operator methods, this isn't an issue. Just include type in name. Rewind... When we say "X is an Additive-group of X", we need to include the "of X" because if Y is a subtype of X, "Y is an Additive-group of X", but probably not "of Y". or more importantly, "of X" is more useful than "of Y". An assertion: (did I make this earlier?) subtyping mutable types doesn't make sense. Only subtyping of immutable values. The rationale is that the important use of subtyping is: is A is a subtype of B, then a member of A can be used where ever a member of B can be used. Thus any change that is legal to a member of B MUST be legal to a member of A, and the B-visible change will be the same. Thus A must maintain the same state, and can only extend it with more information. Yet B-acceptable changes will not usefully affect the extra information...... Maybe not. A could contain a change history Lots of other examples. A shape that can change colour... So we can reasonably subtype all types. Want to motivate result type being function of arg types. choose(a,b:object) -> lub(a,b) choose(a,b)+1 -> lub(additive-group/a, ag/b) maybe say union rather than lub. double(a) -> type-of(a), but what does that mean? must choose significant point in type lattice between root and 2*a. this probably what groups are about, but want more examples. hmm. type info is static. this worth remebering. it is exactly what is important. HereWeGoAgain An "Object" is a "thing" which has "behaviours". By "thing" we mean that it is something that can be held (references, pointed to). It is a bit like a black box. Behaviours include (at least) responses (reactions). A response is elicited when we send an(other) object to the object. The response was three parts: It produces an object. It (may) change the original object into a different object It may send other objects to other objects thereby causing other objects to change into different objects. So far it is pretty confusing. We need some structure. A "type" is a set of "objects". types will usually be prescriptive (is that the right word) meaning that any object which fits the description is a member of the set.