<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1184549185438247550</id><updated>2011-07-07T16:45:00.395-07:00</updated><category term='bazaar'/><category term='weblocks'/><category term='continuations'/><category term='plone'/><category term='lisp'/><category term='mercurial'/><category term='python'/><category term='vcs'/><category term='ide'/><category term='zope'/><category term='dependencies'/><title type='text'>Failed Experiments.</title><subtitle type='html'>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.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://failex.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-2466833476406402945</id><published>2009-09-25T15:14:00.000-07:00</published><updated>2009-09-25T15:32:23.109-07:00</updated><title type='text'>De Icaza "civil and polite"?</title><content type='html'>In response to &lt;a href="http://www.osnews.com/story/22225/RMS_De_Icaza_Traitor_to_Free_Software_Community"&gt;RMS: De Icaza Traitor to Free Software Community&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;Kudos to De Icaza for remaining civil and polite despite such a low blow from RMS.&lt;/blockquote&gt;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 &lt;a href="http://tirania.org/blog/archive/2009/Sep-23.html"&gt;his post&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;I want to say that God loves all creatures. From the formidable elephant to the tiniest ant. And that includes Richard Stallman.&lt;/blockquote&gt;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 &lt;span style="font-style: italic;"&gt;really&lt;/span&gt; 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 &lt;span style="font-style: italic;"&gt;the truth&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-2466833476406402945?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/2466833476406402945/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=2466833476406402945' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/2466833476406402945'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/2466833476406402945'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2009/09/de-icaza-civil-and-polite.html' title='De Icaza &quot;civil and polite&quot;?'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-7553251676323533984</id><published>2009-09-17T12:56:00.000-07:00</published><updated>2009-09-17T13:04:12.959-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ide'/><title type='text'>I wanna make an IDE for my language‽</title><content type='html'>"Why not write an Eclipse plugin instead? That way you can leverage an existing, well-known IDE while introducing features needed for your language."&lt;br /&gt;&lt;br /&gt;"Good idea!"&lt;span style="font-style: italic;"&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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 &lt;/span&gt;&lt;/span&gt;oof&lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt; fell through the hole in the center, falling falling what's that down there is that red—&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-7553251676323533984?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/7553251676323533984/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=7553251676323533984' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7553251676323533984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7553251676323533984'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2009/09/i-wanna-make-ide-for-my-language.html' title='I wanna make an IDE for my language‽'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-9176637929230105688</id><published>2009-08-05T11:58:00.000-07:00</published><updated>2009-08-05T12:06:33.773-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Recursive directory creation API</title><content type='html'>Right way: succeed if directory exists.  Examples: &lt;a href="http://www.gnu.org/software/coreutils/manual/html_node/mkdir-invocation.html#mkdir-invocation"&gt;GNU Coreutils&lt;/a&gt;, &lt;a href="http://www.xach.com/clhs?q=ensure-directories-exist"&gt;Common Lisp&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Wrong way: fail if directory exists.  Example: &lt;a href="http://docs.python.org/library/os.html#os.makedirs"&gt;Python&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;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 &lt;code&gt;‘/’&lt;/code&gt;, when all else doesn't fail.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-9176637929230105688?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/9176637929230105688/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=9176637929230105688' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/9176637929230105688'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/9176637929230105688'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2009/08/recursive-directory-creation-api.html' title='Recursive directory creation API'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-6538626329116003828</id><published>2009-07-07T05:22:00.000-07:00</published><updated>2009-07-07T06:14:47.077-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dependencies'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='plone'/><category scheme='http://www.blogger.com/atom/ns#' term='zope'/><title type='text'>PLIP #42: Remove Zope dependency</title><content type='html'>&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="color: rgb(153, 153, 153);font-size:100%;" &gt;Manage requests, persistence, and scaling directly within Plone, eliminating a system that is confusing to first-time users&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:85%;" &gt;Proposed by&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;Stephen Compall&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Seconded by&lt;/span&gt;&lt;br /&gt;None as yet&lt;/span&gt;&lt;span style="font-size:85%;"&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Proposal type&lt;/span&gt;&lt;br /&gt;Core&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;State&lt;/span&gt;&lt;br /&gt;being-discussed&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Motivation&lt;/h3&gt;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.&lt;br /&gt;&lt;br /&gt;Plone is now large enough that it should be able to stand on its own, without Zope.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Assumptions&lt;/h3&gt;This proposal suggests the removal of Zope only.  Others have suggested, variously:&lt;br /&gt;&lt;/span&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Removal of Python dependency;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Allow extension via CLR;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Allow extension via PHP;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Portability to abacuses.&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-size:85%;"&gt;This proposal only concerns Zope.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Proposal &amp;amp; Implementation&lt;/h3&gt;&lt;span style="font-size:85%;"&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Deliverables&lt;/h3&gt;Included are:&lt;br /&gt;&lt;/span&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;New versions of all Plone packages with Zope dependencies removed.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;A new paster to replace ZopeSkel, with a buildout skeleton that does not include the zope2instance recipe.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;New Plone packages as needed to replace remaining Zope functionality that might be convenient.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-size:85%;"&gt;Replacement of all mention of "products", a decidedly Zope-centric term, with "goodies".&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Risks&lt;/h3&gt;&lt;span style="font-size:85%;"&gt;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.&lt;br /&gt;&lt;br /&gt;To mitigate this inconvenience, we might offer a compatibility package, &lt;span style="font-style: italic;"&gt;Nine&lt;/span&gt;, which provides features of Zope 2 and 3 for Plone 4 add-on goodies that need them.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Participants&lt;/h3&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Progress&lt;/h3&gt;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.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-6538626329116003828?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/6538626329116003828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=6538626329116003828' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/6538626329116003828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/6538626329116003828'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2009/07/plip-42-remove-zope-dependency.html' title='PLIP #42: Remove Zope dependency'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-8841297532980184271</id><published>2009-04-26T03:08:00.000-07:00</published><updated>2009-04-26T07:32:14.534-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bazaar'/><category scheme='http://www.blogger.com/atom/ns#' term='vcs'/><title type='text'>svn-upgrade on a Bazaar Loom</title><content type='html'>I maintain modifications of &lt;a href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt;, a Subversion project, as a &lt;a href="https://launchpad.net/bzr-loom"&gt;Bazaar Loom&lt;/a&gt;.  The bottom thread mirrors the &lt;tt&gt;0.11-stable&lt;/tt&gt; branch using &lt;a href="http://bazaar-vcs.org/BzrForeignBranches/Subversion"&gt;bzr-svn&lt;/a&gt;, with features built purely in Bazaar revisions on top.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;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.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Make sure you are on the top thread.  &lt;tt&gt;svn-upgrade&lt;/tt&gt; only converts the HEAD and its parents, so if you aren't, the conversion will miss higher threads.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;tt&gt;bzr record&lt;/tt&gt; the loom if there are changes.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Copy or branch the loom to a backup, in case you mess up.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;In the Loom directory, run &lt;tt&gt;bzr svn-upgrade --idmap-file=../myproj.idmap http://svn.edgewall.org/repos/trac/&lt;/tt&gt;.  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 &lt;tt&gt;parent_location&lt;/tt&gt; set in &lt;tt&gt;.bzr/branch/branch.conf&lt;/tt&gt; to the &lt;strong&gt;actual&lt;/strong&gt; Subversion branch, you must specify the URL to the &lt;em&gt;repository&lt;/em&gt;.  This should be some parent directory of the Subversion branch URL, and is usually the one that contains the &lt;tt&gt;branches&lt;/tt&gt;, &lt;tt&gt;tags&lt;/tt&gt;, and &lt;tt&gt;trunk&lt;/tt&gt; subdirectories.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;That should spend a bunch of time accessing the network.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;p&gt;Now you need to fix up the &lt;tt&gt;.bzr/branch/last-loom&lt;/tt&gt; file.  This is a plain text file, and opening it you'll immediately see why we saved an idmap.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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 &lt;samp&gt;" : "&lt;/samp&gt;, the current revision after, and of course the thread name.&lt;br /&gt;&lt;br /&gt;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.&lt;/p&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;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.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Back in last-loom, change the revids &lt;em&gt;after&lt;/em&gt; 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.  &lt;strong&gt;Do not change the revid before the " : ".  Do not change line 2.  Do not change the last line, because it was fixed already.&lt;/strong&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Move down through the loom looking for failures of &lt;tt&gt;bzr log --show-ids -l1&lt;/tt&gt;, 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.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Once you've verified everything, move back to the top thread with &lt;tt&gt;bzr switch&lt;/tt&gt; and record the loom.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h3&gt;What about inter-Loom activity?&lt;/h3&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-8841297532980184271?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/8841297532980184271/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=8841297532980184271' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/8841297532980184271'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/8841297532980184271'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2009/04/svn-upgrade-on-bazaar-loom.html' title='svn-upgrade on a Bazaar Loom'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-7407595783702455029</id><published>2008-12-04T18:33:00.000-08:00</published><updated>2008-12-04T18:41:19.447-08:00</updated><title type='text'>OLPC and the dying belief</title><content type='html'>I quoted OLPC &lt;a href="http://failex.blogspot.com/2008/09/blame-release-tarballs-for-installation.html"&gt;earlier&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;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.&lt;/blockquote&gt;&lt;p&gt;Short, straightforward, and powerful language.&lt;br /&gt;&lt;br /&gt;Compare to the &lt;a href="http://laptop.org/en/laptop/software/"&gt;current page contents&lt;/a&gt;.  Here's a sample:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;A truly inspiring stand for constructivist teaching.  Unfortunately, as far as I can tell, little else is being said.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-7407595783702455029?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/7407595783702455029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=7407595783702455029' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7407595783702455029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7407595783702455029'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/12/olpc-and-dying-belief.html' title='OLPC and the dying belief'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-8829307459296425241</id><published>2008-11-30T10:39:00.001-08:00</published><updated>2008-11-30T10:45:41.632-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='weblocks'/><category scheme='http://www.blogger.com/atom/ns#' term='continuations'/><title type='text'>Understanding cl-cont semantics without thinking about CPS</title><content type='html'>Understanding the way that &lt;code&gt;cl-cont&lt;/code&gt; 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.&lt;br /&gt;&lt;br /&gt;As a &lt;code&gt;cl-cont&lt;/code&gt; 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".&lt;/p&gt;&lt;h2&gt;Understanding first&lt;/h2&gt;&lt;p&gt;To follow along with the behavior model of &lt;code&gt;cl-cont&lt;/code&gt;, you should be familiar with dynamic context (such as that established by &lt;code&gt;unwind-protect&lt;/code&gt;), lexical context, and the behavior of full continuations such as you would find in Scheme.&lt;br /&gt;&lt;br /&gt;Understanding "ordinary" continuations is especially important, because &lt;code&gt;cl-cont&lt;/code&gt;'s behavior is more complex than full continuations, and you will be lost without preexisting knowledge of what they are.&lt;/p&gt;&lt;h2&gt;Defining some terms&lt;/h2&gt;&lt;p&gt;First, a &lt;dfn&gt;continuation&lt;/dfn&gt;, unless otherwise qualified, is always a &lt;em&gt;first-class&lt;/em&gt; continuation—a function that, when called, returns its arguments as values to the place where the &lt;code&gt;call/cc&lt;/code&gt; call that created the continuation was called.  This is just to be clear that I hardly ever mean something abstract when I say "continuation".&lt;br /&gt;&lt;br /&gt;The macro &lt;code&gt;with-call/cc&lt;/code&gt; introduces a &lt;dfn&gt;lexical continuable context&lt;/dfn&gt; for the forms lexically contained within it.  We will refer to this so often that I will call it &lt;strong&gt;LCC&lt;/strong&gt; henceforth to avoid confusion.  For all current purposes, it only matters whether code exists within &lt;em&gt;any&lt;/em&gt; 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 &lt;strong&gt;LNCC&lt;/strong&gt; (lexical &lt;em&gt;non&lt;/em&gt;-continuable context).  We will define more rules for determining whether code is in an LCC later.&lt;br /&gt;&lt;br /&gt;Entering an LCC at the beginning creates a &lt;dfn&gt;dynamic continuable context&lt;/dfn&gt; or &lt;strong&gt;DCC&lt;/strong&gt;.  Understanding DCC behavior is the key to understanding &lt;code&gt;cl-cont&lt;/code&gt;.  Unlike LCC, you can have multiple distinct DCCs active at any time, possibly even sharing code, just as one function calling &lt;code&gt;mapcar&lt;/code&gt; doesn't preclude you from doing so.  All code not executing within a DCC is executing within a &lt;strong&gt;DNCC&lt;/strong&gt; (dynamic &lt;em&gt;non&lt;/em&gt;-continuable context).&lt;/p&gt;&lt;h2&gt;Lexical continuable contexts&lt;/h2&gt;&lt;p&gt;As I have said, &lt;code&gt;with-call/cc&lt;/code&gt; introduces an LCC.  This is an implicit progn.  A few convenient macros, such as &lt;code&gt;lambda/cc&lt;/code&gt; and &lt;code&gt;defun/cc&lt;/code&gt;, wrap their non-cc counterparts with &lt;code&gt;with-call/cc&lt;/code&gt;.  Within this form, an LNCC can be inserted with the macro &lt;code&gt;without-call/cc&lt;/code&gt;.  The pseudo-function &lt;code&gt;call/cc&lt;/code&gt; also implies a &lt;code&gt;without-call/cc&lt;/code&gt; around its sole argument.  Within any LNCC, including the implicit one around every program, further LCCs may be introduced with &lt;code&gt;with-call/cc&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;There are some cases where you might think that an LCC is there or doesn't matter, where it actually does.  In this code:&lt;/p&gt;&lt;pre&gt;(defun x ()&lt;br /&gt; (with-call/cc&lt;br /&gt;   1 2))&lt;/pre&gt;&lt;p&gt;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 &lt;code&gt;with-call/cc&lt;/code&gt; to outside the &lt;code&gt;defun&lt;/code&gt;, or use the equivalent &lt;code&gt;defun/cc&lt;/code&gt; convenience macro.&lt;br /&gt;&lt;br /&gt;A similar situation holds for this code:&lt;/p&gt;&lt;pre&gt;(lambda () (with-call/cc 3 4))&lt;/pre&gt;&lt;p&gt;Calling this function is the same as calling X above, and the same solutions apply.&lt;br /&gt;&lt;br /&gt;One final area of confusion may be:&lt;/p&gt;&lt;pre&gt;(with-call/cc&lt;br /&gt; (without-call/cc&lt;br /&gt;   (with-call/cc 5 6)))&lt;/pre&gt;&lt;p&gt;This is not a smooth contour of an LCC; the &lt;code&gt;without-call/cc&lt;/code&gt; always creates an LNCC, even if a new one is created immediately therein.&lt;/p&gt;&lt;h2&gt;Basic DCC execution&lt;/h2&gt;&lt;p&gt;Entering any LCC from a DNCC, by either calling a function created or defined in an LCC or simply evaluating a &lt;code&gt;with-call/cc&lt;/code&gt; 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:&lt;/p&gt;&lt;pre&gt;(defun/cc bob ()&lt;br /&gt; (cons 4 2)&lt;br /&gt; :bob)&lt;br /&gt;(defun/cc slack ()&lt;br /&gt; (bob)&lt;br /&gt; :slack)&lt;br /&gt;(defun moo ()&lt;br /&gt; (bob)&lt;br /&gt; (slack))&lt;/pre&gt;&lt;p&gt;Calling &lt;code&gt;bob&lt;/code&gt; in &lt;code&gt;moo&lt;/code&gt; creates a DCC whose exit point is the code that returns the keyword &lt;code&gt;:bob&lt;/code&gt;.  Calling &lt;code&gt;slack&lt;/code&gt; in &lt;code&gt;moo&lt;/code&gt; creates another one whose exit point is returning &lt;code&gt;:slack&lt;/code&gt;.  The call to &lt;code&gt;bob&lt;/code&gt; within the call to &lt;code&gt;slack&lt;/code&gt; &lt;em&gt;does not create a new DCC, or even alter the existing one!&lt;/em&gt;  Whether calling &lt;code&gt;bob&lt;/code&gt; creates a new DCC depends on the nature of the calling code.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;While in a DCC, calling a function defined in an LNCC suspends that DCC.&lt;/em&gt;  That happens in &lt;code&gt;bob&lt;/code&gt; above when calling &lt;code&gt;cons&lt;/code&gt;, 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 &lt;em&gt;same&lt;/em&gt; DCC is resumed; as such, the cons above destroys neither DCC's exit point information.&lt;br /&gt;&lt;br /&gt;The function rule sounds like a special case, but it is really just a subcase of the continuation case.  &lt;em&gt;Invoking a continuation enters the DCC in which it was created.&lt;/em&gt;  In &lt;code&gt;cl-cont&lt;/code&gt;, calling LNCC functions is handled by grabbing the continuationand invoking it with the function's result after it finishes.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;After exiting a DCC, the only way to reenter it is to invoke one of its continuations.&lt;/em&gt;  Calling a function defined therein only creates a new DCC delimited by that function's body.&lt;br /&gt;&lt;br /&gt;Finally, a point about &lt;code&gt;call/cc&lt;/code&gt; that will surprise you if you are used to Scheme: &lt;em&gt;Invoking &lt;code&gt;call/cc&lt;/code&gt; exits the DCC, returning the values returned by the function you gave it.&lt;/em&gt;  You are, of course, free to simulate Scheme behavior by invoking the continuation from right within that function.&lt;/p&gt;&lt;h2&gt;Strange exit point behavior&lt;/h2&gt;&lt;p&gt;This may seem a little surprising:&lt;/p&gt;&lt;pre&gt;(defvar *cc*)&lt;br /&gt;(defun/cc foo ()&lt;br /&gt; (let/cc cc (setf *cc* cc) 'saved)&lt;br /&gt; 'foo)&lt;br /&gt;&lt;br /&gt;(defun/cc bar ()&lt;br /&gt; (foo)&lt;br /&gt; 'bar)&lt;br /&gt;&lt;br /&gt;(progn&lt;br /&gt; (foo) ; ⇒SAVED&lt;br /&gt; (funcall *cc*) ; ⇒FOO&lt;br /&gt; (bar) ; ⇒SAVED&lt;br /&gt; (funcall *cc*) ; ⇒BAR&lt;br /&gt;)&lt;/pre&gt;&lt;p&gt;The important thing to remember is that it doesn't matter at all what context you invoke a continuation in from a DNCC; &lt;em&gt;you always get the exit point of the continuation's DCC.&lt;/em&gt;&lt;/p&gt;&lt;h2&gt;Nested DCCs&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;&lt;pre&gt;(let (keep-going list)&lt;br /&gt; (progn (with-call/cc&lt;br /&gt;          (setf list (mapcar (lambda (n)&lt;br /&gt;                               (let/cc k (setf keep-going k) 42))&lt;br /&gt;                             (list 1 2 3 4 5))))&lt;br /&gt;   (dotimes (n 5)&lt;br /&gt;     (funcall keep-going (+ n 6)))&lt;br /&gt;   list))&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;mapcar&lt;/code&gt; was defined in an LCC, you would get the result you expect, &lt;code&gt;(6 7 8 9 10)&lt;/code&gt;.  What you expect is that for each element in (1 2 3 4 5), &lt;code&gt;let/cc&lt;/code&gt; saves and suspends the continuation..  The first time, the &lt;code&gt;dotimes&lt;/code&gt; 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).&lt;br /&gt;&lt;br /&gt;Instead, you get the result (42 42 42 42 42).  Why?&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;mapcar&lt;/code&gt; 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.&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-8829307459296425241?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/8829307459296425241/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=8829307459296425241' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/8829307459296425241'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/8829307459296425241'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/11/understanding-cl-cont-semantics-without.html' title='Understanding cl-cont semantics without thinking about CPS'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-5644597208787867471</id><published>2008-10-11T01:30:00.000-07:00</published><updated>2008-10-11T01:36:15.829-07:00</updated><title type='text'>A practical use for change-class!</title><content type='html'>Previously only thought useful for metaprogramming, the amazing &lt;code&gt;change-class&lt;/code&gt; finds a use in the hallowed realm of application programming.&lt;/p&gt;&lt;pre&gt;(defwidget maybe-pagination (weblocks:pagination)&lt;br /&gt;  ((show-pred :initarg :show-predicate :accessor show-paginator-predicate&lt;br /&gt;              :initform (constantly t)&lt;br /&gt;              :documentation "One-arg proc accepting self answering&lt;br /&gt;              whether to show the widget contents."))&lt;br /&gt;  (:documentation "Hide the paginator sometimes."))&lt;br /&gt;&lt;br /&gt;(defmethod weblocks:render-widget-body&lt;br /&gt;    ((self maybe-pagination) &amp;key &amp;allow-other-keys)&lt;br /&gt;  "Iff `show-paginator-predicate''s value answers NIL on myself, don't&lt;br /&gt;send to super."&lt;br /&gt;  (when (funcall (show-paginator-predicate self) self)&lt;br /&gt;    (call-next-method)))&lt;br /&gt;&lt;br /&gt;(defmethod initialize-instance :after&lt;br /&gt;    ((self my-listedit-subclass) &amp;key &amp;allow-other-keys)&lt;br /&gt;  (change-class (weblocks:dataseq-pagination-widget self) 'maybe-pagination&lt;br /&gt;    :show-predicate (f_% (typep (weblocks:authenticatedp) 'admin-account))))&lt;/pre&gt;&lt;p&gt;Wow, huh?&lt;br /&gt;&lt;br /&gt;(The above makes it so that only admins can see the subwidget that&lt;br /&gt;shows the current page with links for paging through the results.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-5644597208787867471?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/5644597208787867471/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=5644597208787867471' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/5644597208787867471'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/5644597208787867471'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/10/practical-use-for-change-class.html' title='A practical use for change-class!'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-7180384005693289578</id><published>2008-09-16T19:32:00.000-07:00</published><updated>2008-09-16T20:51:05.809-07:00</updated><title type='text'>What is cascading rebase?</title><content type='html'>The problem of &lt;dfn&gt;cascading rebase&lt;/dfn&gt; is a serious one, and should not be taken lightly.  Though the idea of cleaning history may be appealing, it is almost always inappropriate.&lt;br /&gt;&lt;br /&gt;This problem is what makes all documenters of rebase functionality implore you to avoid rebasing public repositories.  However, the nature of the problem is difficult to explain to those without a good understanding of the DAG-based branching and merging employed by most modern distributed VCSes, including Bazaar, Mercurial, and Git.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What starts simple…&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Imagine a larger version of this very abbreviated history.  Bob has started a project and commits new revisions to the mainline, and collects contributions by merging from others' branches.&lt;br /&gt;&lt;br /&gt;Here we see Bob's mainline in red.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/mainline"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Topic branching&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Alice has branched mainline-3 to create a feature, and made a few commits.  The idea is that Bob will merge the feature branch, in green, back into mainline.  Work on mainline can continue, as seen by additional mainline revisions.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/feature-branch"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Uh oh…&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Bob realizes that mainline-2 was done wrong.  He decides the problem is serious enough that it has to be redone.&lt;br /&gt;&lt;br /&gt;This is not the only kind of rebasing; rebasing is a subsequence replacement operation, so it can be used to insert revs in history, delete revs from history, or insert and replace any number of revs at any point.  In this case, we're replacing mainline-2 with mainline-2'.&lt;br /&gt;&lt;br /&gt;Here is the completed rebase operation.  The dead revisions are shown in light red.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/rebase"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Rebasing is really branching&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Every revision in a proper DAG history contains an immutable reference to its parents.  So mainline-3 has a reference to mainline-2.  But we rewrote mainline-2, so we must rebuild mainline-3 so it refers to the new mainline-2 instead, 4 to 3, 5 to 4, and so on.&lt;br /&gt;&lt;br /&gt;As such, rebasing doesn't really "rewrite history"; it finds the latest revision that &lt;em&gt;doesn't&lt;/em&gt; have to be rewritten, branches off it, builds the new history onto that branch, and finally replaces the current branch with the new branch.  You can see this clearly in an alternate, but equivalent visualization of the previous graph.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/rebase-is-branching"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Why is the feature branch still connected to the dead revisions?&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;The rules don't just apply to mainline; feature-4 also has a reference to mainline-3.  This is an important integrity feature—as a brancher working on a private feature, Alice wouldn't want Bob to be able to spontaneously rewrite her branch's source by altering revisions made before she branched.  Therefore, those "dead" revisions still live as part of the feature branch's history.&lt;br /&gt;&lt;br /&gt;I show them above the feature branch here to show how they have become part of Alice's history.  They will also be shared with any other branches that were made off mainline after mainline-2 and before the rebase.  Please note that the history of Alice's branch remains exactly the same as it was before the rebase.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/deads-are-feature"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;What happens with a merge?&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Feature branches live to die; they ought to be merged back into the mainline eventually, when the feature is ready to be part of it.  So Alice would like Bob to merge her feature branch into mainline.&lt;br /&gt;&lt;br /&gt;Except the idea of mainline has changed due to the rebase.  Here is a symmetric merge diagram to illustrate.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/merge-prerebase"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;One step in the cascade occurs&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;Alice by necessity includes the broken changes from the old mainline-2 and the mostly duplicate mainline-3.  The DAG sees these as separate from mainline-2' and mainline-3', as they are.  So the merge is wrong.&lt;br /&gt;&lt;br /&gt;To fix this, Alice must produce a new branch and rewrite her changes onto it.  We can do this with rebase, but it requires Alice to know which revisions are duplicates.  Here, Alice must know that mainline-3' is the new basis.  This seems simple, but imagine if mainline-2 had been simply deleted, or some new revisions had been inserted.  Then the revisions would be numbered differently; in that case she would have to rebase from mainline-3 to mainline-2'.&lt;br /&gt;&lt;br /&gt;Here is the result of her rebase, and the correct merge.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/cascading-rebase/merge-postrebase"/&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Now the cascade happens: if any topic branches were made from the "dead" feature-4, feature-5, and feature-6, they must also be rebased onto the new feature-4', 5', or 6', as appropriate.  And so on.  And so on.&lt;/strong&gt;  Hence my name for this issue, which I don't believe has been adopted by anyone but appropriately illustrates its seriousness, &lt;dfn&gt;cascading rebase&lt;/dfn&gt;.&lt;br /&gt;&lt;br /&gt;Cascading rebase cannot be solved automatically; all rebase tools recognize this and have some interactive conflict resolution features.  Furthermore, there is no guarantee that when a rebase goes smoothly on the original branch, it will also go so smoothly on the cascade.  It also gets &lt;em&gt;wildly&lt;/em&gt; complicated to calculate the cascade when you include behavior like synchronization by merging and especially cross-merging.&lt;br /&gt;&lt;br /&gt;Before you consider rebasing, consider other tools for fixing problems.  If you made a mistake in an old revision, and it is serious enough that all branches since should receive the fix as well, a good alternative is to branch off the broken revision, write the fix, commit to the new branch, merge it into mainline, publish the revision, and ask others to merge it in.  Even if they don't, the common history will mean that merging won't see a parallel "fixed" revision, making the merge cleaner and less likely to conflict.&lt;br /&gt;&lt;br /&gt;You don't even have to create a new URL in many cases.  In both Bazaar and Mercurial, the commit to the side branch will receive a revision number in your mainline.  By merging the mainline at this revision number, branches will receive only that revision, without being forced to merge the head of mainline.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-7180384005693289578?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/7180384005693289578/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=7180384005693289578' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7180384005693289578'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7180384005693289578'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/09/what-is-cascading-rebase.html' title='What is cascading rebase?'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-6048388944676581051</id><published>2008-09-08T14:16:00.000-07:00</published><updated>2008-09-08T19:24:23.572-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='bazaar'/><category scheme='http://www.blogger.com/atom/ns#' term='vcs'/><title type='text'>Blame release tarballs for the installation problem</title><content type='html'>…plus love for distributed version control.&lt;br /&gt;&lt;br /&gt;Many have noticed the failure of &lt;a href="http://www.cliki.net/ASDF-Install"&gt;ASDF-Install&lt;/a&gt; to make installing Lisp packages universally easy.  The situation is such that most serious Lispers don't bother with it, and many casual Lispers encounter a blocking problem such as the uninstallability of some dependency.  The latter generally do one of these:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Ask the mailing list for the failing package for help.  This generally elicits either a new package post or an exhortation to use the &lt;a href="http://en.wikipedia.org/wiki/Version_control_system#Distributed_revision_control"&gt;VCS&lt;/a&gt;, as releases are worthless for this particular package.&lt;/li&gt;&lt;li&gt;Ask on IRC.  These also generally lead to a response of “use the VCS”.&lt;/li&gt;&lt;li&gt;Just use the VCS themselves, or explode the tarball and configure it themselves.&lt;/li&gt;&lt;li&gt;Give up.  Well, that's that.&lt;/li&gt;&lt;/ol&gt;I'll spoil the ending and say that I think reliance on release tarballs is the main failing of ASDF-Install.  Furthermore, I think it's a major mistake to assume that the tarball even qualifies as an appropriate modern distribution medium for software as easily rebuildable as most Lisp packages.&lt;br /&gt;&lt;br /&gt;First, there is the symptomatic drawback.  Everyone familiar with source releases has noticed that &lt;strong&gt;you have to download the entire package again to get the changes&lt;/strong&gt;, which could otherwise be represented in a smaller compressed &lt;a href="http://en.wikipedia.org/wiki/Diff"&gt;diff&lt;/a&gt;.  Some GNU packages even distribute an xdelta, a kind of binary diff, along with release tarballs.  The problem with this is that the number of diffs or xdeltas needed for maximum download efficiency is &lt;samp&gt;(&lt;span style="font-style: italic;"&gt;n&lt;/span&gt;−1)²−&lt;span style="font-style: italic;"&gt;&lt;/span&gt;1&lt;/samp&gt; where &lt;samp&gt;&lt;span style="font-style: italic;"&gt;n&lt;/span&gt;=&lt;/samp&gt;the number of releases over the course of the project.   Setting that aside, now that broadband is broadly available, many believe the tarball-upgrade problem has been solved.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Some tarballs have real problems&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;However, I believe we've only treated the symptom, not the actual problem.  We should have taken the inefficiency of making users download whole source trees over and over just to get little changes as a sign that there was a more serious problem with tarballs, demonstrated by further symptoms.&lt;br /&gt;&lt;br /&gt;With tarballs, there's &lt;strong&gt;no automatic way to be sure you have the latest version&lt;/strong&gt;.  So you report a bug, get a reply “it's fixed in the new release; upgrade.”&lt;br /&gt;&lt;br /&gt;Then, there's &lt;strong&gt;no automatic way to upgrade to said version&lt;/strong&gt;.  Even when managed by a binary distribution system like APT, you'll still encounter cases where some feature you want or need has been implemented upstream, possibly even released, but you just have to sit on your hands until it trickles down.&lt;br /&gt;&lt;br /&gt;I've encountered this over and over with binary distributions: had to install my own git to get the &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/git-pull.html"&gt;pull --rebase&lt;/a&gt; option; cdrkit to get a bugfix for my DVD burner; &lt;a href="http://selenic.com/mercurial"&gt;hg&lt;/a&gt; to get &lt;a href="http://www.selenic.com/mercurial/wiki/index.cgi/RebaseExtension"&gt;the new rebase plugin&lt;/a&gt;; &lt;a href="http://www.darcs.net/"&gt;Darcs&lt;/a&gt; to see whether Darcs2 is &lt;a href="http://wiki.darcs.net/DarcsWiki/DarcsTwo"&gt;better at managing trees&lt;/a&gt;; Mono to get &lt;a href="http://www.go-mono.com/archive/1.9/"&gt;.NET 2.0 features&lt;/a&gt;; etc.  Now I'm trying to build my own Evolution to be able to &lt;a href="http://www.openchange.org/index.php?option=com_content&amp;task=view&amp;id=65&amp;Itemid=74"&gt;connect to MS Exchange 2007&lt;/a&gt;, without much luck so far.&lt;br /&gt;&lt;br /&gt;Such as it is, there's no sense in binary-installing software I'm actually serious about using, such as &lt;a href="http://bazaar-vcs.org/"&gt;Bazaar-NG&lt;/a&gt; or Lisp implementations; it's easier to just track and build new releases myself.&lt;br /&gt;&lt;br /&gt;Most importantly, &lt;strong&gt;it places a serious barrier between &lt;em&gt;using&lt;/em&gt; code and &lt;em&gt;modifying&lt;/em&gt; code&lt;/strong&gt;.  It's one thing to make a change, build, and install.  But this isn't automatically persistent.  If all you have is the binary, then you have to download the source again.  If you didn't plan to make the change when you exploded the tarball, you have to rename the tree and explode the tarball again, to make a diff and store it somewhere; planners ahead may use &lt;a href="http://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#cp-invocation"&gt;cp -al&lt;/a&gt;, and take care to break hardlinks when working.  Then, every time there's a new release, you have to reapply the diff; if there are conflicts, you have to fix them then replace the diff (possibly separately, in case you have to reinstall an older release).  &lt;strong&gt;Then&lt;/strong&gt;, if you're serious about getting your change into the upstream, you have to get the source once more, via a VCS, and apply it there as well.&lt;br /&gt;&lt;br /&gt;I have a patch for &lt;a href="http://weitz.de/hunchentoot/"&gt;Hunchentoot 0.15.7&lt;/a&gt; (downloaded from tarball) locally, that lets you start a server in SBCL while in a compilation unit (otherwise deadlocking).  After spending a while on this, I was asked to reapply it to &lt;a href="http://bknr.net/trac/browser/trunk/thirdparty/hunchentoot"&gt;the SVN version mysteriously hosted at BKNR&lt;/a&gt;.  Of course, the one at BKNR had already rewritten the function in question, so my patch was inapplicable.  The disconnect between the idea of developing for Hunchentoot (on an &lt;em&gt;unadvertised&lt;/em&gt; SVN) and using it (see e.g. Weblocks, which will not build against SVN Hunchentoot, because it targets 0.15.7, the latest advertised version of Hunchentoot) has become so large that you might not even call them the same software anymore.  I can't drop in the SVN Hunchentoot because it would break Weblocks, and I can't fix Weblocks (well, dev, I'll probably do a branch sometime) because it would break it for everyone who hasn't found the SVN at BKNR and therefore assumes 0.15.7 is the latest and greatest.&lt;br /&gt;&lt;br /&gt;If I sound frustrated with this, imagine if it was the first time I had ever tried to contribute to a Lisp project.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;DVCS solves all these problems&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;My answer to all of the above is one that Lisp users should be familiar with: “use the VCS”.  Let's go over the problems again, and see how DVCS solves them:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Downloading the same thing over and over.&lt;/strong&gt;  With a DVCS, you get the entire history in compact format, so you can fast-forward and rewind to any release very quickly, and the tool will reconstruct history for you.  Deltas are combined on-the-fly, so there is no quadratic explosion of deltas.  To get new changes, you only download the parts of history you don't have.&lt;br /&gt;&lt;br /&gt;When history gets too big, systems like Bazaar feature horizons and overlays, so you need only download history back to a certain point.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Be sure you have the latest version, possibly upgrading to it.&lt;/strong&gt;  All DVCSes have convenient commands to upgrade to the latest version.  Most also have graphical tools to browse and wind around in history, if you don't like the new version.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Transitioning from user to developer.&lt;/strong&gt;  You may not agree this is an important goal for software just being used by someone, but I will not delve into this, allowing &lt;a href="http://laptop.org/laptop/software/"&gt;the OLPC project to speak for me&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;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.&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;(On a side note, reliance on release &lt;samp&gt;.xo&lt;/samp&gt; files containing &lt;a href="http://wiki.laptop.org/go/Activities"&gt;activities&lt;/a&gt; makes figuring out which activities will work with your XO a nightmare.  To reach the full potential of &lt;a href="http://wiki.laptop.org/go/Activities#Programming"&gt;Develop and related activities&lt;/a&gt;, I think that OLPC will be forced to adopt a VCS-driven, per-XO branching distribution framework.)&lt;br /&gt;&lt;br /&gt;With a local DVCS checkout, all you need to do is make your changes and commit them.  For sending upstream, all tools include, or have plugins, to send one or a series of changes to an upstream mailing list with a single command.  Even private or rejected changes are safe: you can merge new upstream changes onto your branch (for which new conflict resolutions are automatically managed and fully rewindable), or use rebase to move your changes up to the tip of the upstream changes.  If you make many changes and are given a branch on the upstream's server, it's yet another single command to push them to the new remote location.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;But DVCS…&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Let's start with the obvious, &lt;strong&gt;DVCS gives you an unstable developer's version rather than a stable version.&lt;/strong&gt;  This is a straw man, considering that modern DVCSes support powerful branching and merging.  If your mainline frequently destabilizes, you can point everyone to a “stable” DVCS branch URL that receives regular merges from the unstable branch when it stabilizes.  Pushing revisions across the network is so easy, as opposed to making a new release tarball, that this is likely to get far more frequent updates.&lt;br /&gt;&lt;br /&gt;I can imagine a report for a bug needing only minor changes in this environment:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;User: $ bzr merge --pull&lt;/li&gt;&lt;li&gt;User: test test test&lt;br /&gt;&lt;/li&gt;&lt;li&gt;User: hey, there's a bug in the HEAD of the stable branch&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Maint: what with&lt;/li&gt;&lt;li&gt;User: xxx yyy zzz&lt;/li&gt;&lt;li&gt;Maint: okay, just a sec&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Maint: work work work on stable (or cherry fix from unstable)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Maint: merge onto unstable&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Maint: okay, fixed in r4242&lt;br /&gt;&lt;/li&gt;&lt;li&gt;User: $ bzr merge --pull&lt;/li&gt;&lt;li&gt;User: thanks, you're the awesomest Maint&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Super-cheap topic branching means that this process can expand as needed, depending on the size of the changes required.  Furthermore, this easy incremental release process means that the maintainers need no longer weigh the cost of rolling a release against the cost of duplicate bug reports for unreleased fixes; the release is “prerolled”, as it were.&lt;br /&gt;&lt;br /&gt;In a culture of small, incremental changes and widespread tracking of DVCS content, such as that in the Common Lisp community, the “stable” branch might even be the same as the “development” branch, where destabilizing changes are done in separate “topic” branches before being merged into the mainline.  In addition, the effort to make sure that the heads of all the DVCS mainlines are compatible keeps these from driving users into version incompatibility hell.&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Even if that's not enough&lt;/em&gt;, branching and merging allows us infinite granularity in the stability of the release process.  If “stable” changes too often for some users, you can have a “reallystable” branch that merges revisions from “stable” that have reached really-stability, and so on.  This could be used to simulate any kind of release cadence from the “stable” branch that maintainers might like to effect, in only a few shell commands.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;History is too big.&lt;/strong&gt;  Well, first of all, not really.  We've already used the bandwidth argument to dismiss the symptom of redownloading for tarballs; it applies equally here, and you're also getting the benefit of having every version ever.  Even so, for large histories, lightweight checkouts and history horizons let you keep only a subset of history locally.  Bazaar is really a pioneer in this area, but I expect the other DVCSes to catch up in the usual manner of competition among the available tools.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Building and rebuilding all the time takes time.&lt;/strong&gt;  While I can't speak for other environments, the Common Lisp community has admirably solved this problem.  For all of Kenny's reported faults with ASDF, it's still better than anything any other environment has.  It is such that I can run a single &lt;samp&gt;find&lt;/samp&gt; command when downloading a new package by DVCS, thereby installing the package, and Lisp will rebuild changed files and packages on-demand when loading them.  Even without on-demand recompilation, this isn't much of an issue for Lisp: I use a command to wipe out compiled code and rebuild from scratch on a shared Lisp environment I manage, where even given SBCL's relatively slow compiler and FASL loader, it only takes about 90sec to rebuild all 35 or so packages from scratch and write a new image for everyone's immediate use.&lt;br /&gt;&lt;br /&gt;To be honest, this is a real blocker for systems with slow rebuilds and early binding semantics.  It wouldn't work well for C or GHC Haskell, for example.  However, I'm sure that Lisp, systems with on-the-fly interpretation and compilation like CPython and Ruby, and systems with simple, standardized and fast full-compilation processes like Mono would be served well by DVCS-based distribution.&lt;br /&gt;&lt;br /&gt;Probably the most serious objection is really about dependencies, that &lt;strong&gt;you have to have all the DVCS tools used by the systems you use&lt;/strong&gt;.  First, I think existing practice shows that we already have no objection to this; we aren't bothered about requiring everyone to use APT rather than a website with downloads, because we know by comparison that the installation process with APT is orders of magnitude better for users and developers than the traditional Windows “download an installer and run it” method.  (The fact that a Debian APT source does a Fedora yum user no good is all about the system-specific hacks typically packaged with these packages, and has no effect on pristine upstream distribution, which is after all our topic of discussion.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Great, so who's going to implement all this?&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;The most comprehensive effort I've seen at trying to integrate VCS with automated installation is &lt;a href="http://www.pasternacki.net/en/code/cl-librarian/"&gt;CL-Librarian&lt;/a&gt;, which knows how to use a few of the available VCS tools to download and update libraries.  Librarian is mostly a personal effort, and isn't widely ported or adaptive yet, but it's a step in the right direction.  While the above may sound like a very long advertisement for Librarian, I would surely embrace any Common Lisp library manager that takes the new DVCS order to heart and helps us to banish the release tarball.&lt;br /&gt;&lt;br /&gt;I'm currently using a few scripts collectively called &lt;em&gt;lispdir&lt;/em&gt; that drop into the myriad branches in my Lisp tree and update them using the appropriate VCSes.  When I add a new branch, I simply run an &lt;code&gt;asdfify&lt;/code&gt; shell function to add the .asd files therein to my central registry.  It also serves as a list of canonical VCS locations for many projects.  You can &lt;a href="http://scompall.nocandysw.com/lisp/lispdir/"&gt;get that as a Bazaar branch&lt;/a&gt;; acquire.sh downloads all systems, and update.sh updates them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-6048388944676581051?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/6048388944676581051/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=6048388944676581051' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/6048388944676581051'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/6048388944676581051'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/09/blame-release-tarballs-for-installation.html' title='Blame release tarballs for the installation problem'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-7118151759589793868</id><published>2008-09-01T21:03:00.001-07:00</published><updated>2008-09-10T21:24:28.773-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='weblocks'/><category scheme='http://www.blogger.com/atom/ns#' term='mercurial'/><category scheme='http://www.blogger.com/atom/ns#' term='vcs'/><title type='text'>Fixing weblocks test failures, try 1</title><content type='html'>Here we commemorate the demise of the first test fixing branch of Weblocks, &lt;a href="http://www.bitbucket.org/S11001001/weblocks-c6dc18-test-fixes/"&gt;c6dc18-test-fixes&lt;/a&gt;.  At its peak, it got the number of failures in the ~750 test suite down to a whopping 9.  Shortly after its merge back into &lt;a href="http://www.bitbucket.org/coffeemug/weblocks-dev/"&gt;dev&lt;/a&gt;, the count was back up to over 50.&lt;br /&gt;&lt;br /&gt;As I write this, the count on &lt;a href="http://www.bitbucket.org/S11001001/weblocks-test-fixes/"&gt;my new fixes branch&lt;/a&gt; is 84.  On the now 4-patch divergent dev, a trial merge into the fixes branch raises that to 89.  (To be entirely fair, &lt;span style="font-style: italic;"&gt;I&lt;/span&gt; was the committer of the revisions that caused the jump.)&lt;br /&gt;&lt;br /&gt;There is a lesson here about test discipline.  Once you get out of the mindset of making every mainline rev pass all tests, it's hard to get back in.  For quite a while my fix strategy has been a little lax.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If it's small, and I don't think it does much, don't bother testing.&lt;/li&gt;&lt;li&gt;For large changes, make sure the failure count doesn't jump above 100.&lt;/li&gt;&lt;li&gt;Whatever, just make sure my development site (which doesn't even use continuations yet) works.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Symmetric merging is little help here, as all the revs in a topic branch end up as first-class revs in the mainline.  Not that I blame &lt;a href="http://selenic.com/mercurial/"&gt;Mercurial&lt;/a&gt; at all; still, I hope to build future features in topic branches posted on &lt;a href="http://www.bitbucket.org/"&gt;Bitbucket&lt;/a&gt;, preferably with optimized archival storage (backporting hardlinks and such) so dormant branches, such as c6dc18-test-fixes, can stay around forever cheaply.&lt;br /&gt;&lt;br /&gt;I think the failure with c6dc18-test-fixes was not providing public feedback about how the failure counts were diverging between branches.  Now I have scripts that merge, test, and compare test results on two branches, so I can generate all the useless statistics I like.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-7118151759589793868?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/7118151759589793868/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=7118151759589793868' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7118151759589793868'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7118151759589793868'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/09/fixing-weblocks-test-failures-try-1.html' title='Fixing weblocks test failures, try 1'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-7742469071269135337</id><published>2008-08-23T17:19:00.001-07:00</published><updated>2008-08-23T17:46:58.681-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><title type='text'>defclass options slay me, again</title><content type='html'>I previously wrote about missing &lt;code&gt;canonicalize-defclass-options&lt;/code&gt;.  Now I have a nice inconsistency in AMOP to add to the complaints.&lt;br /&gt;&lt;br /&gt;I have believed for some time, for some reason, that this is the standard slot option canonicalizer, excepting the special cases:&lt;p&gt;&lt;/p&gt;&lt;pre&gt;(defun canonicalize-defclass-option (opt)&lt;br /&gt;`(',(car opt)&lt;br /&gt;  ',(if (typep (cdr opt) '(cons t null))&lt;br /&gt;      (cadr opt)&lt;br /&gt;      (cdr opt))))&lt;/pre&gt;In other words, if you gave a single argument, like &lt;code&gt;(:opt &lt;var&gt;val&lt;/var&gt;)&lt;/code&gt;, it wouldn't be listified.  A little weird, with a nasty special case, but a good attempt at dealing with both listy and atomy class options.&lt;br /&gt;&lt;p&gt;Thankfully, AMOP has two contradictory interpretations, neither of which are the above.  First, the example on page 287, which would have it:&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;  `(',(car opt)&lt;br /&gt;   ',(cadr opt))&lt;/pre&gt;Finally, on page 148, hiding from the prying eyes of back-of-the-book indexed content (nowhere &lt;em&gt;near&lt;/em&gt; the entries on &lt;code&gt;defclass&lt;/code&gt;), the true behavior:&lt;br /&gt;&lt;blockquote&gt;Any other class options become the value of keyword arguments with the same name.  The value of the keyword argument is the tail of the class option.&lt;br /&gt;&lt;/blockquote&gt;&lt;pre&gt;  `(',(car opt)&lt;br /&gt;   ',(cdr opt))&lt;/pre&gt;I also previously thought the defclass options to be evaluated, but never mind that.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-7742469071269135337?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/7742469071269135337/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=7742469071269135337' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7742469071269135337'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/7742469071269135337'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/08/defclass-options-slay-me-again.html' title='defclass options slay me, again'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-5690241729962491354</id><published>2008-08-17T22:42:00.000-07:00</published><updated>2008-08-17T23:38:52.758-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='dependencies'/><title type='text'>Dependencies versus effort</title><content type='html'>I do not need to rehash the benefits of relying on other libraries when developing a library here.  If you care about dependencies, you ought to be familiar with those benefits already.&lt;br /&gt;&lt;br /&gt;I wish to instead address the common complaint of "too many dependencies" from those who feel that getting a Common Lisp library installed is too difficult.&lt;br /&gt;&lt;br /&gt;Here is how I feel about such requests:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/images/effort.png" alt="Graph 1"/&gt;&lt;br /&gt;&lt;br /&gt;In short, the effort avoiding a dependency, even in the case of synchronizing with an external source, far exceeds that for the simple process of fetching a dependency yourself and adding it to your ASDF registry.  What's good for the maintainer is good for the library.&lt;br /&gt;&lt;br /&gt;To clarify further:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://scompall.nocandysw.com/images/effort2.png" alt="Graph 2"/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-5690241729962491354?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/5690241729962491354/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=5690241729962491354' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/5690241729962491354'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/5690241729962491354'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/08/dependencies-versus-effort.html' title='Dependencies versus effort'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-3196257223903501506</id><published>2008-08-15T01:18:00.000-07:00</published><updated>2008-08-15T01:39:01.791-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='weblocks'/><title type='text'>A metaclass for weblocks-webapps, try 3</title><content type='html'>This one includes mostly the same &lt;code&gt;shared-initialize&lt;/code&gt; and &lt;code&gt;defwebapp&lt;/code&gt; contents, but updated for some new changes to dev.&lt;br /&gt;&lt;br /&gt;It introduces the &lt;code&gt;:reset&lt;/code&gt; slot option, which follows a leftmost-bound inheritance rule and specifies that &lt;code&gt;shared-initialize&lt;/code&gt; should ignore its second argument for that slot, always assuming the slot should be initialized.&lt;br /&gt;&lt;br /&gt;For &lt;code&gt;weblocks-webapp&lt;/code&gt; in particular, it has the problem that for slots provided with &lt;code&gt;:reset t&lt;/code&gt;, it would be necessary to export the slot names, so that subclasses could cancel the behavior.  It also has the problem that values initialized or normalized by initializer methods are not recognized as reproducible initial values.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(defclass weblocks-webapp-class (standard-class)&lt;br /&gt;  ()&lt;br /&gt;  (:documentation "Class of all classes created by `defwebapp'.  The&lt;br /&gt;  really interesting behavior is in&lt;br /&gt;  `shared-initialize' (weblocks-webapp t &amp;rest)."))&lt;br /&gt;&lt;br /&gt;(defmethod validate-superclass ((class weblocks-webapp-class) superclass)&lt;br /&gt;  (typep (class-name (class-of superclass))&lt;br /&gt;         '(member standard-class weblocks-webapp-class)))&lt;br /&gt;&lt;br /&gt;(defgeneric slot-definition-reset-p (slot-defn)&lt;br /&gt;  (:documentation "Answer whether to change the value of this slot&lt;br /&gt;   when reinitializing or updating an instance after class&lt;br /&gt;   redefinition.")&lt;br /&gt;  (:method ((slot-defn slot-definition))&lt;br /&gt;    "Regardless of this method, only direct-slot-definitions where&lt;br /&gt;    `resetp' is a bound slot may participate in the inheritance rule."&lt;br /&gt;    nil))&lt;br /&gt;&lt;br /&gt;(defgeneric (setf slot-definition-reset-p) (value slot-defn)&lt;br /&gt;  (:documentation "See `slot-definition-reset-p'."))&lt;br /&gt;&lt;br /&gt;(defclass resetting-slot-definition (slot-definition)&lt;br /&gt;  ((resetp :initarg :reset :accessor slot-definition-reset-p))&lt;br /&gt;  (:documentation "I provide the extension that when `resetp' is&lt;br /&gt;  non-nil, and an initarg is not present in the `shared-initialize'&lt;br /&gt;  call, I will use a relevant stored initarg or initfunction to reset&lt;br /&gt;  my value.&lt;br /&gt;&lt;br /&gt;  :reset's inheritance rule is leftmost-bound."))&lt;br /&gt;&lt;br /&gt;(defclass resetting-eslot-definition&lt;br /&gt;    (resetting-slot-definition standard-effective-slot-definition)&lt;br /&gt;  ())&lt;br /&gt;&lt;br /&gt;(defclass resetting-dslot-definition&lt;br /&gt;    (resetting-slot-definition standard-direct-slot-definition)&lt;br /&gt;  ())&lt;br /&gt;&lt;br /&gt;(defmethod direct-slot-definition-class&lt;br /&gt;    ((self weblocks-webapp-class) &amp;rest initargs)&lt;br /&gt;  (declare (ignore initargs))&lt;br /&gt;  (find-class 'resetting-dslot-definition))&lt;br /&gt;&lt;br /&gt;(defmethod effective-slot-definition-class&lt;br /&gt;    ((self weblocks-webapp-class) &amp;rest initargs)&lt;br /&gt;  (declare (ignore initargs))&lt;br /&gt;  (find-class 'resetting-eslot-definition))&lt;br /&gt;&lt;br /&gt;(defun compute-resetting-eslot-definition (eslot class name dslotds)&lt;br /&gt;  "Implement leftmost-bound rule for resetp."&lt;br /&gt;  (declare (ignore name))&lt;br /&gt;  (setf (slot-definition-reset-p eslot)&lt;br /&gt;        (and-let* ((leftmost-bound (find-if (lambda (dslot)&lt;br /&gt;                                              (and (slot-exists-p dslot 'resetp)&lt;br /&gt;                                                   (slot-boundp dslot 'resetp)))&lt;br /&gt;                                            dslotds)))&lt;br /&gt;          (slot-definition-reset-p leftmost-bound)))&lt;br /&gt;  eslot)&lt;br /&gt;&lt;br /&gt;(defmethod compute-effective-slot-definition ((self weblocks-webapp-class) name dslotds)&lt;br /&gt;  (compute-resetting-eslot-definition (call-next-method) self name dslotds))&lt;br /&gt;&lt;br /&gt;(defun reset-slots (instance initargs)&lt;br /&gt;  "Given an instance to be initialized and the initargs passed to the&lt;br /&gt;initializer method, reset the appropriate slots to their original&lt;br /&gt;values."&lt;br /&gt;  (dolist (eslot (class-slots (class-of instance)))&lt;br /&gt;    (when (slot-definition-reset-p eslot)&lt;br /&gt;      (let ((initkeys (slot-definition-initargs eslot)))&lt;br /&gt;        (when (loop for (key) on initargs by #'cddr&lt;br /&gt;                    never (member key initkeys))&lt;br /&gt;          (flet ((set-it (val)&lt;br /&gt;                   (setf (slot-value instance (slot-definition-name eslot)) val)))&lt;br /&gt;            (or (and-let* (initkeys&lt;br /&gt;                           (definit&lt;br /&gt;                            (some (lambda (definit) (member (car definit) initkeys))&lt;br /&gt;                                  (class-default-initargs (class-of instance)))))&lt;br /&gt;                  (set-it (funcall (third definit)))&lt;br /&gt;                  t)&lt;br /&gt;                (and-let* ((initfunc (slot-definition-initfunction eslot)))&lt;br /&gt;                  (set-it (funcall initfunc))&lt;br /&gt;                  t))))))))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;em&gt;Where &lt;code&gt;shared-initialize&lt;/code&gt; calls &lt;code&gt;reset-slots&lt;/code&gt; when its second argument is not &lt;code&gt;t&lt;/code&gt; (as is required for &lt;code&gt;initialize-instance&lt;/code&gt;).&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Notwithstanding the above issues, the real difficulty forcing me to abandon this particular iteration for now is that the test failures in &lt;a href="http://bitbucket.org/coffeemug/weblocks-dev/"&gt;dev&lt;/a&gt; have jumped from 9 (at last pull from &lt;a href="http://bitbucket.org/S11001001/weblocks-c6dc18-test-fixes/"&gt;c6dc18-test-fixes&lt;/a&gt;) to over 90.  I'm sure this has to do with the current &lt;code&gt;defwebapp&lt;/code&gt; implementation having dependency issues eerily similar to that mentioned in my last post.&lt;br /&gt;&lt;br /&gt;By the time I reached this conclusion, I had been too frustrated by fighting with SBCL and SLIME on OS X to do anything about it.  Later today, I'll just boot up in GNU/Linux, wifi be damned.&lt;br /&gt;&lt;br /&gt;Next up, doing as much as I can without the metaclass, just so other dev writers will put more new instance logic in the initialize method instead of &lt;code&gt;defwebapp&lt;/code&gt;.  Then I can revisit the metaclass issue with a fresh look, something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(defclass a-class (s-class)&lt;br /&gt;  ()&lt;br /&gt;  (:persistent-initargs :a :b :c)&lt;br /&gt;  (:transient-initargs :d :e :f))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&amp;hellip;where some initializer method will capture all initargs (not initforms this time, I think), including those given to &lt;code&gt;make-instance&lt;/code&gt;, storing in an instvar, and initarg semantics are determined by a leftmost-persistent-or-transient rule.  This has the benefit of not killing initializer method settings with the common &lt;code&gt;:initform nil&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Or, I'll be back here later with an explanation of why it won't work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-3196257223903501506?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/3196257223903501506/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=3196257223903501506' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/3196257223903501506'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/3196257223903501506'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/08/metaclass-for-weblocks-webapps-try-3.html' title='A metaclass for weblocks-webapps, try 3'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-3629976376818369607</id><published>2008-08-13T00:52:00.000-07:00</published><updated>2008-08-15T01:17:56.655-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='weblocks'/><title type='text'>A metaclass for weblocks-webapps, try 2</title><content type='html'>My next attempt was more interesting, succeeding in point #1, and getting around point #2 with a new slot definition feature.&lt;br /&gt;&lt;br /&gt;Unfortunately, at this point, I ran into the default canonicalization behavior for slots and defclass options.  While AMOP discusses a possible extension via a generic function &lt;code&gt;canonicalize-defclass-options&lt;/code&gt;, it is unfortunately not included in the final MOP.  So:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Except for :default-initargs, class options are always quoted.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Except for :initform, slot options are always quoted.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;That and a dependency issue forced me to abandon this try, which introduces the &lt;dfn&gt;instance initfunction&lt;/dfn&gt; to address the common complaint about initforms that the expression cannot refer to the new instance.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(defmacro and-let* ((&amp;rest bindings) &amp;body body)&lt;br /&gt;  "Like `let*', but stop when encountering a binding that evaluates to&lt;br /&gt;NIL.  Also allows (EXPR), stopping when EXPR is false, and EXPR as a&lt;br /&gt;shortcut for it only if EXPR is a symbol."&lt;br /&gt;  (reduce (lambda (binding body)&lt;br /&gt;            (etypecase binding&lt;br /&gt;              (symbol `(and ,binding ,body))&lt;br /&gt;              ((cons symbol (cons t null))&lt;br /&gt;               `(let (,binding)&lt;br /&gt;                  (and ,(car binding)&lt;br /&gt;                       ,body)))&lt;br /&gt;              ((cons t null)&lt;br /&gt;               `(and ,(car binding) ,body))))&lt;br /&gt;          bindings :from-end t :initial-value (cons 'progn body)))&lt;br /&gt;&lt;br /&gt;(defclass weblocks-webapp-class (standard-class)&lt;br /&gt;  ()&lt;br /&gt;  (:documentation "Class of all classes created by `defwebapp'.  The&lt;br /&gt;  really interesting behavior is in&lt;br /&gt;  `shared-initialize' (weblocks-webapp t &amp;rest)."))&lt;br /&gt;&lt;br /&gt;(defmethod validate-superclass ((class weblocks-webapp-class) superclass)&lt;br /&gt;  (typep (class-name (class-of superclass))&lt;br /&gt;         '(member standard-class weblocks-webapp-class)))&lt;br /&gt;&lt;br /&gt;(defclass instance-initializing-slot-definition (slot-definition)&lt;br /&gt;  ((instance-initfunction :initarg :instance-initfunction :initform nil&lt;br /&gt;                          :accessor slot-definition-instance-initfunction))&lt;br /&gt;  (:documentation "I provide an alternative to `:initform' for slots,&lt;br /&gt;  the `:instance-initfunction', a function taking the instance to be&lt;br /&gt;  initialized."))&lt;br /&gt;&lt;br /&gt;(defclass weblocks-webapp-direct-slot-definition&lt;br /&gt;    (instance-initializing-slot-definition standard-direct-slot-definition)&lt;br /&gt;  ()&lt;br /&gt;  (:documentation "Direct slot definition for `weblocks-webapp-class'es."))&lt;br /&gt;&lt;br /&gt;(defclass weblocks-webapp-effective-slot-definition&lt;br /&gt;    (instance-initializing-slot-definition standard-effective-slot-definition)&lt;br /&gt;  ()&lt;br /&gt;  (:documentation "Effective slot definition for `weblocks-webapp-class'es."))&lt;br /&gt;&lt;br /&gt;(defmethod compute-effective-slot-definition&lt;br /&gt;    ((self weblocks-webapp-class) name direct-slot-defns)&lt;br /&gt;  "Transfer `instance-initfunction' to the effective slot definition,&lt;br /&gt;making sure to override the regular `initfunction' if mine appears&lt;br /&gt;first in the precedence list, so `shared-initialize' will not call it&lt;br /&gt;to fill the slot."&lt;br /&gt;  (let ((eslot (call-next-method)))&lt;br /&gt;    (loop for (dslot . slot-precedence) on direct-slot-defns&lt;br /&gt;          when (slot-definition-initfunction eslot)&lt;br /&gt;            do (return)&lt;br /&gt;          when (and (slot-exists-p self 'instance-initfunction)&lt;br /&gt;                    (slot-definition-instance-initfunction dslot))&lt;br /&gt;            do (setf (slot-definition-instance-initfunction eslot)&lt;br /&gt;                     (slot-definition-instance-initfunction dslot)&lt;br /&gt;                     (slot-definition-initfunction eslot) nil&lt;br /&gt;                     (slot-definition-initform eslot) nil)&lt;br /&gt;               (return))&lt;br /&gt;    eslot))&lt;br /&gt;&lt;br /&gt;(defmethod direct-slot-definition-class&lt;br /&gt;    ((self weblocks-webapp-class) &amp;rest initargs)&lt;br /&gt;  "Use my special version when `:instance-initfunction' is present."&lt;br /&gt;  (if (getf initargs :instance-initfunction)&lt;br /&gt;      (find-class 'weblocks-webapp-direct-slot-definition)&lt;br /&gt;      (call-next-method)))&lt;br /&gt;&lt;br /&gt;(defmethod effective-slot-definition-class&lt;br /&gt;    ((self weblocks-webapp-class) &amp;rest initargs)&lt;br /&gt;  "As `instance-initfunction' is transferred over later, it isn't&lt;br /&gt;present in INITARGS, so assume any slot might need it."&lt;br /&gt;  (declare (ignore initargs))&lt;br /&gt;  (find-class 'weblocks-webapp-effective-slot-definition))&lt;br /&gt;&lt;br /&gt;(defun instance-initialize-unbound-slots (self slot-names)&lt;br /&gt;  "Do the magic promised by `weblocks-webapp-class'."&lt;br /&gt;  (let ((slots (class-slots (find-class self))))&lt;br /&gt;    (dolist (slot slot-names)&lt;br /&gt;      (and-let* (((not (slot-boundp self slot)))&lt;br /&gt;                 (slotd (find slot slots :key #'slot-definition-name))&lt;br /&gt;                 (initfunc (slot-definition-instance-initfunction slotd)))&lt;br /&gt;        (setf (slot-value self slot)&lt;br /&gt;              (funcall initfunc self))))))&lt;br /&gt;&lt;br /&gt;(defmethod shared-initialize :after ((self weblocks-webapp-class) slot-names&lt;br /&gt;                                     &amp;key &amp;allow-other-keys)&lt;br /&gt;  (declare (ignore slot-names))&lt;br /&gt;  (pushnew (class-name self) *registered-webapps*))&lt;br /&gt;&lt;br /&gt;(defclass weblocks-webapp ()&lt;br /&gt;  ((name :accessor weblocks-webapp-name :initarg :name :type string&lt;br /&gt;         :instance-initfunction (lambda (self)&lt;br /&gt;                                  (attributize-name (class-name (class-of self)))))&lt;br /&gt;   ;;snip uninteresting slots&lt;br /&gt;   (prefix :accessor weblocks-webapp-prefix :initarg :prefix :initform ""&lt;br /&gt;           :instance-initfunction&lt;br /&gt;           (lambda (self)&lt;br /&gt;             (concatenate 'string "/" (weblocks-webapp-name self)))&lt;br /&gt;           :type string&lt;br /&gt;           :documentation "The default dispatch will allow a webapp to be invoked &lt;br /&gt;              as a subtree of the URI space at this site.  This does not support &lt;br /&gt;              webapp dispatch on virtual hosts, browser types, etc.")&lt;br /&gt;   ;;snip more&lt;br /&gt;   (init-user-session :accessor weblocks-webapp-init-user-session :initarg :init-user-session&lt;br /&gt;                      :instance-initfunction&lt;br /&gt;                      (lambda (self)&lt;br /&gt;                        (find-symbol (symbol-name '#:init-user-session)&lt;br /&gt;                                     (symbol-package (class-name (class-of self)))))&lt;br /&gt;                      :type symbol&lt;br /&gt;                      :documentation "'init-user-session' must be defined by weblocks client in the&lt;br /&gt;                         same package as 'name'. This function will accept a single parameter - a &lt;br /&gt;                         composite widget at the root of the application. 'init-user-session' is &lt;br /&gt;                         responsible for adding initial widgets to this composite.")&lt;br /&gt;   (ignore-default-dependencies&lt;br /&gt;    :initform nil :initarg :ignore-default-dependencies&lt;br /&gt;    :documentation "Inhibit appending the default dependencies to&lt;br /&gt;the dependencies list.  By default 'defwebapp' adds the following resources:&lt;br /&gt;&lt;br /&gt;  Stylesheets: layout.css, main.css&lt;br /&gt;  Scripts: prototype.js, weblocks.js, scriptaculous.js")&lt;br /&gt;   (debug :accessor weblocks-webapp-debug :initarg :debug :initform nil))&lt;br /&gt;  (:metaclass weblocks-webapp-class)&lt;br /&gt;  (:documentation "snip"))&lt;br /&gt;&lt;br /&gt;(defmacro defwebapp (name &amp;rest initargs &amp;key subclasses slots (autostart t)&lt;br /&gt;                     &amp;allow-other-keys)&lt;br /&gt;  "snip"&lt;br /&gt;  (remf initargs :subclasses)&lt;br /&gt;  (remf initargs :slots)&lt;br /&gt;  (remf initargs :autostart)&lt;br /&gt;  `(prog1&lt;br /&gt;     (defclass ,name ,(append subclasses (list 'weblocks-webapp))&lt;br /&gt;       ,slots&lt;br /&gt;       (:default-initargs . ,initargs)&lt;br /&gt;       (:metaclass weblocks-webapp-class))&lt;br /&gt;    (when autostart&lt;br /&gt;      (pushnew ',name *autostarting-webapps*))))&lt;br /&gt;&lt;br /&gt;(defmethod shared-initialize ((self weblocks-webapp) slot-names&lt;br /&gt;                              &amp;key &amp;allow-other-keys)&lt;br /&gt;  "Use my `instance-initfunction' to initialize any slots that aren't&lt;br /&gt;yet bound."&lt;br /&gt;  (instance-initialize-unbound-slots self slot-names)&lt;br /&gt;  (macrolet ((do-slot ((bind-var &amp;optional (slot-name bind-var)) &amp;body forms)&lt;br /&gt;               `(when (member ',slot-name slot-names)&lt;br /&gt;                  (let ((,bind-var (slot-value self ',slot-name)))&lt;br /&gt;                    ,@forms))))&lt;br /&gt;    (do-slot (init-user-session)&lt;br /&gt;      (or init-user-session&lt;br /&gt;          (error (format nil "Cannot initialize application ~A because no~&lt;br /&gt;                              init-user-session function is found."&lt;br /&gt;                         (webapp-name self)))))&lt;br /&gt;    (when (member 'application-dependencies slot-names)&lt;br /&gt;      (setf (weblocks-webapp-application-dependencies self)&lt;br /&gt;            (build-local-dependencies&lt;br /&gt;             (append (and (not (slot-value self 'ignore-default-dependencies))&lt;br /&gt;                          '((:stylesheet "layout")&lt;br /&gt;                            (:stylesheet "main")&lt;br /&gt;                            (:stylesheet "dialog")&lt;br /&gt;                            (:script "prototype")&lt;br /&gt;                            (:script "scriptaculous")&lt;br /&gt;                            (:script "shortcut")&lt;br /&gt;                            (:script "weblocks")&lt;br /&gt;                            (:script "dialog")))&lt;br /&gt;                     dependencies))))&lt;br /&gt;    (do-slot (path public-app-path)&lt;br /&gt;      (setf (weblocks-webapp-public-app-path self)&lt;br /&gt;            (if (or (null path) (eq path :system-default))&lt;br /&gt;                nil&lt;br /&gt;                (compute-public-files-path &lt;br /&gt;                 (intern (package-name (class-name (class-of self))) :keyword)))))))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Even without the quoting problem, the fact that &lt;code&gt;prefix&lt;/code&gt;'s initfunction requires &lt;code&gt;name&lt;/code&gt;'s initfunction to be called first means that I would need to introduce dependency analysis and expression.  But Kenny has already solved this problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-3629976376818369607?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/3629976376818369607/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=3629976376818369607' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/3629976376818369607'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/3629976376818369607'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/08/metaclass-for-weblocks-webapps-try-2.html' title='A metaclass for weblocks-webapps, try 2'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1184549185438247550.post-2095930501927040512</id><published>2008-08-12T22:05:00.000-07:00</published><updated>2008-08-14T13:05:00.341-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='weblocks'/><title type='text'>A metaclass for weblocks-webapps</title><content type='html'>&lt;span style="display: block;" id="formatbar_Buttons"&gt;&lt;span class="on menu-top" style="display: block;" id="formatbar_FontSize" title="Font size" onmouseover="ButtonHoverOn(this);" onmouseout="ButtonHoverOff(this);" onmouseup="" onmousedown="CheckFormatting(event);toggleFontSizeMenu();ButtonMouseDown(this);"&gt;&lt;code&gt;defwebapp&lt;/code&gt; in &lt;code&gt;weblocks-dev&lt;/code&gt; currently does many mysterious things with its keyword arguments.  But these are the sorts of things that should be given directly to &lt;code&gt;make-instance&lt;/code&gt;, so that you can use your own &lt;code&gt;defclass&lt;/code&gt; forms and your own &lt;code&gt;make-instance&lt;/code&gt; calls to construct &lt;code&gt;weblocks-webapp&lt;/code&gt; instances in creative ways.&lt;br /&gt;&lt;br /&gt;I have a few goals for a rewrite of defwebapp:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;ol&gt;&lt;li&gt;Trivialize the mapping of the &lt;code&gt;defwebapp&lt;/code&gt; form to a &lt;code&gt;defclass&lt;/code&gt; form, putting most of the logic in initializing methods and possibly a metaclass (&lt;em&gt;ahem&lt;/em&gt;, &lt;dfn&gt;class metaobject class&lt;/dfn&gt;).&lt;/li&gt;&lt;li&gt;Don't hide the default slot values in &lt;code&gt;class-default-initargs&lt;/code&gt;.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;On reevaluation of &lt;code&gt;defwebapp&lt;/code&gt;, replace the slot values, at least in the trivial case where the hacker allowed the normal autostarter to instantiate the resulting &lt;code&gt;weblocks-webapp&lt;/code&gt; subclass.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;My first thought, abandoned before I could even start changing &lt;code&gt;defwebapp&lt;/code&gt; proper to use it, would have &lt;code&gt;defwebapp&lt;/code&gt; fill class-instance slots (not to be confused with instance slots with :allocation :class) instead of &lt;code&gt;default-initargs&lt;/code&gt;, then alter the initform and initfunction in &lt;code&gt;compute-effective-slot-definition&lt;/code&gt; to copy over those values to each new instance.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(defclass weblocks-webapp-class (standard-class)&lt;br /&gt;  ())&lt;br /&gt;&lt;br /&gt;(defmethod validate-superclass ((class weblocks-webapp-class) superclass)&lt;br /&gt;  (typep (class-name (class-of superclass))&lt;br /&gt;         '(member standard-class weblocks-webapp-class)))&lt;br /&gt;&lt;br /&gt;(defgeneric class-defaulting-slots (class)&lt;br /&gt;  (:method ((self weblocks-webapp-class))&lt;br /&gt;    (loop for class in (class-precedence-list self)&lt;br /&gt;       while (typep class 'weblocks-webapp-class)&lt;br /&gt;       append (class-direct-slots class)))&lt;br /&gt;  (:method ((self standard-class)) '()))&lt;br /&gt;&lt;br /&gt;(defmethod compute-effective-slot-definition&lt;br /&gt;    ((self weblocks-webapp-class) name direct-slot-defns)&lt;br /&gt;  "Provide a default initform (read the class's version of the slot)&lt;br /&gt;for those without initforms."&lt;br /&gt;  (let ((eslot (call-next-method))&lt;br /&gt;        (defaulting-slots (class-defaulting-slots self)))&lt;br /&gt;    (macrolet ((initfunc () (slot-definition-initfunction eslot)))&lt;br /&gt;      (when (and (find name defaulting-slots :key #'slot-definition-name)&lt;br /&gt;                 (not (initfunc)))&lt;br /&gt;        (setf (slot-definition-initform eslot)&lt;br /&gt;              `(slot-value (find-class ',(class-name self)) ',name)&lt;br /&gt;              (initfunc) (lambda () (slot-value self name)))))&lt;br /&gt;    eslot))&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1184549185438247550-2095930501927040512?l=failex.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://failex.blogspot.com/feeds/2095930501927040512/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=1184549185438247550&amp;postID=2095930501927040512' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/2095930501927040512'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1184549185438247550/posts/default/2095930501927040512'/><link rel='alternate' type='text/html' href='http://failex.blogspot.com/2008/08/metaclass-for-weblocks-webapps.html' title='A metaclass for weblocks-webapps'/><author><name>s11</name><uri>http://www.blogger.com/profile/13346366374080232972</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_dcUVyyrh2Ho/SKVA-2xJ79I/AAAAAAAAAAM/M-n39tyxfAc/S220/cane-logo.png'/></author><thr:total>0</thr:total></entry></feed>
