[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Re: consistency in Self



**> On Wed, 29 Jun 94 10:52:53 MET DST, mulet@info.emn.fr (Mulet Philippe 40448387) said:

[ how syntax can make code more readable ]
> jane goTo: theBallgame With: yourLittleBrother.
> 
> A minor syntactic change, and you have an English sentence:
> 
> Jane, go to the ballgame with your little brother.
> 
> A cute example, just meant to show the possibilities of writing
> the mythical and proverbial "self-documenting" code.

  Mulet> It is true, but remember that historically the syntax of Self is derived 
  Mulet> from the one of Smalltalk.

Of course -- this works for Smalltalk as well.  I think it stops
working for both Self and Smalltalk when you have to start bringing in
strange characters to define things: parens, pipes, colons, and
brackets come to mind in Self -- the definition of an object isn't
terribly readable (meaning, try to read it as English instead of
code).

What I'm trying to say is that this:

jane goTo: theBallgame With: yourLittleBrother.

is more readable than its C++ equivalent:

jane->goTo(theBallgame,yourLittleBrother);

The purpose of the parameters is completely unidentified in C++, but
at least _can_ be identified in a keyword kind of message send.

> On the other side: Sure, Self has lots of potential for malicious
> obfuscation, such as swapping the "ifTrue:Else:" slots on the true and
> false oddballs. But some conversations here at Brown indicate that
> Self's design enables a highly reflexive mode of computing without
> recourse to the additional mechanisms provided for reflexive
> computation in other systems (e.g., the macro systems found in Lisp,
> Scheme, etc. provide the syntax-creation facilities that arise
> naturally out of how Self works). I think this is a big plus.
>
  Mulet> I am working on reflective object-oriented languages, and I wonder what
  Mulet> kind of behavioral reflection you can envisage in Self.

A while back, a brief discussion started here when I asked "What do
you really need a macro system for?" in the context of both the C
preprocessor and Lisp-like macro systems.  The most common useful
purpose seemed to be "Creating new syntax."  I brought up the point
that, in a language like Self, the equivalent of new syntax can be
created at the linguistic level.  I didn't mean you could create the
equivalent of reader macros and make Self look like Algol (for
example). More, that the more common defmacro construct wasn't
necessary: if-then-else is definable at the user level, as are various
kinds of loops, iterators, etc.

The point in the end was that macro-like systems are not per se
necessary to accomplish what they are most often used for.  Macros are
approximately baby forms of full reflective capabilities.  I haven't
put enough time into figuring out what full reflection adds that you
can't get with the builtin stuff.  The only thing that comes to mind
is implementation information, which you can get with mirrors.  And of
course, in general, such information is something you _don't_ want to
see.

  Mulet>  Mirrors are not causally connected to the structures they represent.

Hmm? How do you mean?  What reflective stuff do you want to do that
can't be done with mirrors?  This isn't a challenge: I'm trying to
figure out how much extra junk you have to add to something like the
base Self model to get full reflection. (Part of my research is in
stripping down existing object models until you get something that you
can use to build exciting new object models.  TR available any day
now).

[...]
> a bad thing per se, but then you must ask: why have message lookup at
> all?  Why not just get rid of the implicit inheritance mechanism and
> make everyone forward messages on themselves (explicitly (using the
> Tready of Orlando verbiage here))?

  Mulet> I like very much the idea of being able to compute dynamically the parent
  Mulet> object, this provides the same kind of flexibility as assignable parent slots,
  Mulet> with dynamicity in addition.

If it provides the _same_ kind of flexibility, it isn't necessary.  So
what do you mean by "dynamicity?"  Could you accomplish what you want
by removing actual parent slots and adding a method to handle
unresolved messages, resending the message to whatever slot (possibly
computed) you wanted to be "the parent?"

  Mulet> In this way, you could easily implement a network with delegation link,
  Mulet> the routage algorithm could be handled by parent methods.

I can't quite follow what you're looking for here.  Can you give an
example?

[...]
> Yes, but, at least in Self, it's infinite recursion introduced by the
> programmer, not by the system (as it is in the infinite recursion in
> the classes and metaclasses of Smalltalk and Dylan).

  Mulet> First the infinite regression of metalink in Smalltalk is not infinite at all,
  Mulet> it is closed in a very elegant way by admitting a circularity around Metaclass.

You're right, it isn't infinite. But I see the recursion as being
broken, not in an elegant way, but by admitting an arbitrary rule:
"This single special-case object doesn't follow the rule that applies
to every other object in the system."  I don't like rules like that.
I find it nicer and more elegant to set up the system without the
recursion that requires a special rule just to break out of it (i.e.,
Self).

  Mulet> Then you can easily introduce programmer's infinite recursion in Smalltalk,
  Mulet> much harder is to stop them.

If you couldn't recurse, it would be a poorer language for it.  I feel
that a system is easier to understand the fewer special rules it has
governing  the way it works.  Recursion as part of the system's rules
is one thing: recursion I write down myself is another.  The first is
a roadblock to understanding the system.  The second is something I
have a much greater chance of understanding, because I wrote it.

  Mulet> Self contains potential infinite regressions through mirrors and slot descriptors
  Mulet> that are dealed with lazy creation, but nevertheless it exists.

How do you mean?  Last time I checked, any time you asked for a mirror
on an object, you always got the same mirror.  I.e., mirrors were in a
one-to-one correspondence with object identity (hmm -- another way to
think of mirrors? The object's "identity"?).

> Perhaps some thinking from the land of lazy evaluation is in order. I
> was impressed by how elegant it is to define an infinite sequence in
> languages like Haskell.  E.g., the list of all even numbers looks like
> this (excuse the syntax -- this is just the idea):
> 
> def allEvens = [ 2, 2 * allEvens]
> 
  Mulet> Lisp/Scheme programmers are used for years to use such constructions, called streams.
  Mulet> They are easily implemented with closures, and probably inspired Haskell.
  Mulet> In Smalltalk and Self thanks to blocks you can achieve the same result to
  Mulet> create such infinite series.

Yes, of course.  Although I suspect lazy evaluation inspired streams,
not the other way around (Lisp programmers implemented Prolog in a few
pages of Lisp code, but not until after Prolog had been described).
But I don't have any references to verify this.

The point in Haskell, though, is not that you can get the same kind of
functionality somewhere else (this is not a Turing-equivalence
argument), but that the mechanisms of the language intrinsically
provide streams without extra garbage.  You can't simply take the next
element of the list if you're using closures in something like Scheme
In Lisp, (car  (cdr allEvens)) is a closure, not a number.  In
Haskell, it's a number, not a closure.  Haskell hides the
implementation. Lisp doesn't.  In Self, you can do better: an object
with code and no argument slots is indistinguishable from an object
(analogous to the equivalence in functional programming of literal
constants and functions that return constants).

Brook