PySqueak: "Self" as the source of many ideas in Squeak
In continuing to contemplate what are the essential ideas of Squeak in terms of using them in an educational environment resting on Python, I decide to revisit some of Squeaks' roots. Many of these are in "Self", a prototype based programming language (and IMHO a constructivist one too). The main page for Self: http://research.sun.com/self/language.html I had read of Self, and met one of the key people once at a conference, and I was inspired by many ideas from those interactions, as well as work with NewtonScript (the native programming language of the Newton, also inspired by Self), but I had never seen the Self system in action. It turns out there is a 15 minute or so video on Self, linked to from here: http://www.merlintec.com:8080/Self Kirby, you mentioned the persuasive power of videos in another context, and I suggest viewing the high definition version of the videos there would be useful in seeing how a graphical environment for students could approach the immediate feedback of the command line, especially with most of today's students who are presumably already "point and click" mouse literate. The key principles mentioned in movie for Self's design are based on ideas from the physical world, and are summarized as: * Directness (everything is potentially visible and touchable) * Uniformity (you can consistently change everything) * Modelessness (always running, so no editing/running distinction) Self also demonstrates multiple people doing activities together in one (2D) environment. [BTW Neometron's Learningworks was about 3D communities using Smalltalk and was deployed a decade ago, which shows some other roots of Squeak/Croquet roots here.] From the paper here (first seen in a link below): http://research.sun.com/self/papers/programming-as-experience.html "Finally, in designing Self, we have learned one lesson by making mistakes: examples can persuade the designer to include additional features which later turn out to produce incomprehensible behavior in unforeseen circumstances. This might be called the language designer's trap. Minimalism, simplicity and consistency are better guides. They benefit every programmer, not just the ones who need advanced features. We suspect that many of today's object-oriented languages could profit by dropping features." In my own work in Smalltalk development, knowing that Self existed, and having experience with prototypes in Newtonscript, I was often frustrated by Smalltalk not having Self's elegance. And I think many of the issues Self addresses (particularly eliminating a class side / instance side distinction) are very important to making programming easy to get into, as well as keeping it more fun. In the movie near the end they make the distinction between telling objects what to do (writing code) and just banging them around into position (which Self's GUI helps support, in addition to programming). By building in "Morphic" (the Self GUI paradigm) Squeak tries to get this Self leverage, but I think, given Squeak's use of Smalltalk (which has classes), Morphic always seemed to me to be rather inelegantly bolted on (this is not a slam at the developers, just an inherent problem of a class based programming system trying to work with dynamic constructivist material). One of the things I have always liked about Python is its use of dictionaries for storing values in instance objects. This is computationally inefficient compared to looking up things by position in an array as C or Smalltalk do, but it is more flexible in terms of taking any specific instance and adding more data fields to it, so it is efficient in a human-oriented way and can lend itself somewhat to this sort of prototype based work. So, another idea when thinking about PySqueak is: how can Python better support prototype based development? As well as: how can what we have with Python2.4/Jython2.1 now be used to do that in interesting ways? Now this certainly isn't a new idea, see for example these discussions: http://lists.canonical.org/pipermail/kragen-hacks/2000-September/000264.html http://www.thescripts.com/forum/thread30832.html http://www.prothon.org/ http://www.google.com/search?hl=en&q=Prothon or Prothon's apparent successor, PyCS: http://www.prothon.org/pipermail/prothon-user/2004-August/003269.html http://pycs.org [However, the Prothon and Pycs links seem to be down right now, I read them through the Google cache just now as this project is new to me, perhaps an indication the project is ended?] These three key principles above, relating to a dynamic nature of the Self system, are probably needed to support any constructivist approach to software creation and use. But notice how they are the opposite of the principles behind something like C++ or other statically typed languages, which focus on data hiding, specialized interfaces, and set-in-stone compiled applications. Or as Alan Kay put it: http://www.smalltalk.org/articles/article_20040731a.html "I invented the term Object-Oriented, and I can tell you I did not have C++ in mind." This statically compiled approach culminates brilliantly in something like OCaml, http://en.wikipedia.org/wiki/Ocaml since if you want types with the least pain and syntactic clutter, OCaml can infer many of them for you. C++ or OCaml are pretty much about specifying everything in advance and locking down the system, assuming it will never change at runtime, to get the most out of memory and computer cycles focused on a narrowly defined fixed problem. This is not to say C++ or OCaml can't be used to make open systems (say, by defining a Smalltalk or Common Lisp or Python virtual machine in some way), but openness at runtime is clearly not what those languages are about. Now there are places for that sort of efficient and effective closedness, but it is a very different philosophy from creating open systems like Self, or Squeak. Python clearly potentially can be used in a Self-like or Squeak-like open way, and some core development tools rely on that, but in practice most of the time Python seems not to be used that way. Perhaps this is due to, as a speculation, that most people come to Python from a C/C++ background, which is why they liked the "f(a, b, c)"-style syntax as opposed to keywords (Smalltalk) or lots of parens (Lisp). Clearly, if most Python development had a long legacy of being oriented around that sort of open and dynamic constructivist principle, with the size of the Python community and its technical skill, we would probably already have a dynamic environment better than Squeak for all the things Squeak does. :-) So, as I reflect on this, the roots of not having Squeak on Python *already* may have more to do with Python community culture and history and mindset than Python limitations. One can of course hope that a successful PySqueak project in that direction will prove to be a "better mouse trap" and the Python community will flock to it, although that may not be realistic, speculating about the Prothon and PyCS sites being down. And as it pointed out here by Sjoerd Visscher: http://lambda-the-ultimate.org/classic/message11653.html "Flexibility is nice, but useless when the libraries are implemented in one programming style. That programming style will be the only natural style of the language." I want to underscore and emphasize this difference between these two philosophies of programming language design, because it seems that the directions proposed for mainstream Python, if anything: http://www.artima.com/weblogs/viewpost.jsp?thread=86641 have been in directly the opposite direction to Self, to a Python with static typing (including RPython). So, frankly, there very well simply be an insurmountable cultural barrier here. And there is nothing wrong with that -- static types are a valid way to generate code that efficiently uses memory and computation to solve a specific well defined purpose (like a well specified Python VM or any other well specified problem). However such a focus will remain at odds as far as I can see with the notion of a living and evolving population of objects system, which is at the core of a constructivist vision of education or programming. Hopefully a diverse Python community could productively use both paradigms in language variants, so an RPython for certain specific tasks, and a proto-Python for everything else (or at least, some forms of educational use)? Unfortunately, the more one sees a path diverging from Python as it is commonly defined (towards, say, a Prothon? direction) the quicker interest in a PySqueak will evaporate, if it has not already. :-) Because then it is just some quirky side variant. Still, there are niches in the Python community, like with Jython, where a slight change in direction may be more feasible, at the very least as a proving ground of sorts (Jython sometimes has its own identity crisis of whether to track CPython or strike out on its own, usually resolved in favor of tracking.). So, again, in thinking what Squeak on Python might mean, as opposed to aping Squeak, Python could build on its strength of using dictionaries internally and support a more prototype based environment like what Prothon or PyCS tried to move towards, making it more similar to Self than the derivative (in that sense) Squeak (which still uses classes). Coupled with a self-like GUI, I think that would better support constructivist educational needs. Of course, then one can ask, is this just reinventing the Self wheel and is that worth doing (to potentially gain Python community advantages, which might be questionable if the approach deviated in any significant way from mainstream Python)? It would seem clearer that the most leverage would be gained if Python (or Jython) as it is now (or with at best trivial changes) could be used to support Self-like features, and thus realize some of the constructivist ideals also embodied and further popularized in Squeak. Or, as Ian Bicking points out here in "Initial thoughts on Prothon": http://www.artima.com/forums/flat.jsp?forum=122&thread=41588 "[After mentioing Prothon syntax changes from Python...] I actually wonder to what degree prototypes could be implemented in Python with metaclasses. While some of the syntax would currently be crufy with Python (e.g., you'd have to create a function, then assign the function to an object), that's kind of a separate issue. The bpython metaclasses asic object model in Python feels like it should be replaceable, especially since we already have both new and old style classes. I think it's still a bit hard to think about these with the syntax getting in the way or otherwise being distracting, but feel like the potential is in there somewhere. If it could be hacked onto Python instead of developed entirely separately from Python, it could make Prothon much more interesting from the perspective of practical programming. Ultimately it would probably have to be like Stackless -- a patch against Python implementing some necessary changes to syntax. But that's a more viable development effort, and one that's more likely to percolate ideas into the main Python interpreter." I think that Ian's suggestion is the most sensible strategy for proceeding in that direction and continuing to maintain community interest in a PySqueak inclined towards Self-like prototypes and a related GUI for constructivist educational use (and other general use as well). And if metaclasses turn out to not be the best approach, still anything simple which preserves the Python syntax while giving the new features (say, having all prototypes inherit from a common class which overrides __getattr__ and __setattr__ to simply look into the instance to find data and behavior?) Ian, any recent thoughts on that? I would speculate that part of the general interest of the world in HyperCard was that it to some extent worked in a direct-manipulation protype-based way. Perhaps that similarity resonates with you as well? --Paul Fernhout
Paul D. Fernhout wrote:
Or, as Ian Bicking points out here in "Initial thoughts on Prothon": http://www.artima.com/forums/flat.jsp?forum=122&thread=41588 [with quoted text from Ian which had "python metaclasses\n" pasted in it by me by mistake.]
Ian- Sorry to mangle your eloquent writing by accidentally pasting "python metaclasses" in the middle of it (up till 4am writing inspired by seeing the Self video, sorry). --Paul Fernhout
On 5/5/06, Paul D. Fernhout <pdfernhout@kurtz-fernhout.com> wrote: <<SNIP>>
So, as I reflect on this, the roots of not having Squeak on Python *already* may have more to do with Python community culture and history and mindset than Python limitations.
Constraints provide form and allow function. I'm thankful that Python has the heritage Guido gave it: easy to learn if you're an adult in a late 1900s technical mindset, already working around computers, probably already knowing some C (but finding it slow going and unproductive). Python made it easier for astronomers, health professionals, statisticians, engineers to get work done in a computer language. That's a fine place to start, no apologies needed. I go back to my view that a powerful 3D graphics engine is something many languages would like to use and there's no reason the Python community should shoulder the entire developmental effort. Establish a common low level API that lots of languages might use. Such a 3D engine is for data visualization and simulations, but also for storyboarding and puppet shows -- not unlike Blender in some ways. We have cameras (points of view) and shapes with behaviors and attributes, coordinate systems. A single Python script could set up the scene and carry us through a story about a White Monkey. I'm reminded of a Pycon demo in DC, where we saw a simulator and language trainer used for English speakers going to Iraq. Unreal Tournament 3000 was the game engine. Python bindings had been established... Suddenly, we were in the outskirts of Baghdad, encountering Iraqis lolling around a coffee shop in a somewhat destroyed landscape. The dialog was basically: "Hello [polite introductions] who is your leader and how can we find him?"; detailed instructions provided [left, right, left, straight...]; following instructions; finding and greeting leader [politely]. That's if all went well. At every decision point, trainees might experience Game Over, perhaps because of intonation. A speech analyzer on the other end of the microphone contained algorithms looking for intelligible Arabic. Anything less was unacceptable. Kirby
Paul D. Fernhout wrote:
The key principles mentioned in movie for Self's design are based on ideas from the physical world, and are summarized as: * Directness (everything is potentially visible and touchable) * Uniformity (you can consistently change everything) * Modelessness (always running, so no editing/running distinction) Self also demonstrates multiple people doing activities together in one (2D) environment. [BTW Neometron's Learningworks was about 3D communities using Smalltalk and was deployed a decade ago, which shows some other roots of Squeak/Croquet roots here.]
This is a little of what I intend with HTConsole; the objects are (vaguely) live, and potentially shared. Though I've only implemented useful liveness for functions so far. It's at conflict with the idea of source code, and live objects really require an image, and I've never liked images. So I'm not sure if it's a useful path.
Now this certainly isn't a new idea, see for example these discussions: http://lists.canonical.org/pipermail/kragen-hacks/2000-September/000264.html http://www.thescripts.com/forum/thread30832.html http://www.prothon.org/ http://www.google.com/search?hl=en&q=Prothon or Prothon's apparent successor, PyCS: http://www.prothon.org/pipermail/prothon-user/2004-August/003269.html http://pycs.org [However, the Prothon and Pycs links seem to be down right now, I read them through the Google cache just now as this project is new to me, perhaps an indication the project is ended?]
As far as I know, the project fizzled soon after the move to CLR was announced.
I want to underscore and emphasize this difference between these two philosophies of programming language design, because it seems that the directions proposed for mainstream Python, if anything: http://www.artima.com/weblogs/viewpost.jsp?thread=86641 have been in directly the opposite direction to Self, to a Python with static typing (including RPython).
I wouldn't put too much weight on this, especially RPython. RPython isn't meant to compete with Python in that way. And it is Squeaky in its own way.
Or, as Ian Bicking points out here in "Initial thoughts on Prothon": http://www.artima.com/forums/flat.jsp?forum=122&thread=41588 "[After mentioing Prothon syntax changes from Python...] I actually wonder to what degree prototypes could be implemented in Python with metaclasses. While some of the syntax would currently be crufy with Python (e.g., you'd have to create a function, then assign the function to an object), that's kind of a separate issue. The basic object model in Python feels like it should be replaceable, especially since we already have both new and old style classes. I think it's still a bit hard to think about these with the syntax getting in the way or otherwise being distracting, but feel like the potential is in there somewhere. If it could be hacked onto Python instead of developed entirely separately from Python, it could make Prothon much more interesting from the perspective of practical programming. Ultimately it would probably have to be like Stackless -- a patch against Python implementing some necessary changes to syntax. But that's a more viable development effort, and one that's more likely to percolate ideas into the main Python interpreter."
Some things other people have done (like Logix: http://livelogix.net/logix/) makes me think that this could be done in CPython now, with just some fiddling in the compile process. I think the Python object model (which can't easily be changed) is already flexible enough for this. And ignoring some syntactic details you could do this in current CPython.
I think that Ian's suggestion is the most sensible strategy for proceeding in that direction and continuing to maintain community interest in a PySqueak inclined towards Self-like prototypes and a related GUI for constructivist educational use (and other general use as well). And if metaclasses turn out to not be the best approach, still anything simple which preserves the Python syntax while giving the new features (say, having all prototypes inherit from a common class which overrides __getattr__ and __setattr__ to simply look into the instance to find data and behavior?)
I think it could be easier than that. I think a lot of the more declarative programming I do really has prototypes underneath it. (Ironically in Javascript I never find a need for prototypes, except to simulate classes.) An example: class Person(SQLObject): name = StringCol(notNull=True) I think Person.name is really best represented as a prototype. I've played around with using this: class Person(SQLObject): class name(StringCol): notNull = True As an equivalent operation -- it starts looking better when you add methods to the object (e.g., on_delete, on_update, etc). Actually having Person.name be a class is annoying, but you can get class statements to generate instances with some appropriate hackery. At that point you start blurring the distinction between class and instance. So, in this model, clone is the basic operation. I.e.,: class obj(prototype): a = 1 Means something like: obj = prototype.__class__() obj.__dict__.update(prototype.__dict__) obj.a = 1 (The actual mechanics require some dumb fiddling with fake metaclasses.) This is different from the delegation of a typical prototype language (where the prototype isn't copied, but is called back to), but it's fast and uses normal Python. And I guess with clone in a prototype language you are basically doing this copy? I'm more comfortable with copies than chains of delegation. There was a proposal recently for a "make" syntax in Python (http://www.python.org/dev/peps/pep-0359/), so this would look like: make prototype obj: a = 1 And the exact semantics would be something like: obj = prototype('obj', (), {'a': 1}) Or my preference: obj = prototype.__make__('obj', {'a': 1}) Sadly Guido rejected it. I think it's a much better syntax with cleaner semantics for this kind of case, and I think this kind of case is actually extremely common. The whole Lisp S-Expr stuff is about this kind of declarative data structure, and adding that to Python just requires this little tweak of the language; it doesn't give Python macros, but the way Lisp uses data structures is for much more than just macros. Smalltalk doesn't have anything like Lisp either, but uses live objects to some similar effect, which is what binds it to the image.
Ian, any recent thoughts on that? I would speculate that part of the general interest of the world in HyperCard was that it to some extent worked in a direct-manipulation protype-based way. Perhaps that similarity resonates with you as well?
I'm not really sure how to fit HyperCard into this -- I don't have a strong feel for what it really means. I suppose one aspect is that the source code is not at the core; instead the card stack is (or whatever content-centric metaphor you use), and code is attached there. Ad hoc structured code doesn't work that well with a class... you get something like: class Foo: def bar(self)... foo = Foo() And the class/instantiation is just a kind of boilerplate. OTOH, modules are another model; if each content-context (aka card) was a module, you get some of the structure without the conceptual overhead of the class. -- Ian Bicking / ianb@colorstudy.com / http://blog.ianbicking.org
Ian Bicking wrote:
Paul D. Fernhout wrote: This is a little of what I intend with HTConsole; the objects are (vaguely) live, and potentially shared. Though I've only implemented useful liveness for functions so far. It's at conflict with the idea of source code, and live objects really require an image, and I've never liked images. So I'm not sure if it's a useful path.
Ian- Thank you for your detailed reply and I promise more thought on it, but on this one issue, to respond immediately, consider my earlier comments on images and the muse as to a potential "Pythonic" resolution. Basically, it is to think of the Python *image* as being defined by a Python *program* that builds an object world and is stored in a textual *.py file, and just looks and acts like regular Python source code -- ideally with comments preserved too. So, no pickled objects, not binary image files, just Python source code as readable text that rebuilds hierarchies and meshworks of objects. Or, as I realized shortly afterwards, perhaps an "python image" would be nto jsut one *.py file, but a whole bunch of *.py files in a directory, some of which might be more about defining classes (when you program that way) and some of which are about defining specific in-use instances. So, the Python image saving system might use the existing tagging of Python objects back to file names, for example (perhaps with some extensions needed). This way, a "save image" essentially would rewrite your directory of source. A bit scary perhaps, but with subversion or such not too risky perhaps. And not that different in some ways from what a tool like BoaConstructor does (or attempts to do). And other round trip GUI development systems (like Delphi) do this as well to varying degrees. The key difference of this approach might be that you were not usually editing the textual Python files in that directory by hand, but rather were using constructivist HyperCard and self-like tools to modifying a running Python program. Only then would you save the whole thing, to be able to reload next time you wanted to be in that specific environment defined by that directory. So, very similar to how things are done now in most respects, except the changes are made to the running Python object system, not the frozen textual representation of a pointing time of it. Naturally, you could still make changes by hand to the *.py files when the system was stopped, but if the GUI system was good enough, eventually few people might want to work that way for most things. It is also risky in another way, because loading a Python program could do anything to you system (unless we have Python VMs with Java like security limits on writing to files or sockets, where the VM enforces the restrictions internally from security flags). But then again, a Squeak image could do anything to your system when you load it in too. That security issue just needs to be dealt with at the VM level, or at the OS level, or through a system of trusted sites and so on (the "we're all consenting adults Python philosophy", etc.). So I think it could be managed in practice with risk equivalent to what we have now. --Paul Fernhout
Paul D. Fernhout wrote:
Ian Bicking wrote:
Paul D. Fernhout wrote: This is a little of what I intend with HTConsole; the objects are (vaguely) live, and potentially shared. Though I've only implemented useful liveness for functions so far. It's at conflict with the idea of source code, and live objects really require an image, and I've never liked images. So I'm not sure if it's a useful path.
Ian-
Thank you for your detailed reply and I promise more thought on it, but on this one issue, to respond immediately, consider my earlier comments on images and the muse as to a potential "Pythonic" resolution. Basically, it is to think of the Python *image* as being defined by a Python *program* that builds an object world and is stored in a textual *.py file, and just looks and acts like regular Python source code -- ideally with comments preserved too. So, no pickled objects, not binary image files, just Python source code as readable text that rebuilds hierarchies and meshworks of objects.
That's very hard to implement... For instance, imagine the user does this: x = [1, 2] Then they get a handle on that [1, 2] list (we'll call it OB1) and do "OB1[0] = 5". How do we represent that as source? Because they are acting on actual objects, not bindings, and OB1 doesn't have any name, nor will it exist at the same address in a second run of the program. In order to turn this into source, you'd have to keep track of how you can repeatably get to OB1 (I guess you'd see that it was named "x" through source introspection?), and then translate operations on that object into source that does operations on the path to that object. I think doing this generally becomes infeasible, though you can get close through successively more bizarre source code generation, until you end up with something that looks like a Python-source version of pickle, and even that won't work very well. This is all not to say that modifying Python source in a manner different than ASCII is infeasible. Only that "source" and "runtime" have to be kept separate to keep this process sane. Images -- which mingle the two -- have never felt sane to me; clever, but not manageable. Unfortunately we do not have very good abstractions in Python related to source, so they have to be invented from scratch. The AST might help, but higher-level abstractions are also called for. For instance, you might define a color interactively in some fashion, and the color gets serialized to: import color color.Color(r, b, c) That color object might be immutable (probably should be), but the *source* isn't immutable, and the source means something. How can we tell what it means? I can imagine a bit of interaction between the source and the runtime to do this. For instance, we might see that "color" is bound to a specific module, and "color.Color" to a specific object. We'll disallow certain tricks, like binding "color" dynamically, monkeypatching something into "color.Color", etc. Ultimately figuring out exactly what color.Color is isn't *easy*, but at least it is feasible. Using existing introspection we can figure out some parts, some of the sorts of things that IDEs with auto-completion figure out. They can figure out what the arguments to Color() are and the docstring. But, you can also imagine adding an editor or other things to that object; a richer form of __repr__, or a richer editable form than the ASCII source. Maybe there would be a flag on Color that says it can be called without side effect (meaning we can speculatively call it when inspecting source); and then the resulting object might have something that says it can be displayed in a certain way (with its real color), and has certain slots you can edit and then turn into a new Color invocation. This is where the 'make' syntax starts to come into play again, or more generally where declarative source comes into play. Take a typical non-declarative module, like optparse: parser = OptionParser() parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='Be verbose') There's nothing there that looks like a literal; the first call to OptionParser isn't very interesting, all the structure is in later parts. This on the other hand: make OptionParser parser: make Option verbose: """Be verbose""" aliases = ['-v'] action = 'store_true' dest='verbose' that is really a much more fully-formed piece of source that can be understood as a complete piece; you could add methods to OptionParser and Option to make this usefully editable. (Or use generic functions to avoid having to modify those objects to give them rich source representations) Sometimes declarative structures are in conflict with runtime dynamicism, but I don't think they have to be. The declarative form doesn't make the .add_option() method obsolete; it just offers a static form for the most common case where you statically construct the options. There also might be a hint of static typing here -- an OptionParser source editor might suggest an option to add another Option, and so it will be bound to the Option class specifically; but it's just important to leave in the same generality in the source editor as the original source has. That is, OptionParser should defer to Option for the 'verbose' key that's already set. This is all harder than what HTConsole is doing currently, mostly because Python source introspection is much poorer than Python object introspection.
It is also risky in another way, because loading a Python program could do anything to you system (unless we have Python VMs with Java like security limits on writing to files or sockets, where the VM enforces the restrictions internally from security flags).
Loading a Python program can already do anything, if you put the commands at the top-level (not in a function). So Python source really is focused on constructing objects -- even functions and classes are *built* by the source, not declared by it, and so circular imports and circular class references can be challenging as a result. -- Ian Bicking / ianb@colorstudy.com / http://blog.ianbicking.org
Ian Bicking wrote:
That's very hard to implement...
Probably true, at leas to do it in a human readable way.
For instance, imagine the user does this:
x = [1, 2]
Then they get a handle on that [1, 2] list (we'll call it OB1) and do "OB1[0] = 5". How do we represent that as source? Because they are acting on actual objects, not bindings, and OB1 doesn't have any name, nor will it exist at the same address in a second run of the program.
In order to turn this into source, you'd have to keep track of how you can repeatably get to OB1 (I guess you'd see that it was named "x" through source introspection?), and then translate operations on that object into source that does operations on the path to that object.
I think doing this generally becomes infeasible, though you can get close through successively more bizarre source code generation, until you end up with something that looks like a Python-source version of pickle, and even that won't work very well.
Just as reference to Squeak Smalltalk, it has a "changes" file that records each top level Transcript type interaction (equivalent to a Python shell in many ways) so that these can be replayed if you need to rebuilt an image from the last version you saved if there was a crash. That's not what I am proposing here. But to address your specif example, maybe it does point to a paradigm shift. Is this what you would do in an interactive session for your example?
x = [1, 2] OB1 = x OB1[0] = 5 del OB1 x [5, 2] OB1 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'OB1' is not defined
But, your "program" in terms of an image at that point no longer needs to assign "x = [1, 2]". What it needs to do, to get back to where it is, is assign "x = [5, 2]" if I understand your example correctly? Basically, there is a bound variable in some scope, and a value it is. As long as objects are reachable, we can write them out starting from roots of the system (class path, the stack, modules and globals, and so on). Anything we cant; reach should have been garbage collected anyway (though might not have with CPython's reference counting system). So, we recreate the binding and the object in as simple a way as possible, to make the objects that are part of the accessible system. (Might make sense to combine this work with a true generational mark-and-sweep multiple-spaces tenuring etc. garbage collection system for Python? :-) Maybe I don't understand the example? As I see it right now, while it might be interesting to have a change log about how objects got to be how they are now, what counts when saving an image to a Python *.py file for future reload would be just their current structure and the name bindings. As a note, remember that if there was a line of program code somewhere to create an object using a class, using "x = [1, 2" that line would be stored and regenerated as part of the class being stored. The fact that creating a new object using an "test = Spam()" would assign test.x = 1 is independent of the fact that in recreating test, we might do: test = object() test.__class__ = Spam # can this work? fails with a TypeError test.x = 5 or, with some sort of optimizing storage system geared to writing human-equivalent code rather than doing things one piece at a time: test = Spam() text.x = 5 Again, though maybe I am missing something obvious here? Now, there may be a lot of things to improve this. I like your "make" idea in that regard, and perhaps other ideas could help with that as well. You are absolutely right in Python syntax defining how to create a set of objects being more than declaring a specific structure. Forth has some of the same aspects (only worse!). It is a big headache in many ways to analyze Forth (as opposed to Smalltalk or Java or plain C) but Python now has the advantage that this approach of using a Python program to save the state of a sea of objects is more feasible. Obviously somewhere you are going to have some code with a lot of declaration of temporary variables and deleting them for complex manipulation on structures. Perhaps this could require sort of engine to optimize how to do that in a human readable way, sort of like an optimizing compiler? A big task, but potentially doable for people who like that sort of thing. --Paul Fernhout
Paul D. Fernhout wrote:
Ian Bicking wrote:
That's very hard to implement...
Probably true, at leas to do it in a human readable way.
For instance, imagine the user does this:
x = [1, 2]
Then they get a handle on that [1, 2] list (we'll call it OB1) and do "OB1[0] = 5". How do we represent that as source? Because they are acting on actual objects, not bindings, and OB1 doesn't have any name, nor will it exist at the same address in a second run of the program.
In order to turn this into source, you'd have to keep track of how you can repeatably get to OB1 (I guess you'd see that it was named "x" through source introspection?), and then translate operations on that object into source that does operations on the path to that object.
I think doing this generally becomes infeasible, though you can get close through successively more bizarre source code generation, until you end up with something that looks like a Python-source version of pickle, and even that won't work very well.
Just as reference to Squeak Smalltalk, it has a "changes" file that records each top level Transcript type interaction (equivalent to a Python shell in many ways) so that these can be replayed if you need to rebuilt an image from the last version you saved if there was a crash. That's not what I am proposing here.
But to address your specif example, maybe it does point to a paradigm shift. Is this what you would do in an interactive session for your example?
x = [1, 2] OB1 = x OB1[0] = 5 del OB1 x [5, 2] OB1 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'OB1' is not defined
Interactive sessions are source. The OB1 I referred to is the actual object in memory, which has no real name, though it can be referenced directly in memory. Maybe a better example is something in HTConsole right now. Any function you can get your hands on in HTConsole is editable, in-place. So you can do:
import random random.choice
And you'll get a form that shows the source to that function, and lets you edit it. That works fine in-memory. But saving that is hard. There's actually lots of ways to get access to a function. When you get random.choice, you are actually getting access to a method of a singleton stored in the random module. When you edit that, you are editing the method. How do you represent that edit as source? Do you see that the function is available as random.choice.im_func, or random.Random.choice? Or a property, which might be available as SomeClass.some_property.fget. There's lots of paths to functions, and the function alone doesn't necessary know how to get to it. And functions are relatively easy; other objects can be much harder... >>> items = range(5) The contents of items cannot be edited in place, because they are recreated dynamically. They can only be modified by later commands. Keeping a log of commands is possible. But you aren't editing live objects anymore, except perhaps an illusion of that. Instead you are generating saveable commands from a specific location, which are immediately executed to give you feedback but also saved so they can be replayed in the future. As I've been thinking about this, I think command generation is pretty much like direct source modification, but direct source modification also leads to nice source. So that's why I'm thinking that live objects isn't a good metaphor, after going down that path some. -- Ian Bicking / ianb@colorstudy.com / http://blog.ianbicking.org
Ian- Maybe we are still thinking about different things here. I'm going on the presumption that Python, like Smalltalk, have various "roots" which are things like (in Python) the loaded modules, the active threads and their stacks, and perhaps some other state. A Python world of objects is defined by these, since if any Python objects exist but are not reachable somehow, they can't effect the system. As I reflect on that, I can see how in theory outside forces that get a handle into object memory might make changes and such things might exist in other processes or threads, but that would just be a broken VM (or VM abstraction) to me more than a problem to solve. :-) So, going on this assumption that all Python objects of interest are reachable from these roots (even if some of the roots themselves might be only known to the VM, like the current thread?) then a recursive procedure can flow down through from these roots and write one big text file consisting of mainly: # big module import lots_of_stuff temp1 = object() temp1.__class__ = Point temp1.x = 10 temp1.y = 20 temp2 = object() temp2.__class__ = Point temp2.x = 40 temp2.y = 50 temp3 = object() temp3.__class__ = Rectangle temp3.origin = temp1 temp3.extent = temp2 modules.mymodule = object() modules.mymodule.__class__ = Module # or whatever? modules.mymodule.myrect = temp3 And so on. Now obviously this gets tedious, so an optimizing writer would instead produce: # module mymodule myrect = Rectangle(Point(10, 20), Point(40, 50)) But that's a matter of a smart writer, which I think is doable. Just think of it as like optimizing code (although maybe in reverse. :-) Now the problem you point out, that there may be several ways to reference an object is obviously a real one. But a smart writer should handle that (with a little handwaving by me. :-) But essentially, like with pickle, the writer would know it has to write the definition for a function out somewhere, and so it would pick a good spot, and then other references to that function could refer back to the first place it was defined. (Or it could be put in a temp variable which was later deleted after it was done being used.) Even if the writer picked a bad spot, the problem would only be that the code looked harder to read and understand, not that it would not recreate the same objects in memory that were written out (though perhaps at different locations; I'm presuming that would not matter -- if it did, then that might be a problem requiring VM changes or some fancy object creation order footwork, which would almost certainly mess up the code's readability). As I've just casually glanced at the CPython VM source code, and only just a bit more at the Jython code, I don't know enough off hand to see whether there might be other bigger problems. Anyway, I still don't see a show stopper (unless it is specific object ids or exact memory locations being important -- perhaps affecting dictionary hashes or some such thing?). Difficulties yes, maybe painful ones, but no showstopper so far as I can see to writing out running code as Python files (again, unless the above object ID issue). I can see that nicer source will likely result from tracking commands as the user defines them, as you suggest. But still, my focus isn't on readable source. That's more a nice to have feature from my set of priorities for GUI oriented construction and programming. What I am more interested in is, can the running program be changed, and can the changes be saved so they can be reloaded? So, our priorities may diverge there, but that's OK -- makes life interesting. :-) --Paul Fernhout Ian Bicking wrote:
Interactive sessions are source. The OB1 I referred to is the actual object in memory, which has no real name, though it can be referenced directly in memory.
Maybe a better example is something in HTConsole right now. Any function you can get your hands on in HTConsole is editable, in-place. So you can do:
import random random.choice
And you'll get a form that shows the source to that function, and lets you edit it. That works fine in-memory. But saving that is hard.
There's actually lots of ways to get access to a function. When you get random.choice, you are actually getting access to a method of a singleton stored in the random module. When you edit that, you are editing the method. How do you represent that edit as source? Do you see that the function is available as random.choice.im_func, or random.Random.choice? Or a property, which might be available as SomeClass.some_property.fget. There's lots of paths to functions, and the function alone doesn't necessary know how to get to it. And functions are relatively easy; other objects can be much harder...
items = range(5)
The contents of items cannot be edited in place, because they are recreated dynamically. They can only be modified by later commands.
Keeping a log of commands is possible. But you aren't editing live objects anymore, except perhaps an illusion of that. Instead you are generating saveable commands from a specific location, which are immediately executed to give you feedback but also saved so they can be replayed in the future.
As I've been thinking about this, I think command generation is pretty much like direct source modification, but direct source modification also leads to nice source. So that's why I'm thinking that live objects isn't a good metaphor, after going down that path some.
Ian Bicking wrote:
This is all not to say that modifying Python source in a manner different than ASCII is infeasible. Only that "source" and "runtime" have to be kept separate to keep this process sane. Images -- which mingle the two -- have never felt sane to me; clever, but not manageable.
Well, perhaps sanity is overrated. :-) And Smalltalk has been managing this for thirty years. :-) But it is true, it requires a bunch of special purpose tools. Changeset browsers. A change log. External source code control. Image cloners. and so on. Probably a Python that stored a running image as Python program files might need some specialized tools too. And I will go further and agree that I find mixing running application state (like what windows are opened or what the stack looks like) with application data (your email, your accounts receivable) is pretty scary too. But again, one needs some sort of tools to help you with that, and there is no reason some things can't be in the image and other things not in it. In a Smalltalk system, generally there is at least a file with library source code and a file with user additions to that as a current change log. Although in the case of Squeak, it can decompile methods (if it saves comments) even if you don't have the source code. One of the people on the project (Alan Kay or Dan Ingalls?) quipped something to the effect that a Squeak image was really a source code compression algorithm with a development environment thrown in for free. But, a Smalltalk image is generally saved as binary objects. But now that disk is cheap and cpu cycles plentiful, storing an image as python program files seems OK to me resource-wise.
Unfortunately we do not have very good abstractions in Python related to source, so they have to be invented from scratch. The AST might help, but higher-level abstractions are also called for. For instance, you might define a color interactively in some fashion, and the color gets serialized to:
import color color.Color(r, b, c)
That color object might be immutable (probably should be), but the *source* isn't immutable, and the source means something. How can we tell what it means?
I'm not sure anyone needs to ever tell what that means, except the Python parser when it reads in the code and runs it (in the context of other code it also loaded). Obviously when code is being written out which can reconstruct a current object tree, then the writing process needs to understand what it means by each piece of code, especially as it might refer to previously defined bits to shorten or make more readable the output code file. But since it is writing the code, there should not be any ambiguities of intent. The hardest issue perhaps becomes making the results look as close to human written Python as possible (say, using "class" to define a class instead of making a plain object and turning it into a class by setting fields).
I can imagine a bit of interaction between the source and the runtime to do this. For instance, we might see that "color" is bound to a specific module, and "color.Color" to a specific object. We'll disallow certain tricks, like binding "color" dynamically, monkeypatching something into "color.Color", etc. Ultimately figuring out exactly what color.Color is isn't *easy*, but at least it is feasible.
Using existing introspection we can figure out some parts, some of the sorts of things that IDEs with auto-completion figure out. They can figure out what the arguments to Color() are and the docstring.
Perhaps you are thinking about this from the point of view of something like PyDev parser, which reads Python code to syntax highlight it and perhaps provide code assistance. But, since the code has already been read, and we are manipulating the object tree directly, we know what everything means, because we just look at what the objects are directly by inspecting them. Granted, some of those objects represent code, and the code may be wrong or ambiguous or confusing from the users point of view, but that is ignorable by the code writer, which just writes out the snippet of code the way a user put it in.
But, you can also imagine adding an editor or other things to that object; a richer form of __repr__, or a richer editable form than the ASCII source. Maybe there would be a flag on Color that says it can be called without side effect (meaning we can speculatively call it when inspecting source); and then the resulting object might have something that says it can be displayed in a certain way (with its real color), and has certain slots you can edit and then turn into a new Color invocation.
I can see the value of all this from the GUI side, with various desired display options defined in the class or prototype. But again, Python object are (or should be :-) constructible from scratch without calling a class' init function. So, given that a writer can inspect an instance and just write out all the fields in its dictionary and their values it seems like we can write out any object (though perhaps it may need to recursively write out parts of embedded objects first, and then perhaps patch up circular references at the end).
This is where the 'make' syntax starts to come into play again, or more generally where declarative source comes into play. Take a typical non-declarative module, like optparse:
I like what you outline and I see how something like it could make the code the output writer creates be more legible.
This is all harder than what HTConsole is doing currently, mostly because Python source introspection is much poorer than Python object introspection.
Good point. And perhaps an area of Python that this project could work on? Can you elaborate on what parts of source introspection might need the most work and in what ways so that either programs or people can better inspect running Python programs?
Loading a Python program can already do anything, if you put the commands at the top-level (not in a function). So Python source really is focused on constructing objects -- even functions and classes are *built* by the source, not declared by it, and so circular imports and circular class references can be challenging as a result.
True. But, the writer should be able to figure out how to handle this, perhaps using some sort of recursive process with a patch list and a cleanup routine? --Paul Fernhout
Paul D. Fernhout wrote:
Ian Bicking wrote:
This is all not to say that modifying Python source in a manner different than ASCII is infeasible. Only that "source" and "runtime" have to be kept separate to keep this process sane. Images -- which mingle the two -- have never felt sane to me; clever, but not manageable.
Well, perhaps sanity is overrated. :-)
And Smalltalk has been managing this for thirty years. :-)
But it is true, it requires a bunch of special purpose tools. Changeset browsers. A change log. External source code control. Image cloners. and so on. Probably a Python that stored a running image as Python program files might need some specialized tools too.
Zope went down that path with through-the-web development with the ZODB as the image. I think everyone agrees that didn't work. Smalltalk has pursued that with more vigor, but I was never comfortable with the image. In some ways you can read my comments as me figuring out just why I dislike the image model. I think it's important to figure out, because the image model seems very compelling for an education environment.
Unfortunately we do not have very good abstractions in Python related to source, so they have to be invented from scratch. The AST might help, but higher-level abstractions are also called for. For instance, you might define a color interactively in some fashion, and the color gets serialized to:
import color color.Color(r, b, c)
That color object might be immutable (probably should be), but the *source* isn't immutable, and the source means something. How can we tell what it means?
I'm not sure anyone needs to ever tell what that means, except the Python parser when it reads in the code and runs it (in the context of other code it also loaded). Obviously when code is being written out which can reconstruct a current object tree, then the writing process needs to understand what it means by each piece of code, especially as it might refer to previously defined bits to shorten or make more readable the output code file. But since it is writing the code, there should not be any ambiguities of intent. The hardest issue perhaps becomes making the results look as close to human written Python as possible (say, using "class" to define a class instead of making a plain object and turning it into a class by setting fields).
With the color example, I'm really thinking about richer kinds of source literals. Maybe color.Color(1, 0, 0) is a better example. You can statically determine that is "red", and you could display it as red. And maybe it's not that much of a stretch to think about the program as a set of these objects. They aren't live objects, and they aren't entirely equivalent to the live objects. So a function definition is a source object; the function itself is the runtime object. We can treat them as equivalent, until we get to the point that we want to update things.
I can imagine a bit of interaction between the source and the runtime to do this. For instance, we might see that "color" is bound to a specific module, and "color.Color" to a specific object. We'll disallow certain tricks, like binding "color" dynamically, monkeypatching something into "color.Color", etc. Ultimately figuring out exactly what color.Color is isn't *easy*, but at least it is feasible.
Using existing introspection we can figure out some parts, some of the sorts of things that IDEs with auto-completion figure out. They can figure out what the arguments to Color() are and the docstring.
Perhaps you are thinking about this from the point of view of something like PyDev parser, which reads Python code to syntax highlight it and perhaps provide code assistance. But, since the code has already been read, and we are manipulating the object tree directly, we know what everything means, because we just look at what the objects are directly by inspecting them. Granted, some of those objects represent code, and the code may be wrong or ambiguous or confusing from the users point of view, but that is ignorable by the code writer, which just writes out the snippet of code the way a user put it in.
You can't know much about the objects inside a function, because they aren't bound to anything. You can know about objects defined at the module level. So even with runtime interaction you really can't do a whole lot better than PyDev.
But, you can also imagine adding an editor or other things to that object; a richer form of __repr__, or a richer editable form than the ASCII source. Maybe there would be a flag on Color that says it can be called without side effect (meaning we can speculatively call it when inspecting source); and then the resulting object might have something that says it can be displayed in a certain way (with its real color), and has certain slots you can edit and then turn into a new Color invocation.
I can see the value of all this from the GUI side, with various desired display options defined in the class or prototype. But again, Python object are (or should be :-) constructible from scratch without calling a class' init function.
I don't understand what you mean here? Most objects aren't constructible from scratch. Many objects are immutable, and can only be recreated, not modified.
So, given that a writer can inspect an instance and just write out all the fields in its dictionary and their values it seems like we can write out any object (though perhaps it may need to recursively write out parts of embedded objects first, and then perhaps patch up circular references at the end).
You mean pickle?
This is all harder than what HTConsole is doing currently, mostly because Python source introspection is much poorer than Python object introspection.
Good point. And perhaps an area of Python that this project could work on? Can you elaborate on what parts of source introspection might need the most work and in what ways so that either programs or people can better inspect running Python programs?
There's a bunch of pieces. There's work already done on many of them, and people (particularly in the IDE crowd) interested in refining that work. It's not something I've done a lot with, but here's some non-obvious pieces: * Better source code readers. Perhaps the work on a Python API to the AST would help here. Python 2.5 introduces a better internal model for source (the AST), but actual access to that isn't worked out yet. It's important to know how that abstract structure maps to actual source; right now I think it just has a line-level granularity. * The decompiling tools might be useful. There's already several; I know they drop things, but I don't know what. I think they do pretty well. If things like comments lived in the AST, that'd be great, but they don't currently. I think some variable names might be lost too, but I'm not sure. * Tools to figure out the relations between modules. Or maybe more generally, just a way of determining as much as possible statically. PyChecker and other tools do this some. It doesn't have to be perfect. * We can only really work with modules that can be safely imported and do not care about their environment. Lots of modules aren't like that, but I think those modules should just be given up on, or they need to be fixed. PyChecker and other projects have to worry about this a lot, and it's just not worth it -- environment-dependent modules just suck and need to be fixed. And maybe that's mostly it. The AST seems most important, though current tools actually do a lot of the necessary parts. So, for instance, I was stressed out about how a function gets edited in place. But maybe that doesn't need to be too hard. A function usually has a reference to where it comes from (the file and line number). You could then parse the code to figure out where the function ends. Then you rewrite the file with the new function definition, indented to the same level as the old function definition. Few other objects carry their source information. Modules do (just their __file__). Classes do not, nor do attributes or other assignments, and objects like lists often have no direct representation in the source. But classes are easy enough to figure out by just looking at the source, and at least they carry their original name. Well, at least this makes me feel a little better about what I want to do with HTConsole. I just have to avoid editing objects that are not obviously represented by source. I.e., you can edit/reassign variables and class attributes and functions, but not instances or lists. -- Ian Bicking | ianb@colorstudy.com | http://blog.ianbicking.org
Ian Bicking wrote:
Zope went down that path with through-the-web development with the ZODB as the image. I think everyone agrees that didn't work. Smalltalk has pursued that with more vigor, but I was never comfortable with the image. In some ways you can read my comments as me figuring out just why I dislike the image model.
Well, everyone? :-) Check out http://www.1010wins.com/ for what was, last I heard, a major Zope site. Still, a lot of peopel agree with you, so there are clearly issues here. For all I know, that group is unhappy? I myself like images you can build from text when you want to, but that has never been the focus of most Smalltalks (and isn't for Squeak so far). But, I think a truly Pythonic approach might insist on building objects from text, although it is true that we have pickle, which uses text, but not as a Python program.
I think it's important to figure out, because the image model seems very compelling for an education environment.
And John also made some of those points indirectly. I think from a "classroom" standpoint, teachers generally want to be in control of what the student sees, at least at first, and they also have short classes ("45 minutes) where they do not want to waste time on configuration. So, preparing an image for kids and seeing it pop up exactly like you made it, whatever the hardware (like Squeak can do) is a big win in a classroom setting. Of course, I'm not big on conventional classrooms :-), but I think images are desirable for independent or informal learning as well, which can happen in fits and starts, where you might do something intensely for days and then not revisit something until months later. And it would be nice if everything was just the way you left it. Also, as a developer of (hopefully open) educational simulations, I see the value in being able to deploy the simulation as an image which people could then modify to their own needs. So this is an area of overlap regardless of educational paradigm. Granted though, if you want to learn about the details of the machine you are on, or the notion of a software stack, like Kirby pointed to a week or two ago, in terms of getting into the specifics of GNU/Linux, then images are hiding stuff you might want to make visible. (Of course, you could open a terminal window from your running image. :-) I think a big issue with "images" is that they presume that you will put in the time to learn the tools assembled in the image (assuming the image is mainly about learning programming) rather than use some older ones you are comfortable with already. It is much easier to type text on the command line or use your favorite text editor than to learn an IDE (especially when you are learning a new language at the same time). But the value of learning an IDE can be immense in terms of letting you manage complexity far beyond what you can do with a text editor. It is funny how we can all accept that learning to play a musical instrument like the flute might take years of concentrated study to even get a good "tone" out of it, but we expect computers to be engaging on a minute by minute basis. Or, we just accept that learning to touch type takes days of practice. But computer IDEs? Do they really need to be like video games, engaging and addictive from the first quarter? John Holt wrote on this when he talked about "hard fun" (also mentioned here recently in another context I think) and how playing music, considering the suffering involved to get good at it, could hardly be described as "fun", but there certainly is something there for many people that keeps it engaging as a transcendental experience (as a sense of "flow"). Still, I think people here are right when they suggest typing at the command line an doing text processing produces feedback that keeps people engaged. The question is, can a graphical IDE do that, and I think the answer with Self might usually be "yes". I think the answer with "Squeak" is sometimes "no" though, but I have real experience with that, and have still never used Self. :-) Still, the "E" in CP4E is about "Everyone", so I can see the value in trying to make things accessible to broader audiences. My hope is that both the expert and the novice can be satisfied by a better system, and I think "Self" (or to a lesser extent "Squeak") helps show that way, prudcing a system I can love as an expert, but (taking their word for it, at least for Self :-) I think novices can warm to quickly as well. That gets back to the point I made on innovation -- satisfying both needs at once (e.g. expert and novice both love it) rather than compromising somewhere between them (e.g. not so hard for novices, but not so interesting for experts).
I can see the value of all this from the GUI side, with various desired display options defined in the class or prototype. But again, Python object are (or should be :-) constructible from scratch without calling a class' init function.
I don't understand what you mean here? Most objects aren't constructible from scratch. Many objects are immutable, and can only be recreated, not modified.
OK, good distinction. But the immutable ones are (always?) recreateable from code? And the rest can be made by creating a bare object "object()" and filling in its dictionary? I get errors when I try that though:
x = object() x <object object at 0xb7e503b8> class Foo: ... pass ... x.__class__ = Foo Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: __class__ must be set to new-style class, not 'classobj' object
I do not yet understand that error.
I guess, there might also be objects that wrap external resources like sometimes handles to bitmaps where those resources could not be accessed? In Smalltalk, this is handled in part by the distinction between "new" and "basicNew", e.g.: http://lists.squeakfoundation.org/pipermail/squeak-dev/2003-May/057790.html where "new" may call initialization methods with various side effects (often just "self basicNew initialize"), but "basicNew" just gives you the object of that class without much data in it (so you can call initialize yourself or do something else). The message "basicNew" is what is used typically by Binary Object Streaming systems when objects are recreated, then they fill in the fields from what is in the file.
So, given that a writer can inspect an instance and just write out all the fields in its dictionary and their values it seems like we can write out any object (though perhaps it may need to recursively write out parts of embedded objects first, and then perhaps patch up circular references at the end).
You mean pickle?
Well, a pickle-like system that writes out (hopefully readable) Python program code, yes.
There's a bunch of pieces.
Thanks for the elaboration on this. I need to think more on these points you raise. Still, it seems your focus here in what you outline as outstanding issues is more on reading and preserving and otherwise handling human written source code or the complexity of a large multi-module textually defined program (which is understandable for you are writing an IDE in a sense, where you want to give the user/developer help *before* the program is running) then reading and preserving objects (where source code may be just part of what is stored, and the user/developer is given help *while* the program is running). Perhaps the difference is that with an IDE you care more about how the objects are going to be created (given you are helping the user to write programs to do just that), whereas with saving and reloading an image I just care about anything that works and is marginally understandable for debugging the save/load process, expecting the coding to be done within the IDE on a fine grained basis (i.e. you are just look at one function at a time in a browser, like in Smalltalk, rather than scrolling through a file). Not to say producing clean readable Python programs as images isn't a good goal down the road for me, but clearly with an IDE it is your upfront goal. I think an innovative convergance comes if a new system with an image works well enough that people would choose using it to program over just editing long text files. --Paul Fernhout
On 5/7/06, Paul D. Fernhout <pdfernhout@kurtz-fernhout.com> wrote:
It is funny how we can all accept that learning to play a musical instrument like the flute might take years of concentrated study to even get a good "tone" out of it, but we expect computers to be engaging on a minute by minute basis. Or, we just accept that learning to touch type takes days of practice. But computer IDEs? Do they really need to be like video games, engaging and addictive from the first quarter?
This question defines a long-running theme here on edu-sig and I think you phrase it beautifully. I just don't see getting away from the intricate syntax of Latinate computer languages any time soon. They're intrinsic to the mix. However, we may have higher level simulation languages, where just dragging some ideogram or hieroglyph into a scene causes all these emergent behaviors, thanks to algorithms encoded using lexical tokens. Sometimes the knowledge domain we're hoping to model simply demands a high end animation engine running in the background. The users of this model don't participate in the source coding all that directly, and have a more graphical IDE within which to set up their energy, data, or currency flows. ESRI's use of embedded Python is a good example: to the end user, it's drag and drop, but under the hood, you're writing a Python script. The trend in modern OSs is to provide 3D geometry capability through a shared API in principle available to any hosted application. OpenGL, Direct3D, Xgl... I'm thinking that education applications in the "world" genre (like Squeak, like Uru, like ActiveWorlds) do well to take advantage of this existing machinery and not reinvent these wheels. But the glue layer, where the rubber meets the road, is still lexical in nature. I don't care how pretty your canvas is, or what kind of textured tube it came from (pneumatic, from Brazil?), it's still Python or C# or SmallTalk or some Latinate doohinky under the hood. Which isn't to say we couldn't also go with Tibetan, Cherokee, Arabic, Cyrillic or other unicode glyph language when committing lexical tokens to a parser. In principle, source code doesn't have to be Romanji. It just happens that's what most of it is so far, at this point in world history, and it's working out pretty well. And as I've said, it might be that the Kanji make more sense at a higher level anyway, when we start with the graphical simulations and need ways of counterbalancing various energies or whatever. Kirby
Paul D. Fernhout wrote:
Ian Bicking wrote:
Zope went down that path with through-the-web development with the ZODB as the image. I think everyone agrees that didn't work. Smalltalk has pursued that with more vigor, but I was never comfortable with the image. In some ways you can read my comments as me figuring out just why I dislike the image model.
Well, everyone? :-) Check out http://www.1010wins.com/ for what was, last I heard, a major Zope site.
Still, a lot of peopel agree with you, so there are clearly issues here. For all I know, that group is unhappy?
There's a general consensus in the Zope community that through-the-web editing and putting application code in the ZODB wasn't a good idea. So they've moved on to other means of development, mostly with files on the disk. At the same time, they've also moved away from the ideal of a web development environment accessible to beginning programmers, which was at least an early Zope goal. Though Plone is probably the exception to both of these. My impressions -- direct and indirect -- of what happens with typical projects stored in the ZODB, is that they just had all of the normal software engineering problems. But they weren't able to take advantage of all the work people were doing in other environments -- version control, Python's code layout (modules, packages, etc), and all the other tools developers use. And maybe that's the only problem (though I think there are others). On the measure of tools alone Smalltalk should be fine, as they've written their own full stack of tools, even though at the same time they pretty much isolate themselves from the mainstream of tools. I also hate the idea of extending Object, which is common in Smalltalk (and Ruby, with no image), and my distaste for that might tinge my feelings on images. Zope didn't add that, but it has its own ways of introducing that same kind of complexity that I dislike.
I think it's important to figure out, because the image model seems very compelling for an education environment.
And John also made some of those points indirectly. I think from a "classroom" standpoint, teachers generally want to be in control of what the student sees, at least at first, and they also have short classes ("45 minutes) where they do not want to waste time on configuration. So, preparing an image for kids and seeing it pop up exactly like you made it, whatever the hardware (like Squeak can do) is a big win in a classroom setting. Of course, I'm not big on conventional classrooms :-), but I think images are desirable for independent or informal learning as well, which can happen in fits and starts, where you might do something intensely for days and then not revisit something until months later. And it would be nice if everything was just the way you left it. Also, as a developer of (hopefully open) educational simulations, I see the value in being able to deploy the simulation as an image which people could then modify to their own needs. So this is an area of overlap regardless of educational paradigm.
I think there's a lot of practical problems with that. What happens when you find a bug in the image? How do you figure out where code came from? Is it system code, teacher code, peer code, your own code? If you edit system code, you've made your image incompatible with anyone else's image, and anyone who imports your change will probably be corrupting their image. And when you have this big ball of an image, the student has to choose which line of continuity they want -- do they keep their code, or throw it all away for the next class? I'm all for making tight bundles of code with controlled environments; that's important for everyone, not just classrooms. But there's another line of control, which is how you integrate new packages into that environment, and how you extract packages, and how you update packages. A classroom should be as ready to do all of these things as professional programmers, because they actually share all the same basic needs -- fixing bugs, applying workarounds, sharing code, making code reasonably portable (if only, for instance, to allow the teacher to run it in their own environment). Software engineering tools and methodology can be hard to adopt (even for professional programmers), but there's also a lot of good ideas in there that apply much more widely than just software. So I think it's worth keeping all those processes in mind.
John Holt wrote on this when he talked about "hard fun" (also mentioned here recently in another context I think) and how playing music, considering the suffering involved to get good at it, could hardly be described as "fun", but there certainly is something there for many people that keeps it engaging as a transcendental experience (as a sense of "flow"). Still, I think people here are right when they suggest typing at the command line an doing text processing produces feedback that keeps people engaged. The question is, can a graphical IDE do that, and I think the answer with Self might usually be "yes". I think the answer with "Squeak" is sometimes "no" though, but I have real experience with that, and have still never used Self. :-)
Programming is definitely hard fun, and like music some people are very much drawn to it and overcome all the hardness without being that bothered. That probably describes the majority of the open source community. But most people don't pursue music that seriously, even given the opportunity -- hard fun isn't very fun for most people and for any particular domain. I don't know if everyone has a hard fun that's right for them, or not. I wish everyone had something, but unfortunately I suspect that enjoying hard fun is itself something that not everyone is prepared to do.
OK, good distinction. But the immutable ones are (always?) recreateable from code? And the rest can be made by creating a bare object "object()" and filling in its dictionary?
I get errors when I try that though:
x = object() x <object object at 0xb7e503b8> class Foo: ... pass ... x.__class__ = Foo Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: __class__ must be set to new-style class, not 'classobj' object
I do not yet understand that error.
you can't turn a new-style class into an old-style class by reassigning __class__, but otherwise you can mess with that attribute pretty freely (or mess with __class__.__bases__). object() also happens to not accept attributes, though any subclass of object will. I'm not sure what the justification is for that.
I guess, there might also be objects that wrap external resources like sometimes handles to bitmaps where those resources could not be accessed?
Yes, that's very common. In general pickle-able objects can be recreated; you can do the same basic thing with the new module, like new.instance(Foo), and then update its __dict__ -- remembering that assigning to attributes might not work, since those attributes might be properties that expect the __init__ to have been called. The object may register itself or do other things in __init__ as well, so it's really hard to be sure you are doing the right thing. Really, it's all the same issues pickle has, and pickle also has some special methods so that most objects can be made pickleable. Some things like open file objects just can't be pickled at all, ever -- though often you can make a pickleable wrapper that fulfills some more abstract purpose, and use pickle's hooks to keep from trying to pickle that objects entire __dict__ (which probably contains the unpickleable file object).
There's a bunch of pieces.
Thanks for the elaboration on this. I need to think more on these points you raise.
Still, it seems your focus here in what you outline as outstanding issues is more on reading and preserving and otherwise handling human written source code or the complexity of a large multi-module textually defined program (which is understandable for you are writing an IDE in a sense, where you want to give the user/developer help *before* the program is running) then reading and preserving objects (where source code may be just part of what is stored, and the user/developer is given help *while* the program is running). Perhaps the difference is that with an IDE you care more about how the objects are going to be created (given you are helping the user to write programs to do just that), whereas with saving and reloading an image I just care about anything that works and is marginally understandable for debugging the save/load process, expecting the coding to be done within the IDE on a fine grained basis (i.e. you are just look at one function at a time in a browser, like in Smalltalk, rather than scrolling through a file). Not to say producing clean readable Python programs as images isn't a good goal down the road for me, but clearly with an IDE it is your upfront goal. I think an innovative convergance comes if a new system with an image works well enough that people would choose using it to program over just editing long text files.
In part I want to avoid a disconnect between the two styles of development. This was another problem with Zope -- people would start with the image/ZODB development, and then it was a really big leap to get to the file-based development. So there was two classes of Zope users, and they didn't always understand each other. Also, I don't want to have to recreate version control tools, or Python's module system, or the ability to share code freely, or any of that stuff. I think a good way to avoid any problems with these tools and processes is to just stick to conventional program representations. -- Ian Bicking | ianb@colorstudy.com | http://blog.ianbicking.org
Ian Bicking wrote:
Some things other people have done (like Logix: http://livelogix.net/logix/) makes me think that this could be done in CPython now, with just some fiddling in the compile process. I think the Python object model (which can't easily be changed) is already flexible enough for this.
And ignoring some syntactic details you could do this in current CPython.
Interesting concept, to put it mildly. And also interesting under "future work" they say: http://livelogix.net/logix/future-work.html "Although Python has provided a great launch pad for Logix, we may switch to another platform at some point. Efficiency and security are two areas that are somewhat lacking with a Python foundation. What are the alternatives? Maybe Mono, maybe Parrot, maybe C--, maybe LLVM..." I know that is one of the reasons I still think about doing something (at least proof of concept) in Jython -- because it can piggy back on the Java JVM security model, and I might also consider writing a low level routine for handling networked programming in Java (if speed was an issue, after doing it all first in Jython). [But, I've also been looking at working with the Idle codebase too, by building on its RPC functionality.] Still, the less I can mess with the syntax the better, I think. That's why I lean towards just having one "Prototype" class.
I think that Ian's suggestion is the most sensible strategy for proceeding in that direction and continuing to maintain community interest in a PySqueak inclined towards Self-like prototypes and a related GUI for constructivist educational use (and other general use as well). And if metaclasses turn out to not be the best approach, still anything simple which preserves the Python syntax while giving the new features (say, having all prototypes inherit from a common class which overrides __getattr__ and __setattr__ to simply look into the instance to find data and behavior?)
I think it could be easier than that. I think a lot of the more declarative programming I do really has prototypes underneath it. (Ironically in Javascript I never find a need for prototypes, except to simulate classes.)
Of course, you probably don't have a Self-like environment for Javascript. :-) Or is there one? I've never done much with JavaScript. I did once listed about 100 reasons to use VisualWorks Smalltalk over C+++ without having to talk specifically about object-oriented programming issues -- just focusing on the environment and the libraries and the VM and issues like garbage collection and integrated fine-grained version control. Anyway, I tried to follow your example, but I am still left confused as to how it is easier than creating GUI tools that manipulate all instances of just one Prototype class? Your example still seems focused on making classes. With one class, a few lines of code to search for values through parent slots and you're done? Essentially, for any prototype-ish work, one would just work with instances of this one class. Am I missing somethgin vital here? Are you thinking about one class per one prorotype object? Is that for performance reasons? Consider:
class Prototype: ... def __getattr__(self, name): ... print name ... x = Prototype() x.spam("hello") spam Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: 'NoneType' object is not callable
Of course it fails trying to call the None returned by default in __getattr___ because the code just prints the field name, but you see I could just have that __getattr__ method look into a field of the object and return the function, or if it was not there, then start looking into parent slots and recursing until it found it or gave up with a call to a "does not understand" function (itself hookable). I do think there might be issues to resolve in how instances of this class interact with other python classes. But I am not sure how complex those issues are, since everything to Python still looks just like a typical python class. All calls work the same way? Or do they? The issues might have more to do with the GUI and it being able to handle dealing with instances of other classes, but that could be done in the usual way such as with inspectors and such that Idle defines. A bit klunky, but probably serviceable as a start? Anyway, something to think more on. A proof-of-concept Prototype class and a GUI to manipulate it should not be that hard. (Famous last words. :-) Designing programming systems is so easy when you stop caring about performance (at least in the short term). :-) But the advice I was given long ago is, slightly updated, is: by the time you get it debugged and anyone has noticed it, computers will be fast enough to run it. :-) Anyway, are there compelling reasons why creating one class per prototype is useful or better? Or maybe I am just missing something here. Perhaps it is confusing in that we are talking about two issues. One is saving the state of a running Python program with arbitrary classes and instances which you have modified while it runs, and the other is an application building process centered around prototypes with classes being more a legacy thing.
I'm not really sure how to fit HyperCard into this -- I don't have a strong feel for what it really means. I suppose one aspect is that the source code is not at the core; instead the card stack is (or whatever content-centric metaphor you use), and code is attached there. Ad hoc structured code doesn't work that well with a class... you get something like:
class Foo: def bar(self)...
foo = Foo()
And the class/instantiation is just a kind of boilerplate. OTOH, modules are another model; if each content-context (aka card) was a module, you get some of the structure without the conceptual overhead of the class.
Yes, it would be that simple using classes I think. Or it could be done using prototypes: p = Prototype() p.border = "dotted" p.background_color = "red" p.foreground_color = Color.red # if allow mixing classes and prototypes p.parent = CARD_TEMPLATE_PREVIOUSLY_DEFINED But I kind of see HyperCard as more like just one set of widgets in a Morphic style interface. Have you had a chance to watch the Self video? Another source for it: http://www.smalltalk.org.br/movies/ If you haven't, I recommend it highly. --Paul Fernhout
Paul D. Fernhout wrote:
I think that Ian's suggestion is the most sensible strategy for proceeding in that direction and continuing to maintain community interest in a PySqueak inclined towards Self-like prototypes and a related GUI for constructivist educational use (and other general use as well). And if metaclasses turn out to not be the best approach, still anything simple which preserves the Python syntax while giving the new features (say, having all prototypes inherit from a common class which overrides __getattr__ and __setattr__ to simply look into the instance to find data and behavior?) I think it could be easier than that. I think a lot of the more declarative programming I do really has prototypes underneath it. (Ironically in Javascript I never find a need for prototypes, except to simulate classes.)
Of course, you probably don't have a Self-like environment for Javascript. :-) Or is there one? I've never done much with JavaScript.
There are some more sophisticated development environments for Javascript, but I've never used any of them, only source editing.
Anyway, I tried to follow your example, but I am still left confused as to how it is easier than creating GUI tools that manipulate all instances of just one Prototype class? Your example still seems focused on making classes. With one class, a few lines of code to search for values through parent slots and you're done? Essentially, for any prototype-ish work, one would just work with instances of this one class. Am I missing somethgin vital here? Are you thinking about one class per one prorotype object? Is that for performance reasons?
I think my example confused you, since "class" was a misnomer. That is, you *can* subclass non-classes using the class statement. I was using that to mean "clone and update". I.e.: class foo(bar): def spam(self): print 'hi' That means "clone bar, and set foo.spam to this function". The use of the class statement is a little confusing, which is why I would like a make statement. To get to mechanics, basically this class statement means: foo = bar.__metaclass__('foo', (bar,), {'spam': spam_func}) So with the right fiddling you can "subclass" an instance. Anyway, I like the syntax, and find it more comfortable than direct assignment (especially for methods). For direct assignment, you might find the "magic_set" decorator I wrote useful: http://svn.colorstudy.com/home/ianb/ruby_blocks.py (toward the bottom) It lets you do: @magic_set(foo) def spam(self): print 'hi' which handles the closure to add instance methods directly to instances (which doesn't work if done just through assignment), among other assignments. It uses the name of the first argument (self, cls, or anything else) to determine what kind of function you are trying to create. -- Ian Bicking | ianb@colorstudy.com | http://blog.ianbicking.org
Ian Bicking wrote:
Paul D. Fernhout wrote: I think my example confused you, since "class" was a misnomer. That is, you *can* subclass non-classes using the class statement. I was using that to mean "clone and update".
OK, that makes more sense to me, and now I can see the cleverness of it. Took a while; thanks for being patient with me. :-) And thanks for the pointer to your other stuff and magic set. I'm a big fan of those collection block operations BTW (although they came from Smalltalk before Ruby :-). Is that stuff under the Python license, or something else? I might want to put it into PataPata, and I hope to use the Python/Jython license for core classes, and then the GPL for any stand-alone applications. --Paul Fernhout
Ian- Thanks again for this pointer. I've added two more proof of concept files to PataPata. The second one demonstrates inheritance with a parent and two children, as well as graphically displaying the morphs. See: http://svn.sourceforge.net/viewcvs.cgi/patapata/PataPata/proof_of_concept_00... I started trying to do it in OpenGL (which I have had working before) and got some errors so punted and chose wx and 2D for that. I don't like making initial proof of concepts that rely on too many dependencies. OpenGL remains a real possibility down the road, but this one problem just shows how installation issues can bite you (in this case, I had commented out the driver for it in my xorg.conf file when I was having other problems related to one of my two ATI cards having problems driving its secondary display head, and didn't care to restart X in the middle of my development to try fixing that.) The third P.O.C. demonstrates the problem your solution addresses. http://svn.sourceforge.net/viewcvs.cgi/patapata/PataPata/proof_of_concept_00... I found two other variants of solutions on the web (differences include are who does the wrapping etc.), and I need to decide how best to proceed on that. Links to these three options including yours: http://svn.colorstudy.com/home/ianb/ruby_blocks.py http://zephyrfalcon.org/weblog/arch_d7_2002_11_16.html http://lists.canonical.org/pipermail/kragen-hacks/2000-September/000264.html [The latter two are more specific to Prototype inheritance in Python.] Going to be traveling much of next week, so probably not much more activity on this for a while. Frustrating to me as I think this is just picking up some speed. A little OpenGL, a few Self-like GUI widgets for inspecting values like in the movie of just rectangles and lines and text, using a wrapper approach for inserting methods into prototypes, adding a remote debugging process perhaps derived from Idle, making some program writing code derived from pickle perhaps, dropping in some bitmap and icon content from our garden simulator, and the result might be a nice proof of concept constructivist educationally-oriented system in a week's work or so. Obviously not everything, and with very rough edges, but something that could show the basic ideas. Sigh. With distractions, and part-time, be nice to get all that together by the end of June, if that, realistically. No promises though. Thanks again for your feedback. --Paul Fernhout Ian Bicking wrote:
For direct assignment, you might find the "magic_set" decorator I wrote useful: http://svn.colorstudy.com/home/ianb/ruby_blocks.py (toward the bottom)
It lets you do:
@magic_set(foo) def spam(self): print 'hi'
which handles the closure to add instance methods directly to instances (which doesn't work if done just through assignment), among other assignments. It uses the name of the first argument (self, cls, or anything else) to determine what kind of function you are trying to create.
On 5/5/06, Paul D. Fernhout <pdfernhout@kurtz-fernhout.com> wrote:
Of course, you probably don't have a Self-like environment for Javascript. :-) Or is there one? I've never done much with JavaScript.
Javascript is a prototype-based language deriving directly from Self. Emulating inheritance-type OO is possible, but kind of a hack, mainly done for folks who are new to prototype-based languages.
Designing programming systems is so easy when you stop caring about performance (at least in the short term). :-) But the advice I was given long ago is, slightly updated, is: by the time you get it debugged and anyone has noticed it, computers will be fast enough to run it. :-)
That hasn't worked well for Squeak, in my experience. Every time I get a faster computer I try it out, and it's still too slow to use. Also, I think one big reason people are leery of putting all their methods and changes on the root object (or even allowing key objects to be modified) is that introspecting objects to find out what messages they can be expected to respond to is a crucial learning path. That is why Python doesn't let you extend object, string, or int, for example, in order to keep the system predictable and reduce the impact of running dir(string) to something a person can understand. If you did that on a javax.swing.JButton (and Jython implemented dir() so that it showed all available methods, which it doesn't) then you would be overwhelmed with hundreds of options, many of which are implementation details of AWT or Swing, or other widgets, and very few of which are useful to the common expectations and needs of using a simple button widget. So, performance is part of it, but comprehensibility (is that a word?) is the more important part. And yes, I realize that prototype-based languages don't have to be like that, but when you can add methods to root objects, the tendency is to do so, and it can make things very hard to maintain. I know Ruby allows this, but haven't had enough experience with Ruby to know if it's a problem. In Javascript, it definitely is a problem, with libraries like Prototype encouraging "let's stick every method on the root object" mentality and cluttering the namespace. In Objective-C there is a nice middle ground, where you can have a new object layer that adds methods to existing classes when it is imported. So you can add things where they feel *right,* but you're not encouraged to do so willy-nilly. That works for me.
I'm not really sure how to fit HyperCard into this -- I don't have a strong feel for what it really means. I suppose one aspect is that the source code is not at the core; instead the card stack is (or whatever content-centric metaphor you use), and code is attached there. Ad hoc structured code doesn't work that well with a class... you get something like:
I think Hypercard fits into the conversation because (like in Morphic (Self or Squeak)), you can access the script behind every widget at runtime, as if each widget has its own "view source" window, except editable. Which would be cool for Python to adopt, I think.
But I kind of see HyperCard as more like just one set of widgets in a Morphic style interface.
Yes, very similar ideas. Also the game The Incredible Machine, with it's halo of options around each object (though not editable). --Dethe
participants (4)
-
Dethe Elza -
Ian Bicking -
kirby urner -
Paul D. Fernhout