Interesting? closure/object idea

I've been designing a language for my kids to use (goal: low barrier to entry, gentle learning curve up to serious power); one of the design choices has been to make closures easy to write. {x: x+5} is equivalent to Scheme's (lambda (x) (+ x 5)); {x*y} is a closure with no arguments. A Graham accumulator would be Acc={var x=0; {x++}} (declare a variable and return a closure over it).

After reading Paul Graham's ILC 2003 talk, I started wondering how much I would need in the language to permit objects to be done as a library. Objects are equivalent to closures, but I wanted to leave room for a convenient syntax. (If nothing else, a lot of the system facilities I'll want to bind to, such as GUI toolkits, are OO, so being able to speak OO lowers the impedance mismatch.)

So, the first thing I thought of was to break encapsulation (again, this was triggered by Graham's talk), to expose the bindings contained in a closure. So, if we do C=Acc(), creating an accumulator as above, then C.x is the current value of x in the closure. Obviously, since bindings in C can also be functions, this also gives us method calls with the usual sort of syntax.

OK, so that can be used to make objects convenient. Then I started wondering about inheritance. After some false starts, I hit upon the idea of letting the right-hand argument of "." be a general expression. So, for example, C.(x+y) would be equivalent to (C.x)+(C.y). Then I could provide a closure constructor as the right-hand argument:

C2=C.{var double={x*=2}}

creates C2 as a closure created within the context of C, with access to C's bindings. This will let me (eventually) do an object library where:

class Point(x,y) {
  to moveBy(dx,dy) {
    x+=dx; y+=dy;
  }
  to abs() {
    sqrt(x*x+y*y);
  }
}
maps to:
Point=
{x,y: {
       var moveBy={dx,dy: x+=dx; y+=dy;};      
       var abs={sqrt(x*x+y*y);};
       }
 }

and:

class PointWithFlip(x,y): Point(x,y) {
  to flip() {
    var tmp=x; x=y; y=tmp;
  }
}

maps to:

PointWithFlip=
{x,y: Point(x,y).{
  var flip={var tmp=x; x=y; y=tmp;}
  }
 }

That is, to create a PointWithFlip, create a Point, and then create a new closure, containing the flip function; that new closure is the PointWithFlip.

I'm pretty happy with this idea; it should let me teach my kids closures, rather than objects as such, but without blocking them from doing OO should it be useful. And, of course, I suspect that being able to get an arbitrary expression evaluated within a closure will turn out to be pretty powerful--with that, you could do things on the fly for which OO requires a subclass. And it doesn't distort the core language much, because you have to be able to construct closures inside closures anyway.

Is this something that's been studied before? If it has, of course, I'd like to read about it before trying to implement it.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Deja vu all over again - Smalltalk?

"designing a language for (my) kids to use
iirc that was one of Alan Kay's intentions with Smalltalk - which is OO with closures (aka blocks)...

Heh. And, in fact, I looked

Heh. And, in fact, I looked at picking up Squeak instead; but I don't like the license (the one that says you have to publish any changes you make; you can't make changes for your own use and keep them private).

...and, OK, the Smalltalk syntax still makes me twitchy, for no good reason.

The main thing that got me thinking about this was that Logo taught me some good stuff when I was a kid (e.g., multithreading), but it has too much of a ceiling; if my kids get sufficiently interested, and skilled, I want them to be able to write anything they want. It's ambitious, but I've got time--they're only 2 years old; I don't even let them touch the keyboard yet. ;-)

Look further

Squeak... but I don't like the license

There are other implementations ;-)

VisualWorks is free for non-commercial stuff.

Free as in freedom

Yeah, but that's not what I want; I want libre, not gratis.

<dig, dig>...but there is GNU Smalltalk; maybe I'll try that.

I'm not sure I understand. W

I'm not sure I understand. Why would your first example map to this:

Point=
{x,y: {
        var moveBy={dx,dy: x+=dx; y+=dy;};      
        var abs={sqrt(x*x+y*y);};
      }
}

instead of this?

Point=
{x,y:  var moveBy={dx,dy: x+=dx; y+=dy;};      
       var abs={sqrt(x*x+y*y);};
}

Also, wouldn't your last example use parens instead of braces?

PointWithFlip=
{x,y: Point(x,y).(
  var flip={var tmp=x; x=y; y=tmp;}
  )
}

In the first example, Point i

In the first example, Point is not the object; it's the class, which is a closure that constructs objects. So {x, y: {...}} takes two arguments, x and y, and returns a closure. The way you suggested would take two arguments, declare two variables, and then not return anything.

In the last example, Point(x,y).{var flip=...} means "construct a Point closure/object, with arguments x and y, and then construct a new closure in that context". If you changed {} to (), that would be more like saying "declare a new variable in that context"...which would work fine, too, now that I think about it. You'd be modifying the Point object/closure instead of creating a...subinstance?...but that's OK, as long as you created it just to be the parent of the PointWithFlip. If you had a single Point object, and wanted to create multiple subinstances, then you'd need to do it the {} way.

