Hello, I spent most of the last week reading the pdfs from the vpri.org site and I am bowled over. It's amazing what you're accomplishing. Congratulations.
I am trying to understand the COLA architecture by way of building a model of it in the python language. I think I've got the basic object model down okay, but I'm having trouble grokking the eval/transform system. I suspect there's an elegant twist there I'm just missing. The attached files are: co.py - a python version of the basic object model. la.py - an attempt at making the transformation engine. rootbeer.py - a "port" of la.py to the co.py object model. Could someone take a look at those, or just fill in some detail about section 5 "Behaviour: symbolic expressions and transformations" from http://www.vpri.org/pdf/rn2006001a_colaswp.pdf "Accessible Language-Based Environments of Recursive Theories"? I think I'm missing something about how the transformation engine is supposed to work. I dug through the Jolt(?) code but I could locate the magic. Thank you in advance, and thank you all for doing such incredible work. The object model alone is breathtaking. Warm regards, ~Simon -- "The history of mankind for the last four centuries is rather like that of an imprisoned sleeper, stirring clumsily and uneasily while the prison that restrains and shelters him catches fire, not waking but incorporating the crackling and warmth of the fire with ancient and incongruous dreams, than like that of a man consciously awake to danger and opportunity." --H. P. Wells, "A Short History of the World"
''' A simple model of an Object Model as per: "Open Reusable Object Models", Ian Piumarta, Alessandro Warth http://www.vpri.org/pdf/tr2006003a_objmod.pdf This departs in several ways from the OM described in that paper. It's meant mainly as a rough sketch done to better understand the ideas. There is no specific symbol type, we just use strings. The vtable also has no specific type, it's just an Object. Last, I lean on the python dict type to act as the vtable's storage. ''' class Object: ''' Root of the whole system, has a vtable and some data. ''' def __init__(self, vtable): self.vtable = vtable self.data = None def send(obj, name, *args, **keyword_args): method = bind(obj, name) return method(obj, *args, **keyword_args) def bind(obj, name): vt = obj.vtable if vt is obj and name == 'lookup': return lookup(obj, name) return send(vt, 'lookup', name) def addMethod(vtable, name, implementation): vtable.data[name] = implementation def lookup(vtable, name): try: return vtable.data[name] except KeyError: if vtable.parent is not None: return send(vtable.parent, 'lookup', name) # Return None if nothing is found, will cause exception higher up. def allocate(vtable): return Object(vtable) def delegated(vtable): if vtable is None: child = allocate(None) else: child = allocate(vtable.vtable) child.parent = vtable child.data = {} # Map names to methods. return child def bootstrap(): # Create the VTable's VTable. vtvt = delegated(None) # VTable is its own VTable. vtvt.vtable = vtvt # Create a VTable for Objects. (Use delegated(None) so it has no # parent. In other words Object, not VTable, is the root of the # system.) object_vt = delegated(None) # Manually tell it it's a VTable. object_vt.vtable = vtvt # VTable is a kind of Object. Thus its VTable's parent is Object's # VTable and since it's its own VTable we just set its parent here. vtvt.parent = object_vt # Give VTable a lookup method by directly calling addMethod(). addMethod(vtvt, 'lookup', lookup) # Now the send() and bind() machinery will work. assert lookup is send(vtvt, 'lookup', 'lookup') # Add addMethod() to the VTable. addMethod(vtvt, 'addMethod', addMethod) # We can add the rest using send(). send(vtvt, 'addMethod', 'allocate', allocate) send(vtvt, 'addMethod', 'delegated', delegated) # We're done. return object_vt, vtvt
#!/usr/bin/env python ''' This is my crude attempt to understand section 5 of "Accessible Language-Based Environments of Recursive Theories", Ian Piumarta - http://www.vpri.org/pdf/rn2006001a_colaswp.pdf I don't quite grok it yet. This is sort of a "pure python" version of what I think should be going on. The __main__ clause below tries to exercise the Eval() call in a simple but hopefully useful way, and then rootbeer.py tries to "port" the same code to use OM objects from co.py (although the OM ast object simply wraps the AST python object from this module. The part I don't quite get is how a "transform property" is associated with the symbols, or in other words, how the get_property() function is supposed to work. I suspect there's a lovely hidden elegance there that will come out if I play with this long enough. Something to do with the way transform() is applied to symbols vs normal objects... Dunno. ''' # Main engine: Eval() and transform(). def Eval(tree, context): if tree is None: return None t = typeof(tree) method = transform(t, context) result = method(tree, context) return Eval(result, context) def transform(tree_type, context): if isSymbol(tree_type): return get_property(context, tree_type) else: return Eval(tree_type, context) ##===----- # Engine depends on these helper functions. def typeof(thing): return thing.symbol def isSymbol(t): return isinstance(t, basestring) def get_property(context, symbol): return getattr(context, symbol, null) ##===----- # Which expect an AST with a symbol attribute and zero or more children. class AST(list): def __init__(self, symbol, *values): self.symbol = symbol self.extend(values) def null(atom, context): print 'null', atom, context ##===----- # Let's play with it. if __name__ == '__main__': # Create a fev different AST types. def makeLiteral(value): return AST('literal', value) def makeSequence(*values): return AST('sequence', *values) def makeOp(op): return AST('op', op) # Instantiate some. cats = makeLiteral("Cats") i23 = makeLiteral(23) from operator import mul mul = makeOp(mul) proggy = makeSequence(cats, i23, mul) # Now we need some contexts to evaluate this AST tree within. # Shared ability to evaluate sequences of ASTs. class Context: def sequence(self, thing, context): for item in thing: Eval(item, context) # Print everything class PrintContext(Context): def literal(self, thing, context): print thing[0] op = literal # Act like a sort of interpreter. class ActiveContext(Context): accumulator = [] def literal(self, thing, context): self.accumulator.append(thing[0]) def op(self, thing, context): op = thing[0] a, b = self.accumulator[:2] result = op(a, b) self.accumulator[:2] = [result] # Run in a "print out" context... Eval(proggy, PrintContext()) print '------------------' # Run in an "interpret" context... c = ActiveContext() Eval(proggy, c) print c.accumulator
#!/usr/bin/env python ''' Playing with integrating la.py and co.py So this is basically an attempt to understand: "Accessible Language-Based Environments of Recursive Theories", Ian Piumarta - http://www.vpri.org/pdf/rn2006001a_colaswp.pdf by implementing [part of] it in python. The co module implements the object model as simply and directly as I could, and the la module tries to implement the transformation engine using that object module. I'm not quite getting it. This works, but I think there's a much more elegant system just out of sight and the secret's in the way you dispatch the transform method to objects vs symbols. I'm not sure though. ''' from co import bootstrap, send from la import AST, Eval # Create a universe to play in... object_vt, vtvt = bootstrap() ########################################## # Let Objects tell you if they derive from a certain type. def isA(thing, family): '''Return bool indicating if thing "is a" family vtable.''' vt = thing.vtable while vt is not None: if vt is family: return True vt = vt.parent return False send(object_vt, 'addMethod', 'isA', isA) ########################################## # Create a Symbol object type. symbol_vt = send(vtvt, 'delegated') # Let Objects tell you their symbol/type. def typeOf(obj): try: return obj.data.symbol except AttributeError: pass # ?? # Let Objects tell you if they are Symbols or not. def isSymbol(symbol): return send(symbol, 'isA', symbol_vt) # Allow for getting and setting names on symbols. Symbols simply add an # attribute to themselves at runtime. Don't call getName() on a symbol # that hasn't had setName() called first... Contrast this to the ast # object that actually wraps the AST python object from the la.py module. def getName(symbol): return symbol.name def setName(symbol, name): symbol.name = name send(object_vt, 'addMethod', 'typeOf', typeOf) send(object_vt, 'addMethod', 'isSymbol', isSymbol) send(symbol_vt, 'addMethod', 'getName', getName) send(symbol_vt, 'addMethod', 'setName', setName) # Some symbols: LIT = send(symbol_vt, 'allocate'); send(LIT, 'setName', 'literal') SEQ = send(symbol_vt, 'allocate'); send(SEQ, 'setName', 'sequence') OP = send(symbol_vt, 'allocate'); send(OP, 'setName', 'op') ########################################## # Create AST object type. AST_vt = send(vtvt, 'delegated') # Give ASTs an init method. Notice that this OM object wraps the python # object? Isn't that cool? It's just like the paper says, you can host # alien objects no problem. def initAST(ast, symbol, *values): ast.data = AST(symbol, *values) send(AST_vt, 'addMethod', 'init', initAST) # Create a few different AST types with some handy factory functions. def makeLiteral(value): ast = send(AST_vt, 'allocate') send(ast, 'init', LIT, value) return ast def makeSequence(*values): ast = send(AST_vt, 'allocate') send(ast, 'init', SEQ, *values) return ast def makeOp(value): ast = send(AST_vt, 'allocate') send(ast, 'init', OP, value) return ast ########################################## # Now we need a context to evaluate this AST tree within. This is the # part I think I'm messing up: the way that symbols and contexts are # supposed to interact to provide the actual transform methods via the # get_property() function. context_vt = send(vtvt, 'delegated') def sequence(thing, context): for item in thing.data: Eval(item, context) def emit(thing, context): print thing.data[0] send(context_vt, 'addMethod', 'sequence', lambda context: sequence) send(context_vt, 'addMethod', 'literal', lambda context: emit) send(context_vt, 'addMethod', 'op', lambda context: emit) print_context = send(context_vt, 'allocate') ########################################## # One last thing to do before this will work, replace the following three # functions in la with these versions. def typeof(thing): return send(thing, 'typeOf') def isSymbol(thing): return send(thing, 'isSymbol') def get_property(context, symbol): # context is a vtable, symbol is a selector, a method name, so... # name = send(symbol, 'getName') # method = send(context, name) # return method return send(context, send(symbol, 'getName')) # We're ready to plug these in to the la machinery. import la la.typeof = typeof la.isSymbol = isSymbol la.get_property = get_property # Eval() and transform() should use the above functions now. ########################################## # Instantiate some ASTs. from operator import mul cats = makeLiteral("Cats") i23 = makeLiteral(23) mul = makeOp(mul) proggy = makeSequence(cats, i23, mul) # Run in a "print out" context... Eval(proggy, print_context)
_______________________________________________ fonc mailing list [email protected] http://vpri.org/mailman/listinfo/fonc
