Failed Experiments.

a Mr. Fleming wishes to study bugs in smelly cheese; a Polish woman wishes to sift through tons of Central African ore to find minute quantities of a substance she says will glow in the dark; a Mr. Kepler wants to hear the songs the planets sing.

Friday, September 25, 2009

De Icaza "civil and polite"?

In response to RMS: De Icaza Traitor to Free Software Community:
Kudos to De Icaza for remaining civil and polite despite such a low blow from RMS.
While I certainly think that RMS went a little too far in his comments, it's unfair to overlook the highly personal attack with which De Icaza opens his post:
I want to say that God loves all creatures. From the formidable elephant to the tiniest ant. And that includes Richard Stallman.
I'm not familiar with De Icaza's religion, but it's common knowledge in the Free Software community that RMS is an atheist. Many atheists, especially in the United States, find proselytizing of the "God loves you" sort to be highly insulting. In this situation, especially, it feels like De Icaza is trying to evoke the popular image among the religious of the Bitter Atheist, the sort who really believes in god but hates him so pretends he doesn't exist, vs. the Friendly, Reasonable Theist, who is just trying to get the Bitter Atheist to see the truth.

Furthermore, the widely quoted comments from RMS were made in the setting of a spoken forum. Once the words are out of your mouth, there's no Backspace key to take them back, and we already know that RMS can get quite emotional about issues affecting the Free Software community.

But De Icaza had a chance to consider his response carefully (he was, after all, writing a blog entry), and he opted to publicly, albeit indirectly and with some subtlety, attack RMS's lack of religion. Being polite doesn't make you right, but even if it did, who was really being less "constructive and civil" here?

Thursday, September 17, 2009

I wanna make an IDE for my language‽

"Why not write an Eclipse plugin instead? That way you can leverage an existing, well-known IDE while introducing features needed for your language."

"Good idea!"

You scrabble up the face of Eclipse Mountain knowing that when you reach the top you will be much higher than you could have gotten in the same amount of time without it.

You finally reach the top and oh my god it's one of those funnel water slides you're going around and around spiraling down
oof fell through the hole in the center, falling falling what's that down there is that red—

Wednesday, August 5, 2009

Recursive directory creation API

Right way: succeed if directory exists. Examples: GNU Coreutils, Common Lisp.

Wrong way: fail if directory exists. Example: Python.

I should think that if a recursive directory creator thinks that failure in the case of existence of the final directory in the series is an option, it should also fail if the parent exists, and so on, proceeding to fail in the case of the existence of ‘/’, when all else doesn't fail.

Tuesday, July 7, 2009

PLIP #42: Remove Zope dependency

Manage requests, persistence, and scaling directly within Plone, eliminating a system that is confusing to first-time users

Proposed by

Stephen Compall

Seconded by
None as yet


Proposal type
Core

State
being-discussed

Motivation

A frequent complaint of newcomers to Plone deployment and development is the use of Zope to provide features such as HTTP request handling, object persistence, transactional safety, and load sharing. It is difficult enough to learn one large system at a time; by requiring the learning of two large systems, Plone effectively makes things twice as hard for newcomers. It is also inconvenient for users to have to install Python 2.4 when they frequently already have 2.5 or 2.6 installed.

Plone is now large enough that it should be able to stand on its own, without Zope.

Assumptions

This proposal suggests the removal of Zope only. Others have suggested, variously:
  • Removal of Python dependency;
  • Allow extension via CLR;
  • Allow extension via PHP;
  • Portability to abacuses.
This proposal only concerns Zope.

Proposal & Implementation

By removing all imports of Zope Python packages and fixing any problems indicated by unit and user testing, and possibly replacing remaining functionality that Zope might provide with Plone-specific packages, we can remove Zope entirely from future buildouts and installations. A wider variety of Python versions may also be used.

Deliverables

Included are:
  • New versions of all Plone packages with Zope dependencies removed.
  • A new paster to replace ZopeSkel, with a buildout skeleton that does not include the zope2instance recipe.
  • New Plone packages as needed to replace remaining Zope functionality that might be convenient.
  • Replacement of all mention of "products", a decidedly Zope-centric term, with "goodies".