Mind you, the effects are different. In the {} way, methods in the parent closure can't call methods in the child closure by name, because they aren't in scope. In the () way, they're in the same scope. That's probably preferable.

In the last example, Point(

In the last example, Point(x,y).{var flip=...} means "construct a Point closure/object, with arguments x and y, and then construct a new closure in that context". If you changed {} to (), that would be more like saying "declare a new variable in that context"...which would work fine, too, now that I think about it. You'd be modifying the Point object/closure instead of creating a...subinstance?...
My expectation would be that the effect of replacing {} by () is to get the a single zero-argument function "flip". As you said in the beginning, C.(x+y) is equivalent to (C.x) + (C.y). Doesn't that mean that

Point(x, y).(var flip = {var tmp= x; x= y; y= tmp})

equals

var flip = Point(x,y).{var tmp= x; x= y; y= tmp}

Now, when you say that "in the {} way, methods in the parent closure can't call methods in the child closure", I take it to mean that the "." dot operator still behaves like a selection. That is, the result is the right operand evaluated in the context of the left operand closure. But in your examples you seem to want the operator to merge the left closure and the right argument into a new closure. If the latter is indeed the case, I don't see why the members of the left closure couldn't have access to the right-hand members after the merge. And I see some benefits in allowing them. For example:

Point=
{x,y: {
       var moveTo={x1,y1: x=x1; y=y1;};      
       var moveBy={dx,dy: moveTo(x+dx, y+dy)};
       }
 }

FixedXPoint=
{x,y: Point(x,y).{
       var moveTo={x1,y1: y=y1;};
       }
 }

If I understand your semantics correctly, the FixedXPoint should return a point with a fixed X but which still has moveTo and moveBy member methods accessible. If, in the resulting merged closure, you let the moveBy method from Point call the moveTo method from the right-hand side of dot operator, you get something like OOP's dynamic method binding.

My expectation would be that

My expectation would be that the effect of replacing {} by () is to get the a single zero-argument function "flip". As you said in the beginning, C.(x+y) is equivalent to (C.x) + (C.y). Doesn't that mean that

Point(x, y).(var flip = {var tmp= x; x= y; y= tmp})

equals

var flip = Point(x,y).{var tmp= x; x= y; y= tmp}

Um...no. Closure.expr means "evaluate expr in the lexical scope of Closure". If expr is a variable declaration, the effect is to declare a new binding in that scope. So:

Point(x, y).(var flip = {var tmp= x; x= y; y= tmp})

means "call Point(x,y) and declare the binding flip = {var tmp= x; x= y; y= tmp} in the resulting closure". So, in object terms, it creates a new Point and then modifies it. But, on the other hand, this:

Point(x, y).{var flip = {var tmp= x; x= y; y= tmp}}

means "call Point(x,y), and then, in the resulting closure, construct a new closure {var flip = {var tmp= x; x= y; y= tmp}}". So, the result is that it creates two closures; call them C1 (the one constructed by Point(x,y)) and C2 (the one constructed by {var flip = ... }). C2 is nested within C1, so C2 has access to C1's bindings, but not the other way around.

I take it to mean that the "." dot operator still behaves like a selection.

"Selection"--is that what this is called? Cool. <citeseer>...here's some stuff about it. Thanks; that'll help!

That is, the result is the right operand evaluated in the context of the left operand closure.

Yes.

But in your examples you seem to want the operator to merge the left closure and the right argument into a new closure

That's what I want, for the purpose of OO inheritance--except that, since C1 code>Point(x,y) is created from scratch, I don't need to construct a new closure; I can just modify C1.

Yes, it's interesting, but...

I'm not sure this provides any new, useful ideas. It is a cool way to look at objects, though. Could you explain to me how this is any easier for beginners than Scheme or Smalltalk (I think the syntax wouldn't be so annoying to you if you just got used to them)? I have two main gripes:

I might be wrong, but it looks like the syntax for this is builtin. You keep talking about it being in a library, but if these transformations are builtin, then it might make the OO system a little less modular from the rest of the language. I noticed that the syntax of the object system looks very similar to that of other popular languages, yet you're talking about this being a good first language; how does syntax similar to other languages help beginners, who have never learned to program before?

You've left out many of the OO features that we've come to know and love. There are two main types of OO that I've seen: Smalltalk-style and CLOS-style. On top of type dispatch, they add different feature sets. Smalltalk-style adds seperation between interface and implementation, by way of making private instance variables and private and protected methods. AFAICT, this doesn't do that. You do have instance methods, and I think that that's cool, but you're lacking many other features. If you didn't want those, you could've gone with CLOS-style, but you didn't. With CLOS-style OO, there is multiple dispatch, meaning that you can dispatch on any of the arguments, not just the first. This eliminates the need for visitor pattern, and the help it provides crops up in unexpected places, such as a + function. I don't think this OO system supports that either.

