(And if some of my complaints seem extreme, blame it on my C/C++
upbringing...)
Doug
Blocks and blocks...
-------------------------
I'm annoyed by code blocks - those things in braces. We use similar,
almost-but-not-quite identical syntax to notate similar-but-not-quite-
identical things - why can't we simplify our lives by making the
"things" entirely identical and use a single syntax?
What do I mean? Consider a "code block" (perhaps a function body,
perhaps one branch of an "if", etc): it's:
- an environment (set of variable bindings from outside the block -
perhaps params/returns, perhaps variables from an enclosing static
scope...),
- a (possibly empty) set of data declarations,
- a (possibly empty) set of nested function definitions, and
- some code that performs some computation on the data available to
it via the environment, and along the way, stores some values into the
data items declared in the block.
--> The declared data items have a "scope" limited to the inside of
the block.
--> The declared data items have a "lifetime" limited to the function
execution.
Now consider a class definition: it's:
- an environment (usually just the params passed to the
constructor),
- a set of data declarations (the object's aggregated fields),
- some function definitions (but we call 'em methods), and
- some code that performs some computation on the data available to
it via the environment, and along the way, stores some values into the
data items declared in the block (we call it a constructor).
--> The declared data items have scopes that depend on how they were
declared (public/private).
--> The declared data items have a lifetime that depends on the
disposition of each object created. (And "static" ones have the
program's lifetime.)
These are almost identical behaviors - only scopes and lifetimes are
really different. Yet for some reason, we write the code in a code
block in-line with the declarations, while for classes we feel a need
to enclose the corresponding code in an anonymously-named function
(named the same as the class in C++, or named __init__() in python
or ...). And that extra syntactic wrapper doesn't seem to do anything
useful for us. Do we need it?
Think about lisp for a minute, particularly w.r.t lambdas and
closures. How might you implement objects in lisp, which lacks all
that specialized syntax? Here's a class definition (f1 ... fn are
just some user-provided blobs of computation code):
(def c (lambda (params)
(let ((x1 (f1 params)) ;; or let* or letrec... as
appropriate
...
(xn (fn params))
(getter (lambda (field)
(cond ((= field "x1") x1)
((= field "x2") x2)
...
((= field "xn") xn))))
(setter (lambda (field val)
(cond ((= field "x1") (:= x1 val))
((= field "x2") (:= x2 val))
...
((= field "xn") (:= xn val)))))
(foo (lambda (z) (:= x1 (+ x2 x3 z))))
)
(lambda (cmd)
(cond ((= cmd 'get) getter)
((= cmd 'set) setter)
((= cmd 'foo) foo)
)
)
)
)
)
That's a class with a constructor, setters, getters, and a method
named foo. Use it like:
(def ob (c params)) ;; more usual (non-lisp) syntax: ob = c
(...)
(def x ((ob 'get) 'x1)) ;; more usual (non-lisp) syntax: x =
ob.x1
((ob 'set) 'x1 val) ;; more usual (non-lisp) syntax:
ob.x1 = val
((ob 'foo) 3) ;; more usual (non-lisp) syntax:
ob.foo(3)
(Yes, there are undoubtedly more efficient, better implementations.
Sue me.)
Using the characterization(s) I started with above,
- the environment is the list of params passed when calling c to
construct an object. (though with lisp, you can also capture values
from enclosing static scopes if you want to.)
- the data declarations are the let/let*/letrec of the xi.
- the method declarations are exemplified by the function foo. (I
think of the getters and setters as part of the data declaration
boilerplate.)
- the code that operates on the input environment (what you probably
think of as the constructor code) is the fn expressions that compute
the initial values of the xi.
The let/let*/letrec construct, the getter and setter functions, and
the final lambda/cond are boilerplate.
Want to make an xi private to the class? Remove its cond entries from
the setter and getter functions. It's a change to the boilerplate.
Want to make an xi readonly? Remove just its entry from the setter
function. It's a change to the boilerplate.
Want a function instead of a class? Replace the lambda that's
returned with whichever xi you want to return as a value. It's a
change to the boilerplate.
So lisp can do this, and fairly simply, showing that code blocks and
class definitions really are specializations of some common, more
general concept.
So what I want is:
- one syntax for all blocks.
- all blocks are first-class entities.
- various options (scope and lifetime) can let you make a block act
like a function body or like a class definition. Or maybe like
something else.
This means (among other things):
- most (all?) control-flow constructs ("if", "while", "foreach",
"when", etc) can just be operators on blocks. So you can create new
ones. ("foreach.in_its_own_thread", anybody?)
- No more icky "return x" statement (anonymous return value - I've
always hated that syntax, for some reason). A block merely alters its
environment - some other compiler mechanism (having to do with
variable binding and "return values" declaration) has the
responsibility for returning the appropriate part of the environment
to a caller, if the block is being used as a body of a called
function. (This may make interprocedural data flow analysis a bit
more tractable?)
- There's only one constructor for a class. Want another (say, with
a different number of params or something)? Write an auxiliary
function. I think this seems cleaner to me, in terms of code hygiene,
as all object construction *must* happen in exactly one place.
Thoughts?
To unsubscribe from this group, send email to noop+unsubscribegooglegroups.com or reply to this email with the words "REMOVE ME" as the subject.
def factorial(n)(fact)
fact = 1
for i = 1 to n
fact *= i
x = factorial(9)