← Back to context

Comment by curtis

10 years ago

Just reading the submission title instantly reminded me of "Anton van Straaten's Koan", and for good reason -- it's quoted in the submitted page, and indeed makes up the first 3 paragraphs.

Here it is:

  The venerable master Qc Na was walking with his student, Anton. Hoping
  to prompt the master into a discussion, Anton said "Master, I have
  heard that objects are a very good thing - is this true?" Qc Na looked
  pityingly at his student and replied, "Foolish pupil - objects are
  merely a poor man's closures."

  Chastised, Anton took his leave from his master and returned to his
  cell, intent on studying closures. He carefully read the entire
  "Lambda: The Ultimate..." series of papers and its cousins, and
  implemented a small Scheme interpreter with a closure-based object
  system. He learned much, and looked forward to informing his master of
  his progress.

  On his next walk with Qc Na, Anton attempted to impress his master by
  saying "Master, I have diligently studied the matter, and now
  understand that objects are truly a poor man's closures." Qc Na
  responded by hitting Anton with his stick, saying "When will you
  learn? Closures are a poor man's object." At that moment, Anton became
  enlightened.

Now I will be the first to admit that I usually just don't get koans. But I think I get this one. Objects and closures are equivalent in the sense that what you can do with one you can do with the other. However, for each particular case one may be better (easier to use, more natural, whatever) than the other. You should use which ever one is the best for each particular problem.

This is of course easier if you are using a programming language that natively supports both closures and objects...

The way I like to put it is that instances and closures are duals: a closure can do exactly one thing, while an instance can, in general, do several things. That's why, when invoking an instance, you have to supply a method name that says which of the several things you want it to do, while when invoking a closure no method name is necessary.

Of course it's not a hard-and-fast distinction: you can have a closure that dispatches on one of its parameters to do one of several different things, and you can have an instance with only one method. But it's usually valid.

  • If you think of a closure as only returning a reference to a function, but a closure can return anything. What if my closure returns an ssh connection to a new machine? A database? A program that generates programs? A hash of functions specialized to my calling arguments? How is a the function that returns a closure different from a constructor?

  • You can have several different functions that all close over the same state. This situation is analogous to an object with several different methods.

    • You can also do it over just one function that takes in a variable that modifies behavior.

      Imagine you have a closure with a function that takes in a string and a list and returns a list. Depending on implementation of the function, you could have the string be the operation you wish to perform. The method name, and the list be the parameters. The returned list is the output.

      Getters could pass an empty list and return a list with one entry

      Setters could pass a list with a value and return an empty list

      And so on :)

      Like this:

         func NewClosure() func(string, ...float64) []float64 {
            x := float64(0)
            return func(method string, args ...float64) []float64 {
               if method == "setX" && len(args) > 0 {
                  x = args[0]
                  return nil
               } else if (method == "getX") {
                  return []float64 { x }
               } else {
                  panic("invalid method")
               }
            }
         }
      
         a := NewClosure()
         b := NewClosure()
      	
         a("setX", 50)
         b("setX", 12)
      	
         fmt.Printf("a.X = %v, b.X = %v", a("getX")[0], b("getX")[0])
      

      Runnable example here: http://play.golang.org/p/68NTyEx6_P

      I like it.

      [edit: added working example .. and realized I'm adding to what my parent poster was saying]

    • For this reason, I think of objects as not equivalent to closures, but rather ML-style functors. I think implementing objects as records with closures as fields is really just a poor man's parameterized module.

I think it depends a lot on the language's syntax and what sugar it offers. I believe there are choices you can make that make closures superior to objects in every possible way.

Consider this simple syntactic rule: `obj.x` <=> `obj("x")`. With this rule, the usual syntax for a method call, `obj.method(x)`, literally becomes the curried application of a closure, `obj("method")(x)`. You could also add a rule like `obj(x) = y` <=> `obj(set(x, y))`. Then you could define objects a bit like this:

    point = (x, y) -> method ->
        match method:
            "x" -> x
            "y" -> y
            set("x", newx) -> x = newx
            set("y", newy) -> y = newy
            "add" -> (pt) -> point(x + pt.x, y + pt.y)
    p = point(1, 2)
    p.x = 5
    print p.add(point(3, 4))

All closures. And the nice thing with that scheme is that meta-programming and reflection become completely trivial. Control over the interface is total. What use is there for "real" objects in this situation?

They are not exactly equivalent though. It is not possible to completely abstract away state changes with objects. You always end up with at least two calls that you have to keep track of.

  • I'm not sure that I would agree here. Do you have a simple example?

    •   foo.DoSomething()
        ... // remember not to return 
            // without foo.Done() called
            // (EDITed for clarity: 
            // foo is the state, that changes,
            // so you have to keep track of 
            // its changes)
        foo.Done()
      

      vs

        DoSomething (func(){
           ... // don't have to remember 
               // a thing, no side-effects, 
               // equivalent of .Done() is
               // called afterwards for you
        })
      

      EDIT2: exact behavior is not the point, keeping track of things is.

      6 replies →