Why

Could you explain to me how this is any easier for beginners than

I should point out that this idea in particular is not part of the "easy to learn" work (except in that it makes the core language smaller). I'm not interested in making the language do OO superbly (I'm starting to lose my OO religion); I mostly want to make sure it can interface adequately to things like Gtk, without having to have a lot of special-purpose bindings.

Accordingly, what I found really interesting about this idea was not so much the implications for OO as the implications for closures. It seems like you ought to be able to pull some interesting rabbits out of your hat when you can break encapsulation and ask a closure to perform an arbitrary computation.

I might be wrong, but it looks like the syntax for this is builtin.

The "." operator would be builtin; the "class to closure" transforms would be some sort of macro library (I haven't nailed that down yet).

Smalltalk-style adds seperation between interface and implementation, by way of making private instance variables and private and protected methods. AFAICT, this doesn't do that.

That's true. That was deliberate; between my experience with Python (which has no privacy) and Paul Graham's comments about encapsulation being just one more way of tying the programmer's hands, I wanted to leave it out.

With CLOS-style OO, there is multiple dispatch, meaning that you can dispatch on any of the arguments, not just the first.

Mmm. I'd have to try CLOS first; my initial reaction is suspicion, because f(a,b,c) could be defined pretty much anywhere. In theory, C++ lets you do that, too; but, in practice, I've rarely used function polymorphism for non-member functions.

Actually, I'm not sure how to do dispatch on types in a language without type declarations...maybe that ought in itself to be a hint to look at CLOS, since Lisp doesn't do type declarations, either. (Or does Common Lisp do them? I've only done elisp and Scheme; I looked at Common Lisp 10-12 years ago, but I didn't have access to an implementation then.)

First Thought

{x: x+5} is equivalent to Scheme's (lambda (x) (+ x 5))

My first thought was: why infix notation? You're going to be using prefix notation for regular function calls, why should arithmetic functions be any different? Infix notation is what you get thought in school, along with special rules governing the order of evaluation. It doesn't work well with more or fewer than two arguments. Really, I believe prefix (or postfix) notation is vastly superior.

I think it's reasonable to wr

I think it's reasonable to write "2+2" because we say "two plus two". We can also say "the sum of two and two", but it's clumsier.

I will grant that it makes sense to have equivalent prefix functions as well, perhaps with the same names, so you've got something more general; but I don't see any reason to omit the infix.

everyone (else) loves infix

It doesn't work well with more or fewer than two arguments.

Well... that's the point. Binary operations are common, and it works really well for those.

You could just as well argue that parens and other delimiters ("circumfix" notation, I believe the Perl folks call it) are also redundant because you can use postfix notation like Joy does. But it's more readable with delimiters. Delimiters do a great job of conveying the idea that "these things are what I'm talking about".

Similarly, infix does a great job of conveying a special relationship or operation between two things. Even lispers write stuff like this (from paulgraham.com):

(type cons) -> fn
"->" is not code here, but it is an infix notation for explaining a concept. Often, infix is just clearer. Why shouldn't a language try to take advantage of this?

Like Pico or Lua

Your language reminds me of Pico and Lua.

See:

Pico: http://pico.vub.ac.be/
Lua: http://www.lua.org/

Yes, I see...

I see your point. Pico itself doesn't appeal to me (targeted at the wrong people, for one thing), but some of the ideas are analogous; for example, an object constructor is a function that returns a lexical context (the chief difference being that my language makes it a closure, so that you can call it). And they're using arrays to represent code, as Lisp uses lists; I didn't mention it, but I'm planning to use arrays.

Lua...hmm. Its fallback system may be interesting, although I think I prefer to implement semantics by defining semantics, rather than by means of error handling.

Python?

In Python, methods are actually closures over the object.

>>> class Point:
...    def Flip(): pass
...
>>> p = Point()
>>> p.Flip
<bound method Point.Flip of <__main__.Point instance at 0x007A7990>>

Oh, yeah...

Oh, yeah, I'd forgotten about that.

I did actually consider just teaching the kids Python. I like it; I use it; I'll probably use it for prototyping this language. (I expect I'll eventually switch to C or something for speed.) But some of its constructs are, ah, not what I would consider optimal. Mostly little warts (e.g., triple-quoting), but what really get me are the limitations on lambda and the lack of true nested scope.

Nested scopes

Forgive me, I don't have formal CS education.

What is the difference between true nested scopes and the nested scopes Python introduced in 2.2?

Python nested scopes

Suppose, in Python, one function scope is nested in another function. An statement in the inner scope may refer to a variable of the outer function, but there's no way to assign to such a variable.

What annoys me most about Python is that the meaning of a name often depends on whether it's an ordinary use or it's being assigned to.