Risks

Users of add-on packages that have not been ported to become goodie-compliant by removing their Zope dependencies may have trouble working with a Plone implementing this proposal, and make take a little time to port.

To mitigate this inconvenience, we might offer a compatibility package, Nine, which provides features of Zope 2 and 3 for Plone 4 add-on goodies that need them.

Participants

The maintainers of the respective packages in Plone Core are responsible for removing the Zope dependencies in their packages. Zope dependencies will be removed from Collective and third-party add-ons as a matter of course.

Progress

Many existing utilities do not call Zope methods and functions at all, and thus may be considered already ported. Many references to Zope do remain in Plone, however.

Sunday, April 26, 2009

svn-upgrade on a Bazaar Loom

I maintain modifications of Trac, a Subversion project, as a Bazaar Loom. The bottom thread mirrors the 0.11-stable branch using bzr-svn, with features built purely in Bazaar revisions on top.

When I upgraded bzr-svn once, the Subversion storage format changed incompatibly, meaning I could no longer track Subversion without rebasing my Loom. Here is how I did it:

  1. Make sure the loom is fully forward-merged so that every thread's head is a parent of the top thread. Revisions you miss won't be converted correctly.

  2. Make sure you are on the top thread. svn-upgrade only converts the HEAD and its parents, so if you aren't, the conversion will miss higher threads.

  3. bzr record the loom if there are changes.

  4. Copy or branch the loom to a backup, in case you mess up.

  5. In the Loom directory, run bzr svn-upgrade --idmap-file=../myproj.idmap http://svn.edgewall.org/repos/trac/. The idmap is for your later reference so you can fix up the loom head references, and you can choose where you want to save it. Unless you have parent_location set in .bzr/branch/branch.conf to the actual Subversion branch, you must specify the URL to the repository. This should be some parent directory of the Subversion branch URL, and is usually the one that contains the branches, tags, and trunk subdirectories.

  6. That should spend a bunch of time accessing the network.

  7. Now you need to fix up the .bzr/branch/last-loom file. This is a plain text file, and opening it you'll immediately see why we saved an idmap.

    The first line is very obviously the Loom file format, which should be "Loom current 1". The second points to the recorded loom, which you can leave alone, because we'll just be fixing where the threads point.

    Each line following is info about a thread, bottom-thread-first (completely counterintuitively, but there you are). The last revision at time of loom-record comes before " : ", the current revision after, and of course the thread name.

    Note that the top (bottom-of-file) thread is a little different. Unlike every other line, the revision ID after " : " is slightly different from the one before. That's because svn-upgrade successfully communicated to loom that the HEAD revision is now the rebased one. It should give you a hint as to how to fix the others.


  8. Looking at the idmap file, you'll quickly see a pattern. Revisions that come from Subversion have their prefixes changed, from "svn-v3-trunk0:" to "svn-v4:", and "%2F" changed to "/" in my case. Non-Subversion revisions simply have "-svn4-upgrade" appended. The whole point is for the svn-upgrade to be trivially repeatable by multiple branch owners, so either memorize the pattern, or keep this file handy.

  9. Back in last-loom, change the revids after the " : " according to the map. If your loom is like mine, with a Subversion revision as the basis and the rest in pure Bazaar, this means changing "svn-v3-trunk0" to "svn-v4" and any instances of "%2F" to "/" for the bottom thread (first in last-loom file), and adding the "-svn4-upgrade" to the rest. Do not change the revid before the " : ". Do not change line 2. Do not change the last line, because it was fixed already.

  10. Move down through the loom looking for failures of bzr log --show-ids -l1, which should print out the revids you just wrote into the loom. If there are failures, you probably messed up a replacement revision; check your idmap file again.

  11. Once you've verified everything, move back to the top thread with bzr switch and record the loom.


What about inter-Loom activity?


The whole point of repeatability in svn-upgrade is to allow independent upgrades to be shared between. So if you have people with divergent looms, simply using svn-upgrade on all of them will make the threads compatible.

