Date: Wed, 30 Aug 1995 16:26:55 -0700 To: Mario.Wolczko@Eng (Mario Wolczko) From: ungar@self (David Ungar) Subject: Re: macros / code construction
We have had lots of discussions about assignment, but what it boils down to for me is that containers are appealingly intuitive notion, and so I am happy to ground out where it does. Going deeper does not seem to me to buy so much.
- Dave
Date: Thu, 31 Aug 1995 00:03:54 +0200 From: rainer@physik3.gwdg.de (Rainer Blome) To: self-interest@self.sunlabs.com Subject: macros / code construction
yes, i know, macros as in the lisp family are a very unstructured way of doing things. i don't actually care for macros, any way to make convenient the use of useful but awkward idioms would do.
i have heard about beta's facilities in that direction but have only just printed the tutorial out, so i can't comment on that. (as always: no library -> no book on beta :-( ) i like the common lisp/loops method combination stuff, so i guess i'll like beta, too.
there are more things that i miss in self: local methods and evaluation of literals in the dynamic context (dynamic? lexical? what does lexical mean anyway, when we don't have source-code anymore?).
imagine doing assignment another way. assignment is a hack (from the functional point of view). it manipulates its context. one should make that explicit by revealing the reflection involved in assignment. first get an object that represents the place (the slot). that is the reflective part. then send it a normal message with the new value as an argument:
(reflect: self slot: "x") store: 1
(buggy, but you know what i mean. how the slot implements the `store:' method? don't you ask! ;-)
of course, this is to be put as a method in the `assignment slot'. now this `slot:' (meta-) message will always eval to the same object (the `x' slot), so it should not be computed every time the assignment method is called.
note that the method now _explicitly_ `knows' the slot it refers to, as opposed to knowing that implicitly by reflecting on its (the method's) own slot name. randy and dave mentioned a very similar way to implement assignment in their ecoop95 paper.
to make this scheme feasible, two things are missing (that means, i can't see them, *I* might be missing them, not self, tell me if i do.):
a way to do computation during initialization
a convenient way to construct code. writing this kind of assignment methods must be automated.
this is long already, i'll stop here. any thoughts? am i making sense?
rainer
-- Dave
The first part of this message is a reply to Dave's and argues about assignment. In my last message I just wanted to use the assignment issue as an example to justify a generic code construction mechanism. The second part tries to clarify on that.
-------------------------------------------------------------------------- PART 1:
Dave wrote:
containers are an appealingly intuitive notion,
Sure, I want assignment to feel like that.
and so I am happy to ground out where it does.
Only it doesn't ground out at containers, it grounds out at the assignment primitive.
Going deeper does not seem to me to buy so much.
As has been pointed out, it buys you a cleaner design and thereby more power.
We have had lots of discussions about assignment, [...]
There was one on this list which I am gonna quote from:
From: Randall.Smith@eng.sun.com (Randy Smith) Date: Thu, 7 Apr 1994 18:08:10 +0800
You have tripped upon what I think is a small, sore wart on the current semantics of assignment.
So what you might write today as (| x <- 7 |), essentially the same as (| x = 7. x: arg = <-. |), would become (| x = 7. x: arg = <-['x']. |).
Another up side [...] is that the story of what <-[x] means is tidy: x: arg = <-['x'] is like x: arg = ( (reflect: self) at: 'x' Put: (reflect: arg) IfFail: [error]).
This looks (as I said) very similar to what I proposed: ((reflect: self) slot: 'x') store: arg
This way, it does indeed ground out at a container, namely the slot. [ Where does the comma belong, here --------------^ ^-- or there? ]
The (putative) Downsides:
1] Today when you see an assignment slot in an object (| x <- 7 |), you know that there are two slots, and that x: is not trapped: it is guaranteed to contain "<-". This simplifies the browsing task facing the programmer.
That's no downside. Assumptions about the implementation of an abstraction are always bad.
2] What is the scope of the x in <-['x'] ? Can it only assign to the x slot in this object? Or can it assign to a slot in some other parent or maybe any object in principle? Some say the former is a reasonable
The slot must not be referred to by name but directly. Reify slots, or at least, make it feel as if they were! Some effects of that might surprise you but these surprises are what I want: for example you could move the slot to a parent and make it a shared data slot, assignment would still work.
-------------------------------------------------------------------------- PART 2:
As Randy pointed out, (| x <- 7 |) means (| x = 7. x: arg = <-. |). This kind of syntactic sugar is indispensable. That's what macros provide: syntactic sugar.
When I do Lisp, I very often use a macro to essentially get an inline function. Since backquoting is awkward and error-prone, I defined a `definline' macro, which in turn makes defining macros a breeze. It is this kind of power that I want. To be able to boost the language with my own idioms.
Imagine having to write the above every time you want an assignable slot, horrible! Now, with reflection, we can invent very powerful extensions to the base language. But using them without syntactic sugar is not feasible, it's just too awkward: (| x = 7. x: = (|:arg| ((reflect: self) slot: 'x') store: arg). |)
(that's still buggy, don't mention.)
So we need a way to create our own syntactic sweets.
(|x = 7. x: = := x.|) might be the way that would look like, where `:=' refers to a global macro that does the right thing.
L. Peter Deutsch (LPD) mentioned that self's syntax is a problem for code manipulation. This is so obvious to me that I forgot to mention it on my list of Self's weaknesses. I can't say I hate it, but it's crap compared to the beauty of Scheme. In my opinion, code beauty is a value, and the ability to make idoms look `just right' enables one to choose the beauty criterion oneself.
LPD> [code as data] works so well in Lisp because Lisp is so simple.
Which is a virtue.
[An aside: LPD> two non-terminals (atoms and numbers) and one terminal (list cells) Huh? The other way round, I should say, and: aren't numbers atoms? ]
Trying to explain what I do, I often explain to people why Self doesn't need explicit control structures. Then I get to show the different syntax for `if'. And most of them instantly object to `bool if: then False: else'. Everybody (including me) wants `if bool then else'. So I wouldn't call macros `efficiency hacks' (LPD), rather `convenience hacks'.
The syntax used by the end user may be whatever she finds convenient, independent of the syntax used by the system designer. The Self syntax is not particularly convenient, to some even repellent. The designer will probably best use a syntax that maps one-to-one on the language concepts and provides a suitable base for structured code manipulation. The Self syntax does neither (see the paper on ASTs), it seems to stem from the authors' Smalltalk experience. Therefore it should be dropped in favor of a fully parenthesized one.
Rainer
"Rainer" == Rainer Blome rainer@physik3.gwdg.de writes:
Dave> containers are an appealingly intuitive notion, Rainer> Sure, I want assignment to feel like that.
Dave> and so I am happy to ground out where it does. Rainer> Only it doesn't ground out at containers, it grounds out Rainer> at the assignment primitive.
I suspect Dave may have been implying that he thinks of slots as containers and that the way the assignment primitive works is meant to suggest that.....
Rainer> As Randy pointed out, (| x <- 7 |) means (| x = 7. x: arg Rainer> = <-. |). This kind of syntactic sugar is indispensable. Rainer> That's what macros provide: syntactic sugar.
Something we talked about a lot in the course we ran at Brown last spring was that for many of these tasks, Self's expressiveness is sufficient. (http://www.cs.brown.edu/courses/cs196b/)
Rainer> Trying to explain what I do, I often explain to people why Rainer> Self doesn't need explicit control structures. Then I get Rainer> to show the different syntax for `if'. And most of them Rainer> instantly object to `bool if: then False: else'. Rainer> Everybody (including me) wants `if bool then else'. So I Rainer> wouldn't call macros `efficiency hacks' (LPD), rather Rainer> `convenience hacks'.
You can get 90% of 'if bool then else': if: bool Then: thenClause Else: elseClause = ( bool if: thenClause False: elseClause ).
That's *my* point when explaining why Self doesn't need explicit control structures. You can define your own, with whatever order you want. The side benefit in my mind is that you *don't* need macros to do it (a plus in my book) -- you just use message sends, something you already need to know. In Lisp or Scheme, you need to learn function evaluation and then also learn macros. Learning macros involves (as Peter Deutsch points out) learning meta-levels of the language (and even the concept of meta-levels (yes, we all studied grammar in high-school, but how many people remember it and how many people generalize the concept to arbitrary patterns of information?))
Part of this side benefit of not needing macros is that you can always look at something and determine where the message send is. Following inheritance paths lets you find the definition of that message send. When I compare this with the obfuscation possible with Lisp macros, the obfuscation possible with Self seems better because it is easier to figure out what the obfuscation is actually doing.
An aside on syntax: What's the missing 10% in 'if bool then else'? The fact that the colons are there and the capitalization is a little funny. You can fix the capitalization with a minor tweak to Self's syntax -- simply reverse which keywords need to be capitalized:
If: bool then: thenClause else: elseClause = ( bool if: thenClause False: elseClause ).
leading to the following comparitively english-like usage:
If: someConditionIsTrue then: doSomething else: doSomethingElse.
Figure out some inference rule to get rid of the colons, then figure out another one to allow white space in names and you're really cooking....
If some condition is true then do something else do something else.
Brook
Brook wrote:
You can get 90% of 'if bool then else': if: bool Then: thenClause Else: elseClause = ( bool if: thenClause False: elseClause ).
Oh, OK. In this case, a macro would indeed be an efficiency hack. Because using a message would give up locality there. Where is the `if:Then:Else:' slot? In globalBehavior, I guess. And that slot takes time to find. If its content is a normal method, it will be evaluated over and over again. Maybe the compiler can optimize that away, but I don't think so.
If it instead explicitly transforms the code, and does that only once, the boolean will again get to handle the message directly. This would have to happen at `initialization time / block/method creation time'. But then a syntactic context is necessary. *frown* Or the code could be modified when it is first evaluated. *big frown*
Gee, I know too little in this area, I give in (for now ;-). Have to read think some more there, especially about BETA and syntactic stuff in Scheme.
Your idea to reverse the capitalization is nice, I like it.
Figure out some inference rule to get rid of the colons, then figure out another one to allow white space in names and you're really cooking....
The result looks tempting, but rules like that can and will _really_ bite you is my guess. :-(
Rainer
self-interest@lists.selflanguage.org