At 01:31 AM 10/19/98 , Ka-Ping Yee wrote:
>
>Further to the message "Good Things About E":
>
>Objects with a run method are like functions -- we can turn a
>"function" into a "method" in Python like this:
>...
>In E we can do this by delegating the method call:
>
> ? define DogMaker() {
> > define barkfunc() {
> > println("woof!")
> > }
> > define Dog { # note 1
> > to bark {
> > barkfunc
> > }
> > to setBarkFunc(func) {
> > barkfunc := func
> > }
> > }
> > }
> # value: <DogMaker>
>
> ? d := DogMaker()
> # value: <Dog> # note 2
>
> ? d bark
> woof!
>
> ? define newbark() {
> > println("arf!")
> > }
> # value: <newbark>
>
> ? d setBarkFunc(newbark)
> # value: <newbark>
>
> ? d bark
> arf!
>
> ?
[+] This is a really cool example, I like it! I assume you were simulating an E machine in your head rather than using one? I copy-pasted it into elmer, and except for one mistake, it ran *exactly* as you thought. The one mistake is that the bark method should instead read
to bark {
barkfunc()
}
As originally written, the bark method was an accessor that returned the current barkfunc. I won't get on your case (this time) about "barkfunc" vs "barkFunc" ;)
>(note 1: I would like to have written "define self" here,
>as i assume this would mean that the code inside the object
>would refer to the object as "self" rather than "Dog".
>However, i suspect this would cause us to get, at note 2,
>"# value: <self>", which isn't very informative. ..?..)
[+] Exactly correct.
For both these reasons, I've been tending towards names more informative
than "self". "self" is used by the inheritance pattern as the instance
variable name by which all slices in a tower refer to the bottom slice, but
each slice still has its own slice-name, and it's the slice-name that gets
printed out. More on this in later email about inheritance.
>What about going the other way -- getting a "function"
>from a "method"?
>
>In Python this is simply
>
> >>> d.bark
> <method Dog.bark of Dog instance at 80acf48>
>
>It can be invoked like this (the bound method object knows
>both the function code and the instance):
>
> >>> f = d.bark
> >>> f
> <method Dog.bark of Dog instance at 80acf48>
> >>> f() # operates on d
> woof!
[#] f() in E.
Since the above presumes d is in scope, and we want an f specific to d,
? define f() {
> d bark
> }
# value: <f>
? f()
arf!
# value: arf!
(an actual elmer session)
>Or, you can get the unbound method directly from the class:
>
> >>> Dog.bark
> <unbound method Dog.bark>
>
> >>> g = Dog.bark
> >>> g
> <unbound method Dog.bark>
> >>> g()
> TypeError: unbound method must be called with class instance 1st argument
> >>> g(d)
> woof!
[#] g(dog) in E.
? define g(dog) {
> dog bark
> }
# value: <g>
? g()
# problem: java.lang.NoSuchMethodException: run/0
? g(d)
arf!
# value: arf!
>To provide the interesting abilities of functional
>programming languages, do we need a macro which expands
>
> method(object, <method>, arity)
>
>into
>
> define _ { # note 3
> define obj := object # note 4
> to run(arg1, arg2, ...) { # note 5
> obj <method>(arg1, arg2, ...)
> }
> }
>
>so as to bring any particular method "to the front" as
>the one standing in for the lambda?
[?] I don't get it.
Btw, do my above examples demonstrate that we don't need any such
additional mechanism?
The line with "# note 4" is an error, as all that does inside an object definition are method and matcher definitions. An object combines state-variables and behavior. An object expression evaluates to an object. An object gets its state-variables *only* from the lexical environment in which it was evaluated. An object gets its behavior only from the methods and matchers inside the object expression, plus the miranda methods.
>(note 4: in some other languages you could write the
>equivalent of "define object := object" to bring in
>the current value of "object" from an outer scope.
>What does "define object := object" mean in E?)
[-] It is unnecessary, as you imply that object is already in scope.
[And now for something completely different.] The above expression creates an extraordinarily strange piece of data.
Recall that the scope of a definition lasts from the defining occurrence of a variable name (the first occurrence of "object" above) to the enclosing close curly. (These scope issues are explained adequately, for the first time, in the documentation that will be appearing on the web site soon.) The second occurrence of "object" is therefore in scope of the first one. The result is a tight reference loop. "object" now refers to the tail of an object reference arrow whose head points back to the same arrow-tail. The above code is equivalent to:
define [object, resolver] := PromiseMaker() resolver forward(object)
A less perverse use of the same circular definition ability:
define tree := [1, tree, 3]
defines "tree" to be the infinite tree [1, [1, [1, ..., 3], 3], 3].
Trees of this form are known as "infinite rational trees", and their use in a programming language were first explored well by Colmerauer in Prolog III. Because distributed programming prohibits certain kinds of local knowledge of non-local facts, E cannot prevent the formation of cyclic data structures -- a cycle can be formed by separate actions taken by machines that don't locally know enough to know they are creating a cycle. Fortunately, as shown by Prolog III, much power flows from making your system deal with cycles correctly. This power is gained not by adding features but by removing restrictions. (E currently forms cycles correctly, but once formed, it doesn't yet deal with them correctly. This is a bug.)
>(note 3: this brace means "create me an object";
[+] correct
>note 5: this brace means "make a me a new scope":
>this needs to be pointed out to the E beginner.
>There isn't any other way than "define _" to say
>"make me an object" without "define"-ing anything
>to be initialized to it, is there?)
[?] I don't understand
>Then, do we provide reduce (fold), map, filter, etc.?
[?] Please define these for us old-timers. It's been a while, and I'm not sure if I ever knew these.
>Allow me to attempt an exercise in E...
>
> define fold(func, start, list) {
> switch(list) {
> match [] { start }
> match [car] + cdr { func(car, fold(func, start, cdr)) }
> }
> }
>
>Does that look right?
[?] I don't know.
Working backwards, I'm sad to say that I couldn't figure out what fold is
supposed to mean by casually reading this code.
>On a totally different tack: now that we have optional type
>information, will we need something like attempted assignment
>(?=) to take the place of dynamic casting?
[?] I don't understand
In the alternative to functional programming not functional? --MarkM