Self Programming Language

Self is a prototype-based dynamic object-oriented programming language

Reference

The SELF Object Model
C2
website at Sun
historical

Description

SS: An interesting object-oriented programming language; basic ideas come from SmallTalk but SELF is more flexible and powerful, and probably even more pure.

SELF was used as test-bed for new ideas about compilation and virtual machines of very-high-level programming languages. More info at [L1 ] and [L2 ].

It should be possible to build an object-system for Tcl that uses ideas from SELF; maybe it was already implemented. Please, if you know more, or have some code to show, write it here.

Selfish is at least inspired by SELF.


RS wrote On things, which might interest you.

SS: great! it is pretty similar to SELF, I like the design RS used to add it to TCL.

Artur Trzewik: Some of SELF's ideas are also included in XOTcl; namely mixins. Also, in XOTcl the object has relative more importance than class. Classes are also objects and the relationship between objects and classes is dynamic.

Self is a prototype-based OO language, but some programing techniques can be well-simulated with XOTcl mixins and filters. The manner of how to program with SELF is also implemented in XOTclIDE. That is to interactively extent or change the running, "living" system. The same way Smalltalkers program.

SS: I see a fundamental difference between SELF and XOTcl btw: SELF is simple; there is even no difference between a method and instance variable, everything is a slot, and there are many other simplifications like no classes at all. On the other end of the spectrum XOTcl has a lot of concepts, so I think the essence is very different, while of course with every kind of OO-related feature you can fake the coding way of prototype-based systems, class-based systems, and so on.

KBK 2006-10-12: One of the earliest OO systems for TCL, BOS, was consciously modeled after SELF. Developed by Sean Levy, then of CMU, it had several other interesting ideas. A description can be found at http://www.ndim.edrc.cmu.edu/ndim/papers/bos.pdf ; the paper also discusses Sean's reasons for abandoning Tcl in the implementation. The original Tcl implementation is at ftp://ftp.tcl.tk/pub/tcl/mirror/ftp.procplace.com/sorted/packages-7.6/devel/bos-1.31.tar.gz .


MJ: Basic implementation of a self-like OO system in plain Tcl. Supports super, self and slot in slot bodies. If a slot cannot be found, the unknown slot is called on the object receiving the message.

MJ: The Tcl implementation below has problems when calls to next or self are chained. The C extension at SELF extension doesn't have these problems.

# parents* for method dispatch

namespace eval self {
    set Object(parents*) {data {}}
    set Object(slot) {method 
        {
            {name args} {
                # value slot
                if {[llength $args]==1} {
                    set value [lindex $args 0]
                    set ::self::[self]($name) [list data $value]
                    return $name
                } elseif {[llength $args]==2} {
                    # method slot
                    set arguments [lindex $args 0]
                    set body [lindex $args 1]
                    set ::self::[self]($name) [list method [list $arguments $body]]
                    return $name
                } else {
                    error "wrong number of args: use slot name value/args ?body?"
                }
            }
        }    
    }
}


proc ::self::selfcmd {self args} {
    if {$args eq ""} {
        return $self
    }
    $self {*}$args
}


proc Object {args} {
    set self [namespace which [lindex [info level 0] 0]]
    set slot [lindex $args 0]
    set old_self [interp alias {} self]
    interp alias {} self {} ::self::selfcmd $self

    set slot_value {}
    if {[llength $args]==0} {return $self}
    
    if {[info exists ::self::${self}($slot)]} {
        set slot_value [set ::self::${self}($slot)]
    } else {
        set ancestors [$self parents*]
        set visited {}
        
        while {true} {
            set obj [lindex $ancestors 0]
            if {[llength $obj]==0} {
                break
            }
            # keep a visited objects list to prevent circular lookups
            if {[lsearch -exact $visited $obj]>-1} {
                set ancestors [lrange $ancestors 1 end]
                continue
            }
            lappend visited $obj
         
            catch {set ancestors [list {*}[lrange $ancestors 1 end] {*}[$obj parents*]]}
           
            if {[info exists ::self::${obj}($slot)]} {
                set slot_value [set ::self::${obj}($slot)]
            }
        }
    }
    if {$slot_value ne  {}} {
        switch [lindex $slot_value 0] {
            method { 
                set res [apply [lindex $slot_value 1] {*}[lrange $args 1 end]]

            }
            data {
                if {[llength $args]==1} {
                    set res [lindex $slot_value end]
                } else {
                    self slot [list data [lindex $args 2]]
                    set res [self]
                }
                
            }
        }
        interp alias {} self {} {*}$old_self
        return $res
    } else {
        interp alias {} self {} {*}$old_self
        error "object '$self' can't handle slot '$slot'"
    }
}