This says nothing about whole-loom merging, though. Loom-recording, even between two equal looms, will create divergent loom records. I've never tried loom merging, so you may have to rough it by doing thread-only merging until everyone is synced up again.

Thursday, December 4, 2008

OLPC and the dying belief

I quoted OLPC earlier

Our commitment to software freedom gives children the opportunity to use their laptops on their own terms. While we do not expect every child to become a programmer, we do not want any ceiling imposed on those children who choose to modify their machines. We are using open-document formats for much the same reason: transparency is empowering. The children—and their teachers—will have the freedom to reshape, reinvent, and reapply their software, hardware, and content.

Short, straightforward, and powerful language.

Compare to the current page contents. Here's a sample:

Thus OLPC puts an emphasis on software tools for exploring and expressing, rather than instruction. Love is a better master than duty. Using the laptop as the agency for engaging children in constructing knowledge based upon their personal interests and providing them tools for sharing and critiquing these constructions will lead them to become learners and teachers.

As a matter of practicality and given the necessity to enhance performance and reliability while containing costs, XO is not burdened by the bloat of excess code, the “featureitis” that is responsible for much of the clumsiness, unreliability, and expense of many modern laptops.

A truly inspiring stand for constructivist teaching. Unfortunately, as far as I can tell, little else is being said.

Sunday, November 30, 2008

Understanding cl-cont semantics without thinking about CPS

Understanding the way that cl-cont transforms forms is one way to understand the sometimes counterintuitive behavior of that code. However, the difficulty of dissecting the meaning of code written in continuation-passing style is one of the major reasons that we use a CPS transformer at all instead of writing it out manually.

As a cl-cont user, you may find it easier to understand a behavior model that removes consideration of CPS entirely. After all, CPS is only the implementation method; the goal is to think of the code as "continuable".

Understanding first

To follow along with the behavior model of cl-cont, you should be familiar with dynamic context (such as that established by unwind-protect), lexical context, and the behavior of full continuations such as you would find in Scheme.

Understanding "ordinary" continuations is especially important, because cl-cont's behavior is more complex than full continuations, and you will be lost without preexisting knowledge of what they are.

Defining some terms

First, a continuation, unless otherwise qualified, is always a first-class continuation—a function that, when called, returns its arguments as values to the place where the call/cc call that created the continuation was called. This is just to be clear that I hardly ever mean something abstract when I say "continuation".

The macro with-call/cc introduces a lexical continuable context for the forms lexically contained within it. We will refer to this so often that I will call it LCC henceforth to avoid confusion. For all current purposes, it only matters whether code exists within any LCC, so you can think of "LCC-ness" as a flag on all code. We also say that any code not within an LCC is in an LNCC (lexical non-continuable context). We will define more rules for determining whether code is in an LCC later.

Entering an LCC at the beginning creates a dynamic continuable context or DCC. Understanding DCC behavior is the key to understanding cl-cont. Unlike LCC, you can have multiple distinct DCCs active at any time, possibly even sharing code, just as one function calling mapcar doesn't preclude you from doing so. All code not executing within a DCC is executing within a DNCC (dynamic non-continuable context).

Lexical continuable contexts

As I have said, with-call/cc introduces an LCC. This is an implicit progn. A few convenient macros, such as lambda/cc and defun/cc, wrap their non-cc counterparts with with-call/cc. Within this form, an LNCC can be inserted with the macro without-call/cc. The pseudo-function call/cc also implies a without-call/cc around its sole argument. Within any LNCC, including the implicit one around every program, further LCCs may be introduced with with-call/cc.

There are some cases where you might think that an LCC is there or doesn't matter, where it actually does. In this code:

(defun x ()
(with-call/cc
1 2))

The LCC only contains the 1 and 2 forms, not the function entry and function exit. Therefore, calling X from any code will result in first executing code in an LNCC, then some LCC code, then some LNCC code. The importance of this distinction will become clear once we start discussing DCCs. To solve this, move the with-call/cc to outside the defun, or use the equivalent defun/cc convenience macro.

A similar situation holds for this code:

