Page Numbers: Yes X: 306 Y: 1.0" First Page: 1
Margins: Top: 1.0" Bottom: 1.3"
Heading:
LECTURE NOTES #8 LISP: LANGUAGE AND LITERATURE May 10, 1984
————————————————————————————————————————————
Lecture Notes #9 State, Polymorphism, and Message-Passing II
Filed as:[phylum]<3-lisp>course>notes>Lecture-08.notes
User.cm:
[phylum]<BrianSmith>system>user.classic
Last edited:
May 10, 1984 1:22 PM
————————————————————————————————————————————
Note:Last time (Tuesday, May 8), we didn’t get through the lecture notes; today we will pick up from where we left off. You should therefore cross out sections C through E in the lecture notes that were handed out last time (lecture notes #8); that material is re-presented here, with some updates and modifications.
A. Brief Review of What we Did (and Didn’t Do) Last Time
Were dealing again with the notion of representing, within the computer, information about some external subject matter.
Last week we talked about defining essentially a new language inside the machine, encoded in the 3-LISP structural field, and then defining operations over the structures in that language.
On Tuesday, we took what looks to be a different approach, and represented the world (i.e., encoded the information about the world) by simply using regular 3-LISP structures.
Footnote: use represent in terms of represent the world, which may involved encoding information in some way; not representing the information. In AI, the phrase "knowledge representation" betrays the latter reading (the one we won’t use), although my sense of practice is that neither reading is used uniquely. My choice comes from the etyomological dependence on the word "present". But there is lots of subtlety here.
More specifically, representation is a specific kind of designation; namely, a representation R represents D if R designates D, but in some way so that the structure of R corresponds (not isomorphism or anything so strict just some correspondence) to the structure of D. A complex description, therefore, is one kind (a linguistic kind) of representation; a painting might be another (iconic, perhaps) representation.
Specifically, we defined a variety of functions that enabled us to represent bank accounts,
Last time, started with our standard parts kit: notions of 3-LISP, and built up a representation that we then abstracted, into the notion of an object.
This time, describe the notion of an object, and then show how we might implement it.
I.e., same stuff, but from the other end.
Describe the notion of an "object", distinguishing between the object represented (designated), and the computational object representing it.
Last is an instance of a data type: perhaps a datum.
B. Objects and Simple Data Types
Basic idea is to extend the language to enable us to define objects, in terms of state variables, and what are often called methods: procedures defined over them. I.e., introduce something called object that does this. Basic format is:
1(define OBJECT-NAME
(object [
state-var1 ... state-vark]
[
METHOD-NAME1 procedure1]
[
METHOD-NAME2 procedure2]
...
[
METHOD-NAMEn proceduren]))
Footnote on OBJECT vs. last time’s DEFINE-OBJECT, the extraneous LAMBDA, etc. This is really the more perspicuous notion.
Then the expression:
(object-name initial-value1 ... initial-valuek)
would designate an object with initial-value1 as the designation of state-var1, etc. Also, it could be used as follows:
(method-namei object argi1 ... argim)
For example:
1(define ACCOUNT
(object [balance]
[WITHDRAW (lambda [amount]
(if (< balance amount)
"Insufficient funds"
(set balance (- balance amount))))]
[DEPOSIT (lambda [amount]
(set balance (+ balance amount)))]
[CURRENT-BALANCE (lambda [] balance)]))
Then we would expect the following:
11> (set A1 (make-account 100))
1= {simple closure ... }
1> ((A1 ’deposit) 2000)
1= 2100
1> ((A1 ’withdraw) 40)
1= 2060
1> ((A1 ’current-balance))
1= 2060
Similarly:
(define CELL
(object [contents]
[UPDATE (lambda [new-contents]
(set contents new-contents))]
[CONTENTS (lambda [] contents)]))
(define FAMILY
(object [mother father kids]
[MOTHER (lambda [] mother)]
[FATHER (lambda [] father)]
[KIDS (lambda [] kids)]
[NEW-KID (lambda [kid] (set kids (cons kid kids)))]))
leading to the behaviour we saw last time:
11> (set C1 (cell "This is a string"))
1= "This is a string"
1> (set C2 (cell 200))
1= 200
1> (contents C1)
1= "This is a string"
1> (update C1 factorial)
1= {factorial closure}
1> ((contents C1) 6)
1= 120
1> (+ 3 (contents C2))
1= 203
and
1> (set LeVesques (family "HECTOR" "PAT" []))
1= {simple closure
... }
1> (father LeVesques)
1= "Hector"
1> (mother LeVesques)
1= "Pat"
1> (new-kid LeVesques "Michele")
1= [Michele]
1> (new-kid LeVesques "Rene")
1= ["Michele" "Rene"]
1> (kids LeVesques)
1= ["Michele" "Rene"]
Some comments on all of this:
We are still supporting abstraction, in the sense that we define this abstract data type, and give out to our users
who are often us, in a different part of the program cf. David Levy’s comment
the interface the public procedures. For example, we would describe the ACCOUNTS object type as follows:
Defined in terms of four procedures:
(ACCOUNT initial-deposit)Designates (after setting up) a new account with an initial deposit of initial-deposit.
(WITHDRAW account amount)Withdraws amount dollars from the account designated by account.
(DEPOSIT account amount)Deposits amount dollars into the account designated by account.
(CURRENT-BALANCE account)Designates the current balance in the account designated by account.
That is all that the user of this package would need to know.
[computer science: will come back presently to why I am calling this an object type, rather than a data type.]
We have extended 3-LISP, clearly: that was part of the confusion from last time. Note, however, an analogy between OBJECT and LAMBDA. Both are used to describe a complex object, instances of which are then used all over the place.
In the sense that a (LAMBDA ... ) expression designates a function, but creates a closure, instances of which are used whenever the function is applied to a specific argument.
This is not a mathematical notion: there is nothing instance-like about the particular application of a function to particular arguments. But if you think of a function as a collection of mappings from values in the domain to values in the range (co-domain), then a particular application can be viewed as one particular instance of that collection: namely, the one where the value in the domain is the argument.
Also, an (OBJECT ... ) expression designates a type, but creates something, instances of which have specific state variables etc. In this case, however, it is natural to think of instance of the type itself (i.e., to think about instances extensionally): namely, specific accounts.
But there is a general moral here, relevant to how one reads programs:
Every time I see a (LAMBDA ... ), I know that within the scope of that operator, I think in terms not of the function as a whole, but the function at a particular value. I.e., I cross from the generic to the specific. All kinds of things change: variables will be bound in that instantial case. Also, I know that the code within the scope of a LAMBDA is not processed when the function is defined, but will be processed once per function application once, in other words, per instance of the function (on this extended reading of instance).
For example, distinguish:
1 (define COUNT
(let [[n 0]]
(lambda []
(begin (set n (+ n 1))
n))))
which we saw in section, and
(define ONE
(lambda []
(let [[n 0]]
(begin (set n (+ n 1))
n))))
In the former case, the N is for the procedure as a whole, and will therefore be shared among all instances. Therefore it will count how often the function is applied.
In the latter case, however, the N is bound per instance, and will therefore be initially zero every time the function is applied. Therefore any expression of the form (ONE) will (rather uselessly) designate the number 1.
The point is that where the LAMBDA is makes all the difference. It is a variable-binding operator, according to the logical notion of binding variables, but it also has this whole/instance import, that is terribly important to recognize.
This is one reason I am not so sanguine about the style of definitions that A&S use: (DEF(FACTN)(IF ... )), because it doesn’t make that obvious.
Similarly: now that I have defined OBJECT, I will think, every time I am within the scope of an (OBJECT ... ) expression, that I am to think not in terms of the type as a whole, but rather of a particular instance of the type.
What about the (LAMBDA ... ) expressions associated with the methods, which are defined within the scope of the OBJECT, and therefore are presumably specific to a particular instance (remember: last time we talked about these private procedures).
Answer: both are true. Code within the scope of a (LAMBDA ... ) expression, that in turn is within the scope of an (OBJECT ... ) expression, is specific to an instance of using the method, and the whole method is specific to an instance of the object type.
C. Implementation of Objects
So this is a definition of OBJECT, and the behaviour we want; how do we implement such a thing?
We will pretend that we had this idea first, and go search for implementation second.
Not strictly true, in terms of how one programs: tends to be much more the latter; abstractions arising out of the code one is writing. However, this direction also occurs.
Take accounts, again, as the illustrative case.
Code that we have is:
1(define ACCOUNT
(object [balance]
[WITHDRAW (lambda [amount]
(if (< balance amount)
"Insufficient funds"
(set balance (- balance amount))))]
[DEPOSIT (lambda [amount]
(set balance (+ balance amount)))]
[CURRENT-BALANCE (lambda [] balance)]))
Won’t go through all the motivating details, but note a few points. First, because of what we have just said about expressions within the scope of OBJECT, we are going to need to have lots of instances of these methods one, in fact, per instance of the object type.
On the other hand, we have required, given our notion of how DEPOSIT and so on are to work, that those names have to be used across all objects of this type. Thus we expect to use the expression (DEPOSIT A1 100), which should somehow apply the deposit function associated with account A! to the argument 100.
These two requirements are met by the following definition (which I just pull from the sky):
1(define MAKE-ACCOUNT
(lambda [balance]
(letrec [[PRIVATE-WITHDRAW-FUN
(lambda [amount]
(if (< balance amount)
"Insufficient funds"
(begin (set balance (- balance amount))
balance)))]
[PRIVATE-DEPOSIT-FUN
(lambda [amount]
(set balance (+ balance amount)))]
[PRIVATE-CURRENT-BALANCE-FUN
(lambda [] balance)]]
(lambda [message]
(cond [(= message ’withdraw) private-withdraw-fun]
[(= message ’deposit) private-deposit-fun]
[(= message ’current-balance) private-current-balance-fun]
[$T (error "unknown message" message)])))))
(define WITHDRAW
(lambda [account amount]
((account ’withdraw) amount)))
(define DEPOSIT
(lambda [account amount]
((account ’deposit) amount)))
(define CURRENT-BALANCE
(lambda [account]
((account ’current-balance)))
So define a procedure that simply takes language of the former sort, and converts it to the latter sort.
I.e., given the general case of the former:
1(define OBJECT-NAME
(object [
state-var1 ... state-vark]
[
METHOD-NAME1 procedure1]
[
METHOD-NAME2 procedure2]
...
[
METHOD-NAMEn proceduren]))
it will produce
(begin
(define
METHOD-NAME1
(lambda args
(((first args) ’
method-name1) . (rest args))))
...
(define
METHOD-NAMEn
(lambda args
(((first args) ’
method-namen) . (rest args))))]
(lambda [
state-var1 ... state-vark]
(letrec [[
private-name1 procedure1]
...
[
private-namen proceduren]]
(lambda [message]
(cond [(= message ’
method-name1) private-name1]
...
[(= message ’
method-namen) private-namen]
[$T (error "unrecognized message" ↑message)])))))
This is relatively straightforward to do, in 3-LISP; the code is, as last time, given at the end of the lecture notes.
So this is where we got to by the end of last time.
D. Mutable Structures
Given this machinery, we will define something very much like a rail, except that it can be changed. Call it an m-rail, for "mutable rail", in which you can replace any element or any tail.
Note: in original 3-LISP, all rails were like this.
Need to define versions of FIRST, REST, CONS, NULL, LENGTH, RAIL (the type predicate), etc.
Straightforward code:
1(define M-CONS
(object [element tail]
[M-RAIL (lambda [] $true)]
[M-FIRST (lambda [] element)]
[M-REST (lambda [] tail)]
[NEW-FIRST (lambda [new-element] (set element new-element))]
[NEW-REST (lambda [new-tail] (set tail new-tail))]
[M-NULL (lambda [] $false)]
[M-LENGTH (lambda [] (+ 1 (m-length tail)))]))
(define EMPTY-M-RAIL
(object []
[M-RAIL (lambda [] $true)]
[M-FIRST (lambda [] (error "empty m-rail" ’?))]
[M-REST (lambda [] (error "empty m-rail" ’?))]
[NEW-FIRST (lambda [new-element] (error "empty m-rail" ’?))]
[NEW-REST (lambda [new-tail] (error "empty m-rail" ’?))]
[M-NULL (lambda [] $true)]
[M-LENGTH (lambda [] 0)]))
Various problems.
For example, the definition of the type predicate M-RAIL isn’t any good, because M-RAIL of anything that isn’t an m-rail will cause an error, instead of returning $FALSE. I.e., M-RAIL should be a total function, and we have defined it only partially, on those cases where it is true.
Nonetheless, the definition basically works OK, supporting such behaviour as the following:
11> (set M1
(m-cons 10
(m-cons 20
(m-cons 30
(empty-m-rail)))))
1= {simple closure
... }
1> (define M-LIST; Should be more convenient to use.
(lambda args
(if (null args)
(empty-m-rail)
(m-cons (first args)
(m-list . (rest args))))))
1= ’m-list
1> (set M1 (m-list 10 20 30))
1= {simple closure
... }
1> (m-first M1)
1= 10
1> (m-length M1)
1= 3
1> (m-rest M1)
1= {simple closure
... }; Not too useful
1> (new-first M1 "Hi there")
1= "Hi there"
1> (m-length M1)
1= 3
1> (m-first M1)
1= "Hi there"
1> (new-rest M1 (empty-m-rail))
1= {simple closure
... }
1> (m-length M1)
1= 1
Why do we call it a rail, as opposed to a sequence?
It looks, after all, as if we can have (M-RAIL 10 factorial) i.e., can have "external" objects part of it.
Could have defined some new platonic entity, called a mutable sequence, but that is crazy.
The point of representing state here is to change information in the machine; saying (NEW-KID LeVesques ’Rene) didn’t give them a new child.
I.e., you can’t change a sequence, since they are mathematical abstractions; but you can change an M-RAIL, implying that an M-RAIL is something that the process has causal access to. May not be exactly the same as what is inside, but without any sensors or perceptors, it is surely pretty close.
Same old problem! want to characterize the machine externally, but don’t want to take that as a reason to forget that it is the machine that we are characterizing.
As indicated in the comment, it is hard to see these new rails. Define a special M-EXTERNALIZE that will convert them into a more convenient notational format. Uses angle brackets:
1(define-object M-CONS
(lambda [element tail]
[M-RAIL (lambda [] $true)]
[M-FIRST (lambda [] element)]
[M-REST (lambda [] tail)]
[NEW-FIRST (lambda [new-element] (set element new-element))]
[NEW-REST (lambda [new-tail] (set tail new-tail))]
[M-NULL (lambda [] $false)]
[M-LENGTH (lambda [] (+ 1 (m-length tail)))]
[M-EXTERNALIZE (lambda []
(let [[rs (m-externalize tail)]]
(string-append
"<"
(externalize ↑element)
(if (m-null tail) "" " ")
(substring 2 (string-length rs) rs))))]))
(define-object EMPTY-M-RAIL
(lambda []
[M-RAIL (lambda [] $true)]
[M-FIRST (lambda [] (error "empty m-rail" ’?))]
[M-REST (lambda [] (error "empty m-rail" ’?))]
[NEW-FIRST (lambda [new-element] (error "empty m-rail" ’?))]
[NEW-REST (lambda [new-tail] (error "empty m-rail" ’?))]
[M-NULL (lambda [] $true)]
[M-LENGTH (lambda [] 0)]
[M-EXTERNALIZE (lambda [] "<>")]))
Thus we get:
11> (set M1 (m-list 10 20 30))
1= {simple closure
... }
1> (m-externalize m1)
1= "<10 20 30>"
1> (begin (new-first M1 (factorial 6))
(m-externalize m1))
1= "<120 20 30>"
1> (new-rest M1 (m-empty-rail))
1= {simple closure
... }
1> (m-externalize M1)
1= "<120>"
E. Generic Operators, Polymorphism, and SubTypes
This is all fine, but there is another abstraction that we need. It would be much more convenient if the same LENGTH, FIRST, REST, etc., could work on all of these various types:
We can’t redefine FIRST and REST, etc., because they are primitive (and 3-LISP isn’t set up this way), but we can define a generic (or polymorphic) version, called REST!:
1(define REST!
(lambda [arg]
(cond [(rail arg) (rest arg)]
[(sequence arg) (rest arg)]
[(string arg) (substring 2 (string-length arg) arg)]
[(m-rail arg) (m-rest arg)]
[$T (error "type error for rest" ↑arg)])))
This requires that we fix up our definition of M-RAIL (i.e., that we correct the problem we noted above), which isn’t so easy, as it happens, because we are modelling everything with functions. But it can be done; let’s just assume it for now.
Specifically, see the discussion in A&S on manifest types.
Supports (supposing we had also defined an analogous LENGTH!):
11> (length! "This is a test")
1= 14
1> (length! [10 20 30])
1= 3
1> (length! (m-cons "a" "b"))
1= 2
In fact, once this is proposed, imagine how useful it would be to do this for:
LENGTH combining LENGTH, STRING-LENGTH, M-LENGTH, etc.
REVERSE combining REVERSE, STRING-REVERSE, etc.
EXTERNALISE ...
The point of the last would be that, by defining M-EXTERNALIZE, we would be able to tap into the way that the system prints things out -- i.e., so that when an M-RAIL was returned, it would print out using angle-brackets directly; we wouldn’t have to call M-EXTERNALIZE explicitly in order to see it.
What about =?
!!! Here we go again: same old problem. Come back in a moment.
In fact we wouldn’t need to define these polymorphic functions explicitly (whose definitions, as illustrated in the example just given, basically dispatch on the type of the argument), if everything were done with messages.
Languages that do this primitively (i.e., everywhere) are called message-passing languages. Like function application, message-passing can be turned into the only combinatory mechanism in a system.
Then function application, which we have used to model message-passing, can in these languages be modelled with message-passing.
In fact I have played a little with a language design in which there is no fact of the matter as to whether function application or message-passing is the primitive means of combination; it is rather a matter of the point of view.
Hierarchies of types:
Often want sub-types, such as:
families with, say, adopted children as well as their own.
Polygons (drawing from A&S page 130): rectangle, parallelogram, etc.
Justified text strings (a sub-type under text strings more generally)
Point is that many of the operations will be inherited from the higher types
Length in characters, for the justified text, for example;
Father, mother, etc., for families
Sometimes you may have special ways of doing things; otherwise default to the higher types
Area for polygons, for example
This is an enormously complex area, lots of active research:
Typical problems that must be dealt with:
Clashes in multiple super-types
F. General Comments
Before looking at semantics, some general comments.
First, why aren’t data types like this primitive in 3-LISP?
For one thing, that wasn’t what I was trying to do when I designed 3-LISP; reflection, which dealt only with internal structures, needed only a small finite number of types, and so I just built those in.
Although, a real account of reflection, as opposed to the miniscule version of reflection embodied in 3-LISP, will require general types, in very interesting ways (what it is to refer to a type as a type, etc.)
Also, I didn’t understand the relationship between the normal semantics of types (as done in c.s.) and the kind of semantics I was aiming for.
This is what I think, in virtue of teaching this class, I am coming to understand better, although I certainly don’t understand it fully yet. An adequate answer will clearly involves at least these three things:
a.Taking seriously the modelling relationship, as well as the designation relationship;
b.Dealing explicitly with information about something, as well as structures that encode that information, and the thing those structures are about.
c.Understanding the relationship between abstraction and "aboutness", which isn’t clear.
Finally, one of LISP’s great powers is that you can implement such things, as we have just done, relatively easily.
This is a very important point about LISP: it is really a very abstract machine language, in terms of which to implement other things; rather than a language in which one does things directly.
Sometimes, depending on the cleanliness of the code, this isn’t obvious.
But this is one of the things that the emphasis on modularity and abstraction is designed to bring out.
Leads to a more general point:
We started with functions and function application, and have modelled various kind of abstract data type.
In doing so, we introduced other methods of combination: message passing, in particular.
But there are other regimens, like the sort of rule-based behaviour on which expert systems are based, that don’t reduce to abstract data types any more than abstract data types reduce to function application.
Sure, can model them with abstract data types, but then we could model abstract data types with functions
That just proves that modelling is a very powerful mechanism.
Before the course is over we will probably introduce a number of other sorts of constructs, as different from data types as data types are different from function application.
In general, as soon as you use some sort of machinery or tool kit, you very quickly rise above the level of the individual ingredients, and need a whole new set of ideas about how to structure the next level up.
Architecture: start with two-by-fours, perhaps, and nails, and perhaps plywood. But then have notions of balloon framing versus post-and-beam. Then open-spaces like Eichler’s versus lots of small rooms like old New-England farmhouses.
Original tool kit makes some things easy; some hard not saying that it doesn’t matter. Point, only, is that the concepts in terms of which the tools are understood won’t suffice to understand the uses to which the tools are put.
That is why understanding how we have dealt with the problems we have encountered so far is more important than understanding what we have done so far.
Finally, this is the most extraordinarily simplistic notion of an abstract data type, method definition mechanism, etc.
Easy to imagine something with much more complexity: procedures associated with state variables, mechanisms to indicate which state variables are directly accessible from the outside, which are private (note that we implemented making MOTHER, FATHER, and KIDS public in the definition of FAMILY), providing a name for the individual object (note our current definition does no such thing), etc. etc.
Interesting set of programming problems to play with.
G. Semantics
In conclusion, look both at the semantics of these data types, and the semantics of the data-language that we used when doing the geography program last week.
I.e., will now analyse the two different approaches to representing information about the world.
By looking at both we will understand each.
Start with the one today: abstract models; data types.
Specifically, go back to the question of a polymorphic equality. Question, like last time, is what are we to do with:
11> (set F1 (family "Tully" "Cleopatra" []))
1= {simple closure}
1> (set F1 (family "Cicero" "Cleopatra" []))
1= {simple closure}
1> (= f1 f2)
Error: Equality not defined over functions
But this isn’t the point: this is taking our question as one over the model, not over the families being modelled.
Formally, need a polymorphic =, or F-= (analogous to M-FIRST).
Semantically: chances are we need equations about who is who, and who isn’t who else, and so forth. No particular reason to suppose that a simple data base about families will know which representations are about the same family, and which aren’t, because family identity probably (though not perhaps solely) depends on personal identity. I.e., it is a substantial question.
What is going on, however, can be described.
First, draw standard modellng diagram, with F, F, etc.













If we use 3-LISP, and its standard repertoire of designatable objects, then it is clear that identity in the model will be computable just in case the model doesn’t include functions, and not computable (at least not obviously computable) just in case it does.
However, and this is perhaps my main point in all of this, identity in the model and identity in the world modelled will not necessarily correlate in any obvious sense.
Sometimes, identity in the modelled world is easier than identity in the model.
For example, as it happens, we modelled accounts with functions, but identity of account is isomorphic with function designator, not with function. So we could easily define:
1 (define ACCOUNT-=
(lambda [a1 a2]
(= ↑a1 ↑a1)))
Footnote for hackers: this isn’t a method; can you define an appropriate method? I.e., fill in the following:
1 (define ACCOUNT
(object [balance]
[WITHDRAW (lambda [amount]
(if (< balance amount)
"Insufficient funds"
(set balance (- balance amount))))]
[DEPOSIT (lambda [amount]
(set balance (+ balance amount)))]
[CURRENT-BALANCE (lambda [] balance)]
[ACCOUNT-= (lambda [account]
... )]))
Answer is clearly yes.
It is interesting that these modelling things are called data types in programming language design, not object types. Reason is partly because of the distinction between model and world modelled.
Go back to last week’s approach: define another internal language, programs defined over it.
First, draw standard modellng diagram, with double-F, double-F, etc.













Now there is something very similar about these.
In both cases, the model whether language or model contained too much information; information that was gratuitous with respect to the world being modelled.
In both cases, therefore, we defined procedures that were more abstract, that ignored the detail, and that dealt with the representation/model in ways that made sense in terms of the designated/modelled world.
Cf. ACCOUNT-=, SAME-ROAD, etc.
In both cases, we aimed towards a state in which we could view the original language as directly about the world, and ignore the representation/model.
Technically, this is called being fully abstract.
More specifically, where we could view the public interface of the module as about the world. In both cases the code that was internal to the data type dealt with the representation/model directly. In this sense, there is a difference in semantics between the internal and external language.
I.e., whereas the code within the functions associated with the data type deals with the representation of information; the functional interface of the data type is meant to be interpretable as defined over the object represented.
I.e., when you cross the boundary of an abstract data type there is a certain sense in which you shift to the meta-level.
But there is a problem (and here I will try to restate what I thought was going on at the end of the geography class last week):
There are ineliminable differences between the two things: representation/model (which encodes information about the world), and the world itself. For example:
Computability is defined over the representation/model. All of the questions of efficiency, behaviour, and so forth, arise there.
This becomes interesting, for example, when you build programs that are simulations of active systems.
Systems, for example, that model circuit layouts on VLSI chips, or that simulate the economy, etc.
What a simulation is, as opposed to a more general program that analyses or deals with information, is (I suggest) one in which the temporal structure of the process corresponds in some ways with the temporal structure of the world being modelled.
Effectiveness of the language.
In either route, it is necessary to have the base computer language be effective, as 3-LISP clearly is.
If it weren’t, then the problem recurses: so this is a harmless assumption.
In the modelling route, that is a problem, because it means you can’t use non-effective language at all.
In the double-designation proposal, it is less of a problem, but you have to specify what should happen (full inference routines) to take care of the non-effective sentences.
Million dollar question:
Are these two views really the same, or are they really different?
Are the distinctions between them, in other words, inessential, in terms of what they are really getting at, or do they represent genuinely different approaches?
I am about to propose an answer. First, however, a prefatory note:
If you model the representation (data language), it will become hard to distinguish between the two.
Put another way, if we classify the representation (data language) in terms of objects in the world, then the classification of the representation and the model of the world may end up being the same abstract object.
Especially true the more abstractly you treat representation.
In other words, there is an apparent tension here between abstraction, for which we are pushing so hard, and semantics, in terms of both the modelling relationship, and the designation relationship.
So my answer is this: both are wrong.
But they fail in different ways.
Put another way, they are crucially different, but they are aiming, ultimately, at the same place.
Specifically, what you want is to have your program deal neither directly with the world, nor with (structural or, God forbid, syntactic) representations of the world, but with information about the world.
Cannot confuse it with the world it is about. Different notions of locality, for example.
Cf. data type, not object type: that, in the end, is correct.
Cf. thinking being intensional.
That, I take it, is what CSLI is here to do.
Appendix. Supporting Macro Definitions
As last time, included without comment:
;;; OBJECT:
;;;
;;; General form:
;;;
;;; (object [state-var-1 ... state-var-k]
;;; [<mess1> <fun1>]
;;; [<mess2> <fun2>]
;;; ...
;;; [<messk> <funk>])
(define OBJECT
(mlambda [call]
(letseq [[state-vars (arg 1 call)]
[pairs (rest (pargs call))]
[fun-names (map (lambda [pair] (acons)) pairs)]]
’(begin
,(map (lambda [pair]
’(define-message ,(first pair)))
pairs)
(lambda ,state-vars
(letrec ,(map (lambda [pair fun-name]
’[,fun-name ,(second pair)])
pairs
fun-names)
(lambda [message]
(cond . ,(map (lambda [pair fun-name]
’[(= message ,↑(first pair))
,fun-name])
pairs
fun-names)))))))))