set ::self::Object(clone) [list method {{new} {
    # we need the parent slot before we can do any method dispatch
    set ::self::[set new](parents*) [list data [self]]
    proc $new args [info body Object]
    return $new
}}]

set ::self::Object(slot) [list method [list {name args} {
    if {[llength $args]==2} { 
        set ::self::[self]($name) [list method $args]
    } else {
        set ::self::[self]($name) [list data [lindex $args 0]]
    }
    return [self]
}]]

Object slot destroy {} {
    unset ::self::[self]
    rename [self] {}
}
 
Object slot slots {} {
    return [lsort [array names ::self::[self]]]
}

package provide self 0.2

MJ: See Self on a class-based OO system for a partial implementation of Self using the Tip 257 OO extension, or "Basis for a Self like OO system based on coroutines" [L3 ] for ... well, for just what it says.


MJ: Inclusion of coroutines in Tcl 8.6 opens another avenue of approach to implement Self in plain Tcl. It seems an object and a coroutine share some common characteristics making coroutines a good fit to implement OO (this is probably evident for all you CS majors, but it was a bit of a revelation to me). I can see at least two things that should be added to coroutines for this to be feasible:

  • ability to yield an error (possibly with -level to create nice error messages in the same manner as return)
  • ability to call the coroutine with arbitrary # args (yield will always return a list)

A short example of what this could look like is:

proc _object {args} {
    set next [lindex $args 0]
    while {$next ne "destroy"} {
    # actual dispatch loop will be here
    switch [lindex $next 0] {
        parents* {
           set res ""
        }
        clone {
           coroutine Object2 _object
           set res Object2
        }
       {} {
           set a 4
           set res ok
       }
    }
    set next [yield $res]
}

coroutine Object _object

Object parents*
Object clone
Object destroy
Object sadas

This seems to be a dead avenue of approach, because you can't call a coroutine from within itself. This seems to make it impossible to call other object slots from with object slots.

NEM 2010-07-20: The connection between OO and coroutines is perhaps more obvious in the Actor model of concurrency [L4 ]. TIP 328 , which introduced coroutines, discussed both of the limitations you describe, with solutions. See the Limitations section of the TIP . The trick is how to combine the two approaches. There is a discussion of OO-like coroutines at the bottom of Coroutines for event-based programming (search for jcw).

As for calling a coroutine from within itself, there are a number of approaches. One is to do all message sends through the event loop:

proc send {actor args} {
    after 0 [list deliver [info coroutine] $actor $args]
    yield
}
proc deliver {from to msg} {
    set result [$to $msg]
    $from $result
}
proc self args { send [info coroutine] {*}$args }

The problem with this approach is that every message send becomes a yield, leading to lots of interleaving and potential for concurrency problems. Another approach is to split the implementation of the actor into two parts: a core (non-coroutine) ensemble, which just dispatches messages like a normal OO object, and a separate inter-actor send command that yields when appropriate. Messages to self can be routed to the normal ensemble (thus avoiding a yield), while messages to other actors can go through a yield/resume context switch. Variations on this theme are possible, but it seems to me that a conventional OO system works better for these kinds of cases, which is why I've not spent the time to implement it. What would be cool would be to implement actors over real threads using a thread-safe message queue to queue up messages (perhaps using coroutines to sweeten the interface).

NEM: A little later: this discussion inspired me to flesh out the beginnings of such a framework for actor model using coroutines.