(lambda () (with-call/cc 3 4))

Calling this function is the same as calling X above, and the same solutions apply.

One final area of confusion may be:

(with-call/cc
(without-call/cc
(with-call/cc 5 6)))

This is not a smooth contour of an LCC; the without-call/cc always creates an LNCC, even if a new one is created immediately therein.

Basic DCC execution

Entering any LCC from a DNCC, by either calling a function created or defined in an LCC or simply evaluating a with-call/cc form, creates a DCC. Upon creation, beyond the usual frame info, one property is captured and saved permanently for that DCC: the exit point. Consider this code:

(defun/cc bob ()
(cons 4 2)
:bob)
(defun/cc slack ()
(bob)
:slack)
(defun moo ()
(bob)
(slack))

Calling bob in moo creates a DCC whose exit point is the code that returns the keyword :bob. Calling slack in moo creates another one whose exit point is returning :slack. The call to bob within the call to slack does not create a new DCC, or even alter the existing one! Whether calling bob creates a new DCC depends on the nature of the calling code.

While in a DCC, calling a function defined in an LNCC suspends that DCC. That happens in bob above when calling cons, which, like all CL standard functions, is defined in some LNCC. Whether the compiler optimizes away the cons is irrelevant to our semantic model. When the function returns, the same DCC is resumed; as such, the cons above destroys neither DCC's exit point information.

The function rule sounds like a special case, but it is really just a subcase of the continuation case. Invoking a continuation enters the DCC in which it was created. In cl-cont, calling LNCC functions is handled by grabbing the continuationand invoking it with the function's result after it finishes.

After exiting a DCC, the only way to reenter it is to invoke one of its continuations. Calling a function defined therein only creates a new DCC delimited by that function's body.

Finally, a point about call/cc that will surprise you if you are used to Scheme: Invoking call/cc exits the DCC, returning the values returned by the function you gave it. You are, of course, free to simulate Scheme behavior by invoking the continuation from right within that function.

Strange exit point behavior

This may seem a little surprising:

(defvar *cc*)
(defun/cc foo ()
(let/cc cc (setf *cc* cc) 'saved)
'foo)

(defun/cc bar ()
(foo)
'bar)

(progn
(foo) ; ⇒SAVED
(funcall *cc*) ; ⇒FOO
(bar) ; ⇒SAVED
(funcall *cc*) ; ⇒BAR
)

The important thing to remember is that it doesn't matter at all what context you invoke a continuation in from a DNCC; you always get the exit point of the continuation's DCC.

Nested DCCs

Certain code looks like it should really be resuming something that it actually can't, and it has to do with nesting DCCs. Here's what I mean:

(let (keep-going list)
(progn (with-call/cc
(setf list (mapcar (lambda (n)
(let/cc k (setf keep-going k) 42))
(list 1 2 3 4 5))))
(dotimes (n 5)
(funcall keep-going (+ n 6)))
list))

If mapcar was defined in an LCC, you would get the result you expect, (6 7 8 9 10). What you expect is that for each element in (1 2 3 4 5), let/cc saves and suspends the continuation.. The first time, the dotimes would be entered, and the next four times it would do another iteration, each time invoking the continuation saved above. In the first case, the 42 would be delivered to the first progn location (and discarded), and the further four times to the implicit progn in dotimes (and discarded).

Instead, you get the result (42 42 42 42 42). Why?

The mapcar throws a kink in. Consider that when mapcar is running, the enclosing DCC is suspended, because mapcar is defined in an LNCC and therefore creates a DNCC by being called. Now, when it calls the LCC-defined function passed to it, a new DCC is created each time. In each of these a continuation is saved and 42 is returned, creating the contents of the list, but only the last saved continuation is ever seen by the dotimes. When mapcar finishes, it resumes the enclosing continuation and it finishes execution before the dotimes can even start.

Now that dotimes has started, it is resuming the DCC solely delimited by the function passed to mapcar. As you can see, the exit point of that function is to return whatever was passed to the continuation. Accordingly, if you wrap the funcall above with a print, you'll see it print 6, 7, 8, 9, and 10, in order.