<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' version='2.0'><channel><atom:id>tag:blogger.com,1999:blog-6752176030157989585</atom:id><lastBuildDate>Tue, 10 Jun 2008 20:32:40 +0000</lastBuildDate><title>arbingersysWrit</title><description/><link>http://blog.arbingersys.com/</link><managingEditor>noreply@blogger.com (JA)</managingEditor><generator>Blogger</generator><openSearch:totalResults>21</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-7640196324346089249</guid><pubDate>Tue, 10 Jun 2008 16:59:00 +0000</pubDate><atom:updated>2008-06-10T14:32:40.972-06:00</atom:updated><title>Plake: Morph a File Based on Targets</title><description>&lt;a href="http://www.arbingersys.com/plake.html"&gt;&lt;img src="http://www.arbingersys.com/images/plake.png" alt="Plake" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This blog was always intended as a means to talk about projects I'm working on, as well as a way to voice my opinions to the world. So far, it's been largely skewed to the latter.&lt;br /&gt;&lt;br /&gt;So, I'd like to talk about a little build tool I've written called &lt;a href="http://www.arbingersys.com/plake.html"&gt;Plake&lt;/a&gt;. In a nutshell:&lt;br /&gt;&lt;blockquote&gt;Plake is a tool that allows you to maintain sections within a single file (usually, variations of the same code/markup/content) and then assemble variations of that file according to which target you call. It was inspired by Make, &lt;span style="font-weight: bold;"&gt;can be used in conjunction with Make&lt;/span&gt;, and is written in Perl, hence the name "Plake".&lt;/blockquote&gt;&lt;a href="http://www.gnu.org/software/make/"&gt;Make&lt;/a&gt; is a nearly ubiquitous build tool. It's used in countless software projects and is even the basis of the CPAN installer that's part of any Perl distribution --&lt;br /&gt;&lt;pre class="code"&gt;perl -MCPAN -e shell&lt;/pre&gt;Make does a really simple, powerful thing. It sets up rules (aka targets) that execute commands or invokes other targets, which is known as dependency chaining. From these rather simple concepts, you are able to orient a project for different variations, &lt;span style="font-weight: bold;"&gt;nicely denoted by a single target name&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;For example, you might type&lt;br /&gt;&lt;br /&gt;&lt;code&gt;make linux_build&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;to build a Linux platform binary, &lt;span style="font-weight: bold;"&gt;which may consist of &lt;span style="font-style: italic;"&gt;X&lt;/span&gt; number of steps that must execute in a certain order&lt;/span&gt;. Or, you might say&lt;br /&gt;&lt;br /&gt;&lt;code&gt;make apache_modperl&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;to include files from your web application specifically for an Apache/mod_perl web environment, along with the more general non-platform specific files.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;What Make can't do&lt;/span&gt;, however, is snag bits of code (or markup) from individual files for a given build. If you've ever looked at cross-platfrom C/C++ code, you've probably noticed the &lt;code&gt;#ifdef&lt;/code&gt; directives in the header files. These are used because sometimes there are small portions of code that need to be excluded when compiling for a certain platform or target, and keeping totally separate files to accommodate this is excessive.&lt;br /&gt;&lt;br /&gt;Plake allows you to define sections within a single file, and then "assemble" only the sections you want at build time. Here's an example.&lt;blockquote&gt;Let's say you have a C++ source file that gets built for the Windows platform and also for Linux. Keep the differences as sections in a single Plake file. Then when you assemble the .cpp file for the given platform, it only contains that platform's code.&lt;br /&gt;&lt;br /&gt;The following commands both produce "myfile.cpp" (but possibly at different folder locations) with only the code that each platform needs:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;plake file=myfile.plk target=windows_build&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;plake file=myfile.plk target=linux_build&lt;/code&gt;&lt;/blockquote&gt;Because Make is generally made up of shell commands, you would put the above commands under the appropriate Make target, and when you type &lt;span style="font-weight: bold;"&gt;make &lt;span style="font-style: italic;"&gt;target&lt;/span&gt;&lt;/span&gt;, Plake assembles the file &lt;span style="font-weight: bold;"&gt;with only the parts you need&lt;/span&gt;   prior to compiling it. The advantage you get, in the scenario above, is that reviewing code is easier, &lt;span style="font-weight: bold;"&gt;since after a specific target is assembled, only the code you need to see is there&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;There are some other uses for Plake, which I've discussed over at &lt;span style="font-style: italic;"&gt;Perlmonks&lt;/span&gt;, &lt;a href="http://www.perlmonks.org/?node_id=678202"&gt;here&lt;/a&gt; and &lt;a href="http://www.perlmonks.org/?node=670323"&gt;here&lt;/a&gt;. This is the short list:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Setting variations for builds&lt;/span&gt;. A convenience for me since I have yet to implement a more complex (i.e. overrides) configuration system, but still have to make subtle changes (usually, by hand-editing) for various implementations at various stages of development.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Assemble C/C++ files for specific platforms&lt;/span&gt;, in the stead of &lt;code&gt;#ifdef&lt;/code&gt;, etc. The resulting .c/.cpp/.h file would be assembled dynamically when the project was &lt;code&gt;make&lt;/code&gt;'d for a given platform, just prior to compilation. The code generated for that platform would be a bit simpler to review, since it only includes code that a person cares about in that build.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Remove experimental features&lt;/span&gt;, stubs, or extra debugging from code prior to generating distros, i.e.  "Cleanup".&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Branching, like what source control does&lt;/span&gt;. You could keep some client or "branch" specific features out of a specific build, but still maintain it in a single file.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Template variations&lt;/span&gt;, like letter writing. Instead of a single boiler plate template, you have targets like "standard_greeting", "enthusiastic_greeting", "familiar_greeting", etc.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Target-based programming for Perl&lt;/span&gt;. Sort of a side-effect, and one I don't see all the ramifications of, but you could use Plake to assemble code targets wholly or partially independent of each other by storing Perl code in a Plake file and doing an &lt;code&gt;eval&lt;/code&gt; against the assembled content for a given target. (&lt;span style="font-style: italic;"&gt;Just think -- you could keep your entire project of hundreds of modules and code files all in one single, massive text file! I can see everyone lining up now...&lt;/span&gt;)&lt;/li&gt;&lt;/ul&gt;        The last item above, &lt;span style="font-weight: bold;"&gt;target-based programming&lt;/span&gt;, is particularly interesting, I think, so I'll cover it briefly before finishing up. Plake was written in Perl, and uses the &lt;code&gt;eval()&lt;/code&gt; function to execute code on the fly. With a minimal change in the code, you could take the content you return from the &lt;span style="font-style: italic;"&gt;plk&lt;/span&gt; file and &lt;code&gt;eval()&lt;/code&gt; it, effectively creating a &lt;span style="font-weight: bold;"&gt;target-based interpreter&lt;/span&gt;. (I include a sample that does this in the download. See &lt;span style="font-style: italic;"&gt;plakeval.pl&lt;/span&gt;.)&lt;br /&gt;&lt;br /&gt;So, if you have a Plake file like&lt;pre class="code"&gt;!plake:&lt;br /&gt;&lt;br /&gt;target('helowrld', "helowrld", '');&lt;br /&gt;target('oneplus', "oneplus", '');&lt;br /&gt;target('both', "helowrld oneplus", '');&lt;br /&gt;&lt;br /&gt;!plake helowrld&lt;br /&gt;print "helowrld\n";&lt;br /&gt;&lt;br /&gt;!plake oneplus:&lt;br /&gt;# Add value to one&lt;br /&gt;print 1+3.14, "\n";&lt;/pre&gt;and you called it with &lt;span style="font-style: italic;"&gt;plakeval.pl&lt;/span&gt;, you would get the following:&lt;pre class="code"&gt;perl t\plakeval.pl file="t/plakeval.plk" target="helowrld"&lt;br /&gt;helowrld&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;perl t\plakeval.pl file="t/plakeval.plk" target="oneplus"&lt;br /&gt;4.14&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;perl t\plakeval.pl file="t/plakeval.plk" target="both"&lt;br /&gt;helowrld&lt;br /&gt;4.14&lt;/pre&gt;When the target &lt;span style="font-weight: bold; font-style: italic;"&gt;both&lt;/span&gt; is called, you can see that we are printing &lt;span style="font-style: italic;"&gt;helowrld&lt;/span&gt; and also adding &lt;span style="font-style: italic;"&gt;3.14+1&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;What this means is that you can stick things together in a file that perhaps make sense in a certain context, but wouldn't otherwise. Like I said, target-based programming is sort of a side-effect, and while I haven't really explored its value, I have a sense that some exists. At any rate, I find it interesting.&lt;br /&gt;&lt;br /&gt;But really, Plake was designed to let you keep variations of a file in a &lt;span style="font-weight: bold;"&gt;single actual file&lt;/span&gt; on the hard drive, and then omit or include parts of it based on a target. And it does that really well. I use it in my own projects and it saves me a considerable amount of error-prone work.</description><link>http://blog.arbingersys.com/2008/06/plake-morph-file-based-on-targets.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-7918156856940455556</guid><pubDate>Fri, 30 May 2008 15:00:00 +0000</pubDate><atom:updated>2008-05-30T10:19:10.216-06:00</atom:updated><title>I'm Trying To Quit... Commercial Software Pt. 2</title><description>&lt;img src="http://blog.arbingersys.com/images/tryquit-ico.png" alt="Trying to quit" align="left" /&gt;&lt;span style="font-style: italic;"&gt;In this experiment, FOSS is effectively graded on whether or not it can substitute all or most of my proprietary software needs, without me having to substantially change the way I use software. It's highly subjective, and human nature, like laziness and apathy, is very much a part of it, as you will see.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is the second installment of my personal Free Open Source Software experiment. Read the first installment &lt;a href="http://blog.arbingersys.com/2008/04/im-trying-to-quit-commercial-software.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Within a year of getting my new notebook, my wife's laptop gave up the ghost. It was a Dell Inspiron 8100, and frankly, we'd gotten our money's worth. I purchased a new laptop, a Gateway M6882, and we did the laptop shuffle again.&lt;br /&gt;&lt;br /&gt;The Gateway came  with Vista, but I wanted to run XP. I immediately discovered that XP was going to be difficult to manage. There was no floppy drive, XP didn't have the needed SATA controller, and there were only three hardware drivers available for XP on the Gateway site.&lt;br /&gt;&lt;br /&gt;After thinking about it, I realized that regardless of my feelings for Vista,  it's going to be inevitable, and I might as well get used to it. However, I'm resentful about my conclusion, and I'm sure I'm not the only one. &lt;span style="font-weight: bold;"&gt;As far as portents go, this is a bad one for Microsoft&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Ultimately, this ended up being a good thing. I'd been wanting an excuse to run Linux, and here it was. I decided to keep Vista, since I might need it, but repartition and dual-boot Linux.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Thus began the second phase of my "experiment"&lt;/span&gt;. I would see just how little I'd have to use Vista, if Linux were available instead.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;Linux&lt;/h4&gt;I started with Ubuntu 7.10 LTS, since it seemed like the distro with the most momentum. Installation was a breeze. I particularly like booting from the CD and getting to play around with the desktop before doing the install.&lt;br /&gt;&lt;br /&gt;After installation, however, I began to bump into oddities and frustrations.&lt;br /&gt;&lt;br /&gt;First, the M6882 is a widescreen with an optimal resolution of 1280x800. The Gnome desktop  &lt;span style="font-weight: bold;"&gt;used the entire screen&lt;/span&gt;, but the top and bottom system bars only went to a width of 1024 pixels. I tried to change the resolution using the system config tools, but nothing worked. I had to hit the forums, and after some time (longer than I would have preferred), finally found a solution that involved editing the &lt;span style="font-style: italic;"&gt;xorg.conf&lt;/span&gt; file. I still don't understand exactly what I changed, but it had something to do with &lt;span style="font-style: italic;"&gt;TV out&lt;/span&gt; settings.&lt;br /&gt;&lt;br /&gt;This gave a bad impression. The facts of life are that in the many &lt;span style="font-style: italic;"&gt;many &lt;/span&gt;installs I've done of Windows, I've never had to do this much work to get the system to the correct screen resolution.&lt;br /&gt;&lt;br /&gt;I still had one other hardware problem that was bothering me. The sound card didn't work. &lt;span style="font-weight: bold;"&gt;This took even longer to fix than the screen resolution, and was twice as painful&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I hit the forums again. I tried several suggestions with rather involved steps, with no success. I had a glimmer of hope when I found and downloaded the Linux drivers from the manufacturer's website. It was a source package, with some simple instructions for compiling and installing. But the install script first removed the existing sound libraries that the X server had been compiled against, using the fatal &lt;span style="font-style: italic;"&gt;rm&lt;/span&gt; command. Then, the build failed. Unaware of what had happened, I gave up and at some point rebooted. &lt;span style="font-weight: bold;"&gt;The desktop failed to load&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; the next time I tried to boot&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;The manufacturer's Linux driver package had  clobbered my non-working, but non-failing sound libraries without backing them up, or even checking that the build succeeded first. At this point I was pretty much hosed, and the easiest thing to do was to reinstall.&lt;br /&gt;&lt;br /&gt;I reinstalled, fixed the screen resolution problem again, and still didn't have sound. I finally found a solution, on some guy's blog. There was no compiling required, just a bunch of funky steps to get a "backports" package installed, after which I had to re-run some updates I'd already done. After that, my sound worked fine. But, like the screen, &lt;span style="font-weight: bold;"&gt;this was far too much work to have to do for something I consider basic and essential to an OS&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;The next hassle I had was that I changed my password, and suddenly was being prompted by the keyring manager every time I logged in.  Again, my only resource was the forums. I'll spare all the details of resolving this problem, but I'll say this: the problem with forums as the help is that you don't know who you can believe. I'm not saying anyone would attempt to purposely mislead you (although they might), but they can and often do get things wrong, communicate the solution poorly, or miss a detail that is essential to your particular system.&lt;br /&gt;&lt;br /&gt;In the keyring case, I followed one person's advice, which involved compiling from source, and began the descent down the dependency &lt;span style="font-style: italic;"&gt;Inferno&lt;/span&gt;, only to find out that all I really needed was to run the following simple command:&lt;br /&gt;&lt;br /&gt;rm ~/.gnome2/keyrings/login.keyring&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Using the community forums as the help system is a problematic solution at best&lt;/span&gt;. With no monetary incentive, you get only the best someone is willing to offer at the time, you have no verification of the expertise of your source, and no one is responsible. You may get an excellent answer, a partial answer, the wrong answer, or no answer.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;Never booting into Vista&lt;/h4&gt;After getting past the problems above, I began using Linux in earnest. As far as the basic things I need to do on a computer, e.g. programming, web surfing, email, FTP, document editing, spreadsheets, playing music, etc, Ubuntu was able to deliver.&lt;br /&gt;&lt;br /&gt;But here's what I still need Vista for:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;DVD playback&lt;/span&gt;. I couldn't play a DVD of &lt;span style="font-style: italic;"&gt;24&lt;/span&gt; with Totem. I had installed GStreamer &lt;span style="font-style: italic;"&gt;the ugly&lt;/span&gt; and also Mplayer. No dice. Mplayer looked like:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/mplayer24.png" alt="mplayer fails" /&gt;&lt;br /&gt;&lt;br /&gt;I also tried VLC. It got some images to the screen, errored out, and froze.&lt;br /&gt;&lt;br /&gt;I didn't give up that easily. Next I installed Totem with the xine backend. When I played the DVD this time, I got the FBI warnings, but it complained about encryption when it came to the video, and also failed.&lt;br /&gt;&lt;br /&gt;In Vista all I have is the Windows Media Center, which sucked in XP. It's  been improved, and other than the audio being slightly lower than I would have liked (perhaps a hardware issue), I can play DVDs without a headache.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Photoshop&lt;/span&gt;. I know I could learn Gimp, but I already know Photoshop, and know it well. It had a steep learning curve, and has all the capabilities I need and then some, so switching doesn't appeal to me. I'd much rather just boot into the system where this app runs and use it there.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Doom9.net&lt;/span&gt;. I use a lot of the multimedia tools (e.g. BeSplit, MeGUI) available from this site. Most of these interfaces, while freeware, run on Windows.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Netflix&lt;/span&gt;. Sorry, but they have that &lt;span style="font-style: italic;"&gt;Watch Instantly&lt;/span&gt; feature, which will not only just run on Windows, but also will only run on &lt;span style="font-style: italic;"&gt;X&lt;/span&gt; number of installs of Windows. I don't like it, but like Vista, it's just the way things are.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;Rooting for Linux&lt;/h4&gt;While I'm growing increasingly fond of Linux, &lt;span style="font-weight: bold;"&gt;and certainly rooting for it&lt;/span&gt;, it's got a ways to go. Hardware will be a weak point for some time to come. This isn't the fault of Linux, but instead the fault of economics. Money is the big &lt;span style="font-style: italic;"&gt;incentivizer&lt;/span&gt;, and the OS that can bring in the most money will always get priority. My experience with the manufacturer's sound driver installation is  a clear example of this.&lt;br /&gt;&lt;br /&gt;Microsoft may not win any medals for its ideals, but sound drivers usually install without the user having to jump through hoops or inadvertently clobbering their system, and I can play most DVDs by just slapping it in the drive.&lt;br /&gt;&lt;br /&gt;Linux also suffers in the support department. Again, this is because the model of Linux is essentially based on altruism. Really, it's an amazing feat that Linux works as well as it does, has the support it has, and is as advanced as it has become. I'm rapidly turning into a fan, and have optimism for the future.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Watch for my next installment, in which I begin to play around with Gimp, surprisingly, because of laziness, switch to openSUSE and am pretty happy with it, and have some trouble connecting to WIFI where Vista does not.&lt;/span&gt;</description><link>http://blog.arbingersys.com/2008/05/im-trying-to-quit-commercial-software.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-5317147973656011559</guid><pubDate>Mon, 19 May 2008 17:41:00 +0000</pubDate><atom:updated>2008-05-20T15:28:58.692-06:00</atom:updated><title>What's a Wiki?</title><description>&lt;img src="http://blog.arbingersys.com/images/wiki.gif" alt="Wiki" align="left" /&gt;Not a particularly hard question, and most people (whose primary exposure to the term is through Wikipedia), will pipe up: It's a website that lets anyone edit and make changes. And they'd be right, but there's more to it.&lt;br /&gt;&lt;br /&gt;A Wiki was originally designed around the philosophy of &lt;span style="font-weight: bold;"&gt;incompleteness&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;interaction&lt;/span&gt;. The concept, created by &lt;a href="http://en.wikipedia.org/wiki/Ward_Cunningham"&gt;Ward Cunningham&lt;/a&gt; was intended to foster &lt;a style="font-style: italic;" href="http://c2.com/cgi/wiki?ContentCreationWiki"&gt;collaboration [which] creates and develops new ideas&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;But it's extremely difficult to know just exactly how your idea will be adapted and ultimately play out when presented to the world.&lt;br /&gt;&lt;br /&gt;Wikipedia came along and decided to&lt;span style="font-weight: bold;"&gt; classify content&lt;/span&gt; using a Wiki, becoming the world's first collaborative encyclopedia. And it &lt;span style="font-style: italic;"&gt;has&lt;/span&gt; stayed true to the ideas above. It's both incomplete and promotes interaction. But it doesn't use Wiki technology to &lt;span style="font-weight: bold;"&gt;develop new ideas&lt;/span&gt;. That's not why it was created.&lt;br /&gt;&lt;br /&gt;Is Wikipedia any less a &lt;span style="font-style: italic;"&gt;Wiki&lt;/span&gt;, then? Not really. While the original intention of Wiki may have been to foster the creation of new ideas, the functionality it provides to do that (i.e. ease-of-use, simple markup, natural collaboration) lends itself to other goals as well.&lt;br /&gt;&lt;br /&gt;So then, a Wiki may be:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Content Creation Wiki&lt;/span&gt;&lt;br /&gt;The original intention of a Wiki -- to collaborate and create new ideas. From &lt;a href="http://c2.com/cgi/wiki?ContentCreationWiki"&gt;c2.com&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;Treat a page here as a half-finished piece of sidewalk art. Don't scuff it up. Don't rub it out. Don't write messages on it like "finish this you bum or I will scuff it" or "I disagree" or "me too".&lt;br /&gt;&lt;br /&gt;Instead, see if you can head it toward completeness. If you can't do that now, leave it be. Maybe one day you will think of something to add. Or perhaps another will. We rely on each other to help new things come into being, like ants building nests.&lt;/blockquote&gt;&lt;span style="font-weight: bold;"&gt;Content Classification Wiki&lt;/span&gt;&lt;br /&gt;Sites like Wikipedia, which classify existing knowledge to make it usable. These sites tend to be larger, edited more stringently, and try to present knowledge "authoritatively". [&lt;a href="http://c2.com/cgi/wiki?ContentClassificationWiki"&gt;link&lt;/a&gt;]&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Knowledge Base Wiki&lt;/span&gt;&lt;br /&gt;I'm adding this one, since I'm increasingly seeing Wikis used this way. This type tends to be specific to organizations, and are either used to accumulate and distribute information about a specific product or service, or used internally to collaborate and share information, e.g. company policies, inter-departmental information, etc.&lt;br /&gt;&lt;br /&gt;These Wiki "types" really only differ in their intention and audience. They all foster collaboration, are simple to use, and are generally ongoing, with no real &lt;span style="font-style: italic;"&gt;finalization &lt;/span&gt;date.</description><link>http://blog.arbingersys.com/2008/05/whats-wiki.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-4440879033941300531</guid><pubDate>Tue, 13 May 2008 16:58:00 +0000</pubDate><atom:updated>2008-05-15T13:45:28.613-06:00</atom:updated><title>Re: Why People Are Passionate About Perl</title><description>&lt;img src="http://blog.arbingersys.com/images/perlonion.png" alt="Perl" align="left" /&gt; Here's my response to &lt;a href="http://use.perl.org/%7Ebrian_d_foy/"&gt;brian_d_foy&lt;/a&gt;'s &lt;a href="http://use.perl.org/%7Ebrian_d_foy/journal/36356" style="color: rgb(255, 102, 34);"&gt;&lt;b&gt;People Passionate About Perl&lt;/b&gt; meme&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;I first starting using Perl to...&lt;br /&gt;&lt;/b&gt;I began looking into Perl in the 90s -- when it was suffering less from perception issues -- as an alternative web development platform to ASP. ASP presented a low bar, and I was making web front-ends to databases in a very short time. Then, after satisfying initial needs, more demands began to be made on our web applications, and ASP's low bar began to be an inhibitor.&lt;br /&gt;&lt;br /&gt;I reviewed Perl as an alternative, and (I'll be honest) after getting past the syntax, began to understand the power I was toying with. Then, I discovered &lt;a href="http://www.cpan.org/"&gt;CPAN&lt;/a&gt;. ASP never looked quite the same after that.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;I kept using Perl because...&lt;br /&gt;&lt;/b&gt;It's never given me a reason not to. Sorry, but I'm not loyal for loyalty's sake. If a tool like Perl can't make my life easier than tool &lt;span style="font-style: italic;"&gt;X&lt;/span&gt;, then it's time to investigate tool &lt;span style="font-style: italic;"&gt;X&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;But Perl hasn't failed on this account. It's proven to be highly adaptable, and the energy of its community has fit it to new paradigms readily. For instance, is there a &lt;span style="font-weight: bold;"&gt;Ruby on Rails&lt;/span&gt; for Perl? Try &lt;a href="http://www.catalystframework.org/"&gt;Catalyst&lt;/a&gt;, or the newer &lt;a href="http://jifty.org/view/HomePage"&gt;Jifty&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As for pre-packaged functionality, I don't think there's a language that can compete. CPAN continues to &lt;a href="http://www.perlmonks.org/?node_id=659849"&gt;grow and grow&lt;/a&gt;. In fact, if you want to contribute, the difficulty now is thinking up something that hasn't already been done. &lt;a href="http://blog.arbingersys.com/2008/03/reverse-callback-templating.html"&gt;Try templates, for example&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This means one thing: If I need a tool to get something done, Perl is the easiest choice. It's powerful, flexible, and continues to edge into functionality that I haven't even begun to think about.&lt;br /&gt;&lt;br /&gt;Oh yeah, and it also provides industry leading regular expressions via operator, for the absolutely most convenient and shortest possible way of using this very important technology.&lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;I can't stop thinking about Perl...&lt;br /&gt;&lt;/b&gt;Actually, I can stop thinking about Perl, and frequently do. That's because there are &lt;a href="http://blog.arbingersys.com/2007/12/mr-spolsky-and-work-is-life-principle.html"&gt;other things in my life&lt;/a&gt; besides Perl. However, &lt;span style="font-weight: bold;"&gt;I think in Perl&lt;/span&gt; when I think about crafting software, or anything abstract and computational. Its natural language model makes this easy.&lt;br /&gt;&lt;br /&gt;And since web, Internet, and database are the spaces for the majority of my software ideas, thinking in Perl is a huge benefit for me, because so many others are thinking in Perl for the same spaces, answering questions I haven't thought to ask yet (CPAN again).&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;I'm still using Perl because...&lt;br /&gt;&lt;/b&gt;This is mostly covered above. But here's one more.&lt;br /&gt;&lt;br /&gt;Line count. I use Perl day-to-day to handle any number of tasks, of any size and importance. Perl itself reduces line count just in the power of its syntax. I'm not talking about merely writing obfuscated code. I'm talking out the &lt;a href="http://blog.arbingersys.com/2007/12/test1.html"&gt;power inherent in the language itself&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;And now, back to CPAN.&lt;br /&gt;&lt;br /&gt;Recently at work we needed to parse a handful of Excel spreadsheets that were formatted &lt;span style="font-style: italic;"&gt;more or less&lt;/span&gt; the same. I handed this job off to a contractor who works for me. He created a C# project, and then left for the day. He wasn't able to come back the next day, so I took the project over. He had barely gotten started, but he already had five or six files involved, and &lt;span style="font-weight: bold;"&gt;a couple hundred lines of code&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I immediately thought we should be doing it in Perl. This was a one-off project, so why do a whole Visual Studio project? I Googled around and found &lt;a href="http://www.ibm.com/developerworks/linux/library/l-pexcel/"&gt;this tutorial&lt;/a&gt;. I installed the modules from CPAN, adapted the samples to my needs, and about an hour and &lt;span style="font-weight: bold;"&gt;80 lines of code&lt;/span&gt; later, I had the spreadsheets munged into SQL and ready to go.&lt;br /&gt;&lt;br /&gt;Sorry, but I'll take the shorter route every time, if I can.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;I get other people to use Perl by...&lt;br /&gt;&lt;/b&gt;Well, I blog. Not exclusively about Perl, and not even explicitly to advocate Perl, but it &lt;span style="font-style: italic;"&gt;is &lt;/span&gt;about Perl, because, like I said before, I think in Perl. It's going to leak out.&lt;br /&gt;&lt;br /&gt;I have pointed people to Perl when it best suits their needs. A guy I work with wants to learn programming, and was looking at Python. I asked why he was interested in programming, and he admitted he just wanted to write a few scripts to download content off a website. I nodded and said, "You should use Perl."&lt;br /&gt;&lt;br /&gt;Python may have this covered as well, but I showed him how in one line of code (via LWP::Simple) I could grab the text of a website. I also pointed him to all the modules -- you guessed it, available on CPAN -- that can &lt;a href="http://search.cpan.org/dist/HTML-Parser/Parser.pm"&gt;rip apart HTML&lt;/a&gt; and extract just the things you need.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;I also program in&lt;nobr&gt; &lt;wbr&gt;&lt;/nobr&gt;... and&lt;nobr&gt; &lt;wbr&gt;&lt;/nobr&gt;..., but I like Perl better since...&lt;br /&gt;&lt;/b&gt;Although I know several other languages, I program primarily in C# and Perl.&lt;br /&gt;&lt;br /&gt;Both languages work well for the domains in which I use them. I use C# to write Windows specific applications. C# just has better hooks into the system, with less weirdness.&lt;br /&gt;&lt;br /&gt;I use Perl for pretty much everything else. And where they cross domains, i.e. web development  (ASP.NET), I prefer Perl, because (1) it has more pluggable functionality, and for free, and (2) has a shorter development-to-production time. This is partly due to my proficiency in Perl, but also because there is less setup involved in new projects, and less OO wrapping.&lt;br /&gt;&lt;br /&gt;If it's a simple app, I use a minimal amount of Perl. If it's more complex, I use the frameworks available, like CGI::Application and templates. With ASP.NET, you're pretty much bound to the framework -- with all its complexity -- even for simple projects.</description><link>http://blog.arbingersys.com/2008/05/re-why-people-are-passionate-about-perl.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-8873092577502129946</guid><pubDate>Fri, 09 May 2008 02:13:00 +0000</pubDate><atom:updated>2008-05-13T10:53:19.908-06:00</atom:updated><title>Google Docs Finally Matter To Me</title><description>&lt;img src="http://www.google.com/google-d-s/images/docslogo.gif" alt="Google Docs" align="left" /&gt;To be honest, online documents never really were a big sell for me. Frankly, it's an application space that's pretty boring, and ultimately, you sacrifice functionality. What functionality do you get with Google Docs that an "offline" word processor can't provide in spades? Well, just this: Your documents can be accessed and edited from anywhere &lt;span style="font-weight: bold;"&gt;[that you have a high-speed Internet connection]&lt;/span&gt;. That last part is mine, since you won't see that in any marketing phrase for an online word processor. But it's significant.&lt;br /&gt;&lt;br /&gt;It may surprise you, but until recently I only had a dial-up connection at home. Because I live in a rural area outside the city, the only option for me was satellite, which I didn't find appealing due to the cost/performance ratio. At work, however, we have DS3, so I had no real Internet deficiency.&lt;br /&gt;&lt;br /&gt;My work also provided me with a 4GB thumb drive -- and lanyard! -- so I had an extended sneakernet, and anything that was too painful to download from home (almost everything), I would download at work. Also, document synchronization between work and home was answered by simply keeping documents on the thumb drive. This guaranteed that wherever the location, I always had the up-to-date revision.&lt;br /&gt;&lt;br /&gt;So because I had &lt;span style="font-weight: bold;"&gt;only one&lt;/span&gt; high-speed Internet connection, the single advantage Google Docs could provide over &lt;span style="font-style: italic;"&gt;Word&lt;/span&gt; or &lt;span style="font-style: italic;"&gt;OpenOffice Writer&lt;/span&gt; didn't exist.&lt;br /&gt;&lt;br /&gt;And let's be honest. The interface, while well done for a web app, doesn't compare to a locally running application written for your platform. What is Google Docs, really? It's a &lt;span style="font-weight: bold;"&gt;word processor running under a web browser&lt;/span&gt;. What is Microsoft Word? &lt;span style="font-weight: bold;"&gt;It's just a word processor&lt;/span&gt;. Which program do you think is going to be better suited to the task of word processing, and capable of offering more power? The one that gets to focus its logic on word processing, or the one that also has to be a web browser? &lt;span style="font-weight: bold;"&gt;Google Docs only has an advantage as a web platform&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;When DSL became available in my area, the game changed. I now have a fast, always on connection at the two places where I do most of my work: at home and at my office. My sneakernet has pretty much ceased to exist. If I need to transfer anything I simply use FTP, email, or VPN.&lt;br /&gt;&lt;br /&gt;But all of the above methods are kind of clunky for synchronizing files. Our VPN only works with Microsoft clients, unfortunately, and I use Linux quite often when I'm home. FTP would work the best, but there are a lot of extra steps (or extra setup) when compared to just plugging in a thumb drive and clicking on the file you want to edit.&lt;br /&gt;&lt;br /&gt;Many of the documents I work on are spec documents for software projects. I don't really need anything more than just basic word processing functionality: headings, emphasis, bulleted lists, tables. Google Docs does all this pretty well.&lt;br /&gt;&lt;br /&gt;I recently started to develop a spec for a Perl library, and made this my first real try of Google Docs &lt;span&gt;now that I have &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;more than one &lt;/span&gt;&lt;span&gt;reliable high-speed Internet connection&lt;/span&gt;. I started writing the spec about a half hour before I left work one day. On the way home, I had some new ideas, and wanted to add them while they were still fresh. &lt;span style="font-weight: bold;"&gt;This was the moment Google Docs finally began to matter to me&lt;/span&gt;. It was the easiest synchronized document edit I had made to date. I just logged in to my laptop when I got home, made my additions, and saved.&lt;br /&gt;&lt;br /&gt;Since then, any document that I'll edit from more than one location goes directly to Google Docs.&lt;br /&gt;&lt;br /&gt;Google Docs, or any online word processor, only has real value as a web platform. And a web platform only has value where there is a sufficiently high-speed Internet connection available. As &lt;span style="font-style: italic;"&gt;that&lt;/span&gt; becomes more and more common, online word processing will begin to matter to more people.</description><link>http://blog.arbingersys.com/2008/05/google-docs-finally-matter-to-me.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-4985019666857550396</guid><pubDate>Tue, 06 May 2008 17:13:00 +0000</pubDate><atom:updated>2008-05-08T08:42:57.092-06:00</atom:updated><title>Keeping A Digital Diary On A Trēo</title><description>About a year and a half ago, my wife and I took a trip to Mazatlan, Mexico. In my backpack, along with my laptop, I had stowed a &lt;span style="font-weight: bold;"&gt;Siemens SX56 PDA&lt;/span&gt;. This was the first time we had ever visited Mexico, so I decided I wanted to keep a day-by-day account.&lt;br /&gt;&lt;br /&gt;The SX56, like most PDAs, has a microphone. I changed the recording settings to a low frequency -- 8 kHz 8 bit stereo (still good enough for voice recording) -- and recorded the events of our Mexico vacation. &lt;span style="font-weight: bold;"&gt;Since then I've maintained a personal audio diary on my PDA&lt;/span&gt;, trying to put something in for each day without being bogged down by boring minutia. Of which, sadly, there is enough.&lt;br /&gt;&lt;br /&gt;The SX56 has only 32MB of storage, and part of that is used for system files. I found myself filling it up and having to dump to my laptop far too frequently. It turns out this was hardly an insurmountable problem.&lt;br /&gt;&lt;br /&gt;I bought a Sandisk 256MB flash card, and switched the voice recorder to save to it automatically. &lt;span style="font-weight: bold;"&gt;This solved two annoyances at once&lt;/span&gt;: I didn't have to dump the voice recording files as frequently to my laptop, and I no longer needed to sync via cable, which is always a pain. I could just plug the flash card into my laptop move the files off with Explorer.&lt;br /&gt;&lt;br /&gt;For a long time, this was a very workable solution. It still would be, in fact, but by a small stroke of fortune, I was able to upgrade to a Trēo. Here's a picture:&lt;br /&gt;&lt;div style="width: 402px; font-size: 85%; margin-left: 2em;"&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/treo.jpg" alt="Treo Digital Diary" /&gt;&lt;br /&gt;The journey of a digital photo: This picture was taken on my wife's Nokia cell phone, emailed from there to my Gmail account, download to my laptop, cropped using Gimp, and then FTP'd to my website.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Not very long ago, a guy I work with brought in a box of about thirty Trēo phones like the one I snagged above. He had gotten them from his old employer who no longer needed them since they had just gotten a new budget. &lt;span style="font-style: italic;"&gt;(Aren't they the lucky ones...)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;After playing with one for a while, I decided I'd take him up on his offer of having one for free. It had all the features of the SX56, and then some. Like twice the storage space on the phone itself. And a real keyboard and navigation button, instead of purely on-screen controls. And of course, my 256MB flash card plugs right in.&lt;br /&gt;&lt;br /&gt;Also, it has a camera. This didn't seem that significant at first, but we were recently on a hike, and I was able to take a picture of my wife and daughter, and save it on the flash card along with my diary's audio files. So now my diary has taken on a whole new dimension: &lt;span style="font-weight: bold;"&gt;It will include real as well as audio imagery&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;This isn't the first time I've tried to keep a diary. A couple times in the past I was inspired to do it, and each time, it fizzled. The reason my PDA diary hasn't, I think, is because it lends itself so well to the task. It's portable, by which I mean it has a battery and fits in your pocket, and it requires little effort -- just click and talk about the day's events.&lt;br /&gt;&lt;br /&gt;Really, the hardest thing at this point is making sure that you only record things that are actually interesting. You don't want to bore your future self, after all.</description><link>http://blog.arbingersys.com/2008/05/keeping-digital-diary-on-tro.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-3672824600049102628</guid><pubDate>Sat, 03 May 2008 17:15:00 +0000</pubDate><atom:updated>2008-05-03T12:18:35.806-06:00</atom:updated><title>ScratchPad MX - Save Stuff For Later</title><description>&lt;img src="http://www.arbingersys.com/images/scratchpadico.png" align="left" /&gt; For the longest time, I've had a shortcut on my Start menu that launched a text document called &lt;span style="font-style: italic;"&gt;scratch.txt&lt;/span&gt;. This way, with a few clicks, I could save something I might need later, or if I needed a place to temporarily stick some clipboard stuff, I could use it for that. But the problem was, I didn't need a full-blown editor (or even half-blown, like Notepad) to do this. I wanted something that was editor-&lt;span style="font-style: italic;"&gt;like&lt;/span&gt;, but &lt;span style="font-weight: bold;"&gt;stripped down to and streamlined for just the functions I needed&lt;/span&gt;. A real scratch pad, not an editor acting like one.&lt;br /&gt;&lt;br /&gt;Specifically, I wanted the following:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Key combo launch&lt;/span&gt;, so it would be available at a moment's notice.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;A command to create a section&lt;/span&gt; (insert a divider of some kind) to keep stuff separate from each other.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Save and close with a single command&lt;/span&gt;, for when I need to save something quickly until I have time to think about it.&lt;/li&gt;&lt;li&gt;After a while, you'll accumulate an eclectic mix of stuff, so a way to &lt;span style="font-weight: bold;"&gt;jump from section to section&lt;/span&gt;. (Also, a way to search.)&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Close without saving&lt;/span&gt;, for when I'm just using it to store something temporarily, like when I'm &lt;span style="font-style: italic;"&gt;clipboarding&lt;/span&gt; heavily.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;So that's what I came up with. I call it &lt;span style="font-weight: bold;"&gt;ScratchPad MX&lt;/span&gt;. Here's what it looks like:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.arbingersys.com/images/scratchpadmx.png" alt="ScratchPad MX" /&gt;&lt;br /&gt;&lt;br /&gt;Along the top, you can see the five commands available to the program. Pretty self-explanatory. &lt;span style="font-weight: bold;"&gt;Ctrl+f&lt;/span&gt; will jump through each section, which is defined by the line of "=" characters. If you type some text and highlight it, &lt;span style="font-weight: bold;"&gt;Ctrl+f&lt;/span&gt; will search down through the document for that text. (So actually, there are 5.5 commands.)&lt;br /&gt;&lt;br /&gt;You can &lt;span style="font-weight: bold;"&gt;download &lt;/span&gt;the installer &lt;span style="text-decoration: underline;"&gt;&lt;/span&gt;&lt;a href="http://www.arbingersys.com/dnlds/scratchpad_mx.exe"&gt;here&lt;/a&gt;. It will automatically install the program, and optionally, you can install a hotkey, &lt;span style="font-weight: bold;"&gt;WinKey+Space&lt;/span&gt; that you can use to bring ScratchPad MX up instantly.&lt;br /&gt;&lt;br /&gt;ScratchPad MX is &lt;span style="font-weight: bold;"&gt;completely free&lt;/span&gt;. Also, this is version 0.01 (&lt;span style="font-style: italic;"&gt;barely better than beta&lt;/span&gt;), so if there are problems or you think a feature might be useful, either leave me a comment, or email me at one of the addresses on my &lt;a href="http://www.arbingersys.com/"&gt;home page&lt;/a&gt;.</description><link>http://blog.arbingersys.com/2008/05/scratchpad-mx-save-stuff-for-later.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-4077504584931534173</guid><pubDate>Wed, 30 Apr 2008 15:00:00 +0000</pubDate><atom:updated>2008-05-05T21:59:29.044-06:00</atom:updated><title>Google App Engine: [A Better] Many-to-many JOIN</title><description>&lt;img src="http://blog.arbingersys.com/images/gae.jpg" alt="GAE" align="left" /&gt;&lt;span style="background-color: rgb(255, 255, 187);"&gt;(This is a follow-up to my original post &lt;a href="http://blog.arbingersys.com/2008/04/google-app-engine-many-to-many-join.html"&gt;GAE: Many-to-many JOIN&lt;/a&gt;. It probably wouldn't hurt to read that first, since this post sort of assumes you have.)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;After getting some feedback on my original post, a simpler, more &lt;span style="font-style: italic;"&gt;SQL analogous&lt;/span&gt; way to obtain the many-to-many behavior was pointed out to me.&lt;br /&gt;&lt;br /&gt;I've created another sample (download it &lt;a href="http://www.arbingersys.com/dnlds/gaemany2.tar.gz"&gt;here&lt;/a&gt;), and will go over it below. Afterwards, I'll talk about why you &lt;span style="font-weight: bold;"&gt;shouldn't&lt;/span&gt; model your data this way, and instead should &lt;span style="font-weight: bold;"&gt;denormalize your data&lt;/span&gt; for optimization in the Datastore.&lt;br /&gt;&lt;br /&gt;Here are the new data Models. (The full code listing is &lt;a href="http://blog.arbingersys.com/gaemany2_example.txt"&gt;here&lt;/a&gt;.)&lt;pre class="code"&gt;class Libraries(db.Model):&lt;br /&gt;   notes = db.StringProperty()&lt;br /&gt;&lt;br /&gt;class Books(db.Model):&lt;br /&gt;   notes = db.StringProperty()&lt;br /&gt;&lt;br /&gt;class Library(db.Model):&lt;br /&gt;   name = db.StringProperty()&lt;br /&gt;   address = db.StringProperty()&lt;br /&gt;   city = db.StringProperty()&lt;br /&gt;   libscol = db.ReferenceProperty(Libraries,&lt;br /&gt;       collection_name='libscol')&lt;br /&gt;&lt;br /&gt;   def books(self):&lt;br /&gt;       return (x.book for x in self.librarybook_set)&lt;br /&gt;&lt;br /&gt;class Book(db.Model):&lt;br /&gt;   title = db.StringProperty()&lt;br /&gt;   author = db.StringProperty()&lt;br /&gt;   bookscol = db.ReferenceProperty(Books,&lt;br /&gt;       collection_name='bookscol')&lt;br /&gt;&lt;br /&gt;   def libraries(self):&lt;br /&gt;       return (x.library for x in self.librarybook_set)&lt;br /&gt;&lt;br /&gt;class LibraryBook(db.Model):&lt;br /&gt;   library = db.ReferenceProperty(Library)&lt;br /&gt;   book = db.ReferenceProperty(Book)&lt;/pre&gt;I still have the &lt;code&gt;Books&lt;/code&gt; and &lt;code&gt;Libraries&lt;/code&gt; models, as you can see. These are needed to collect the &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; entities so I can easily iterate over them and output. The &lt;code&gt;Book&lt;/code&gt; model contains a reference to &lt;code&gt;Books&lt;/code&gt;, via &lt;code&gt;Book.bookscol&lt;/code&gt;, and &lt;code&gt;Library&lt;/code&gt; to &lt;code&gt;Libraries&lt;/code&gt;, via &lt;code&gt;Library.libscol&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;LibraryBook&lt;/code&gt; model just contains references to the &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; models. This creates our "join". After we add libraries and books to the Datastore, we will link them to each other using &lt;code&gt;LibraryBook&lt;/code&gt; entities.&lt;br /&gt;&lt;br /&gt;When the page loads, we first create and store our data entities.&lt;pre class="code"&gt;# Library collection&lt;br /&gt;libs = Libraries()&lt;br /&gt;libs.put()&lt;br /&gt;&lt;br /&gt;# Book collection&lt;br /&gt;books = Books()&lt;br /&gt;books.put()&lt;br /&gt;&lt;br /&gt;# Setup libraries&lt;br /&gt;lib1 = Library(name='lib1', address='street a',&lt;br /&gt;   city='city1', libscol=libs)&lt;br /&gt;lib2 = Library(name='lib2', address='street b',&lt;br /&gt;   city='city2', libscol=libs)&lt;br /&gt;lib1.put()&lt;br /&gt;lib2.put()&lt;br /&gt;&lt;br /&gt;book1 = Book(title='book1', author='author one',&lt;br /&gt;   bookscol=books)&lt;br /&gt;book1.put()&lt;br /&gt;book2 = Book(title='book2', author='author one',&lt;br /&gt;   bookscol=books)&lt;br /&gt;book2.put()&lt;br /&gt;book3 = Book(title='book1', author='author two',&lt;br /&gt;   bookscol=books)&lt;br /&gt;book3.put()&lt;br /&gt;book4 = Book(title='book2', author='author two',&lt;br /&gt;   bookscol=books)&lt;br /&gt;book4.put()&lt;br /&gt;book5 = Book(title='book3', author='author two',&lt;br /&gt;   bookscol=books)&lt;br /&gt;book5.put()&lt;br /&gt;&lt;br /&gt;l1 = LibraryBook(library=lib1, book=book1)&lt;br /&gt;l2 = LibraryBook(library=lib1, book=book2)&lt;br /&gt;l3 = LibraryBook(library=lib1, book=book4)&lt;br /&gt;l4 = LibraryBook(library=lib2, book=book4)&lt;br /&gt;l5 = LibraryBook(library=lib2, book=book5)&lt;br /&gt;l6 = LibraryBook(library=lib2, book=book3)&lt;br /&gt;l7 = LibraryBook(library=lib2, book=book1)&lt;br /&gt;l1.put()&lt;br /&gt;l2.put()&lt;br /&gt;l3.put()&lt;br /&gt;l4.put()&lt;br /&gt;l5.put()&lt;br /&gt;l6.put()&lt;br /&gt;l7.put()&lt;/pre&gt;First, we create our &lt;code&gt;Libraries&lt;/code&gt; and &lt;code&gt;Books&lt;/code&gt; entities, &lt;code&gt;libs&lt;/code&gt; and &lt;code&gt;books&lt;/code&gt;. These will be passed into each &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; entity we create.&lt;br /&gt;&lt;br /&gt;After we create our books and libraries, we generate a lot of &lt;code&gt;LibraryBook&lt;/code&gt; entities, assigning a library and a book to each one. Each &lt;code&gt;LibraryBook&lt;/code&gt; entity now links one library with one book. As you may have noticed, some books are assigned to both libraries, some are not.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;Library&lt;/code&gt; contains a method called &lt;code&gt;books()&lt;/code&gt;. It returns every book in the &lt;code&gt;librarybook_set&lt;/code&gt; as an iterable data structure. Because &lt;code&gt;LibraryBook&lt;/code&gt; holds a reference to &lt;code&gt;Library&lt;/code&gt;, any &lt;code&gt;Library&lt;/code&gt; entity (say, &lt;code&gt;lib1&lt;/code&gt;), is given a back-reference to the collection of &lt;code&gt;LibraryBook&lt;/code&gt; entities. If you do not define a &lt;code&gt;collection_name&lt;/code&gt;, GAE automatically creates one by appending "_set" to the model name. This is where &lt;code&gt;librarybook_set&lt;/code&gt; came from, in case you were wondering.&lt;br /&gt;&lt;br /&gt;Given a library entity like &lt;code&gt;lib1&lt;/code&gt;, the &lt;code&gt;books()&lt;/code&gt; method allows us to easily return all the books at that library by simply assigning or iterating over &lt;code&gt;lib1.books()&lt;/code&gt;. The &lt;code&gt;Book&lt;/code&gt; model contains a method called &lt;code&gt;libraries()&lt;/code&gt; which does just the opposite: allows you to get all the libraries where a given book resides.&lt;br /&gt;&lt;br /&gt;Our data has been created and linked. Now we pass it in to the template.&lt;pre class="code"&gt;template_values= {&lt;br /&gt; 'lib': lib1.name,&lt;br /&gt; 'books_at_lib': lib1.books(),&lt;br /&gt; 'forbook': book1.title,&lt;br /&gt; 'libs_by_book': book1.libraries(),&lt;br /&gt; 'libs_books': libs.libscol.order('name'),&lt;br /&gt; 'books_libs': books.bookscol.order('-author').order('title')&lt;br /&gt;}&lt;/pre&gt;In this example, we not only display all libraries and all books (via &lt;code&gt;libs_books&lt;/code&gt; and &lt;code&gt;books_libs&lt;/code&gt;) the way we did in the previous post, but also output all books at a library (&lt;code&gt;books_at_lib&lt;/code&gt;), and all libraries that contain a given book (&lt;code&gt;libs_by_book&lt;/code&gt;).&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/gaemany2.png" alt="" /&gt;&lt;br /&gt;Here's &lt;a href="http://blog.arbingersys.com/gaemany2_index.txt"&gt;the template&lt;/a&gt;, if you want to take a look at it.&lt;h4 class="arb-subhead"&gt;Denormalize your data&lt;/h4&gt;As I stated before, the GAE Datastore is not a relational database. Databases were designed for compactness and efficiency, and normalization is used, in part, as a way to minimize the size of your data on disk.&lt;br /&gt;&lt;br /&gt;The Datastore has been built, first and foremost, with scalability in mind. Scalability means, in essence, "add more servers as needed, without re-writing your code". Specifically to the GAE Datastore, it means "disk space is cheap, stop worrying about it, and scale".&lt;br /&gt;&lt;br /&gt;Consider modifying our &lt;code&gt;LibraryBook&lt;/code&gt; model above to look like&lt;pre class="code"&gt;class LibraryBook(db.Model):&lt;br /&gt;  library = db.ReferenceProperty(Library)&lt;br /&gt;  book = db.ReferenceProperty(Book)&lt;br /&gt;  booktitle = db.StringProperty()&lt;br /&gt;  libraryname = db.StringProperty()&lt;/pre&gt;Now, we are not only storing each book's title in the &lt;code&gt;LibraryBook&lt;/code&gt; entity, but we are also storing it in the &lt;code&gt;title&lt;/code&gt; property of the referenced &lt;code&gt;Book&lt;/code&gt; entity. While this is obviously not space efficient, and certainly not the elegant, normalized way of storing relational data our brains are used to, &lt;b&gt;it scales well and is fast&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;It scales because the Datastore runs on who knows how many commodity computers in the background (without the knowledge of our application), and it's fast because we have the most commonly needed fields available immediately. If you need to poke further into the data, like to get the street address of the library, you would use the referenced models, and our JOIN then comes into play.&lt;br /&gt;&lt;br /&gt;(Thanks, &lt;a href="http://groups.google.com/group/google-appengine/browse_thread/thread/e9464ceb131c726f/6aeae1e390038592#6aeae1e390038592"&gt;Ben the Indefatigable&lt;/a&gt; for illuminating this.)</description><link>http://blog.arbingersys.com/2008/04/google-app-engine-better-many-to-many.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-1968351214865364726</guid><pubDate>Mon, 28 Apr 2008 20:15:00 +0000</pubDate><atom:updated>2008-04-30T10:57:16.192-06:00</atom:updated><title>Google App Engine: Many-to-many JOIN</title><description>&lt;img src="http://blog.arbingersys.com/images/gae.jpg" alt="GAE" align="left" /&gt;&lt;span style="background-color:#ffffbb"&gt;Update: After reading this, you might want to check out &lt;a href="http://blog.arbingersys.com/2008/04/google-app-engine-better-many-to-many.html"&gt;GAE: [A Better] Many-to-many JOIN&lt;/a&gt;, which gives an improved way of doing this, plus goes into why you &lt;b&gt;shouldn't&lt;/b&gt; normalize your data.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A public library has many books. In SQL-speak, this is a one-to-many relationship. (For the sake of the argument, I'll assume each library has only one copy of a given book). It follows then, that many libraries have many books. This is a many-to-many relationship. On the heels of my recent post &lt;a href="http://blog.arbingersys.com/2008/04/google-app-engine-one-to-many-join.html"&gt;&lt;span&gt;GAE: One-to-many JOIN&lt;/span&gt;&lt;/a&gt;, here is an example showing &lt;span style="font-weight: bold;"&gt;how to do a many-to-many JOIN&lt;/span&gt;&lt;span&gt; using &lt;/span&gt;&lt;span&gt;the Google App Engine Datastore&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;You can download this entire sample &lt;a href="http://www.arbingersys.com/dnlds/gaemany.tar.gz"&gt;&lt;span&gt;here&lt;/span&gt;&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A many-to-many SQL query for our library scenario would look something like&lt;br /&gt;&lt;pre class="code"&gt;SELECT&lt;br /&gt;    *&lt;br /&gt;FROM&lt;br /&gt;    library&lt;br /&gt;INNER JOIN&lt;br /&gt;    libraries_books&lt;br /&gt;ON&lt;br /&gt;    library.KEY=libraries_books.library_KEY&lt;br /&gt;INNER JOIN&lt;br /&gt;    books&lt;br /&gt;ON&lt;br /&gt;    libraries_books.book_KEY=books.KEY&lt;/pre&gt;To duplicate this functionality in the Datastore, we have to model our data as follows. (Full code listing &lt;a href="http://blog.arbingersys.com/gaemany_example.txt"&gt;here&lt;/a&gt;.)&lt;pre class="code"&gt;# These are used for linking/ordering&lt;br /&gt;class Books(db.Model):&lt;br /&gt;    notes = db.StringProperty(required=False)&lt;br /&gt;&lt;br /&gt;class Libraries(db.Model):&lt;br /&gt;    notes = db.StringProperty(required=False)&lt;br /&gt;&lt;br /&gt;# Data models&lt;br /&gt;class Library(db.Model):&lt;br /&gt;    name = db.StringProperty(required=True)&lt;br /&gt;    address = db.StringProperty(required=True)&lt;br /&gt;    city = db.StringProperty(required=True)&lt;br /&gt;    library_list = db.ReferenceProperty(Libraries,&lt;br /&gt;        required=True, collection_name='ref_libs')&lt;br /&gt;&lt;br /&gt;class Book(db.Model):&lt;br /&gt;    title = db.StringProperty(required=True)&lt;br /&gt;    author = db.StringProperty(required=True)&lt;br /&gt;    library = db.ReferenceProperty(Library,&lt;br /&gt;        required=True, collection_name='books')&lt;br /&gt;    book_list = db.ReferenceProperty(Books,&lt;br /&gt;        required=True, collection_name='ref_books')&lt;/pre&gt;The &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; models share a one-to-many relationship. This is setup using the &lt;code&gt;Book.library&lt;/code&gt; &lt;span style="font-style: italic;"&gt;db.ReferenceProperty&lt;/span&gt;. Nothing really new here (if you read my &lt;a href="http://blog.arbingersys.com/2008/04/google-app-engine-one-to-many-join.html"&gt;one-to-many post&lt;/a&gt;, anyway).&lt;br /&gt;&lt;br /&gt;We need some additional references to pull off the many-to-many relationships, however, plus a couple extra Models. (It's important to note that the &lt;span style="font-style: italic;"&gt;db.ReferenceProperty&lt;/span&gt; in itself only allows for a one-to-many relationship. That's why we need more than one get the many-to-many behavior.) I've created the &lt;code&gt;Libraries&lt;/code&gt; and &lt;code&gt;Books&lt;/code&gt; models for this. You may notice that they have an optional, largely unnecessary property named &lt;code&gt;notes&lt;/code&gt;. This can pretty much be ignored. We really just need these entities to exist in order to point to them from our &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; entities.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;Library&lt;/code&gt; model contains a reference to &lt;code&gt;Libraries&lt;/code&gt; through a property named &lt;code&gt;library_list&lt;/code&gt;. &lt;code&gt;Book&lt;/code&gt; has a reference to &lt;code&gt;Books&lt;/code&gt; via &lt;code&gt;book_list&lt;/code&gt;. Having references to both &lt;code&gt;Libraries&lt;/code&gt; and &lt;code&gt;Books&lt;/code&gt; allows us to manipulate the sorting for each collection, as you will see below.&lt;br /&gt;&lt;br /&gt;When the page loads in our browser, the first thing we do is create entities from our models, and give them some data.&lt;pre class="code"&gt;# Library collection&lt;br /&gt;libs = Libraries()&lt;br /&gt;libs.put()&lt;br /&gt;&lt;br /&gt;# Books collection&lt;br /&gt;books = Books()&lt;br /&gt;books.put()&lt;br /&gt;&lt;br /&gt;# Setup libraries&lt;br /&gt;lib1 = Library(name='lib1', address='street a', city='city1',&lt;br /&gt;    library_list=libs)&lt;br /&gt;lib2 = Library(name='lib2', address='street b', city='city2',&lt;br /&gt;    library_list=libs)&lt;br /&gt;lib1.put()&lt;br /&gt;lib2.put()&lt;br /&gt;&lt;br /&gt;# Books:&lt;br /&gt;# Both libraries&lt;br /&gt;book1 = Book(title='book1', author='author one',&lt;br /&gt;    library=lib1, book_list=books)&lt;br /&gt;book2 = Book(title='book1', author='author one',&lt;br /&gt;    library=lib2, book_list=books)&lt;br /&gt;# Only first library&lt;br /&gt;book3 = Book(title='book2', author='author one',&lt;br /&gt;    library=lib1, book_list=books)&lt;br /&gt;# Both libraries&lt;br /&gt;book4 = Book(title='book3', author='author two',&lt;br /&gt;    library=lib1, book_list=books)&lt;br /&gt;book5 = Book(title='book3', author='author two',&lt;br /&gt;    library=lib2, book_list=books)&lt;br /&gt;book1.put()&lt;br /&gt;book2.put()&lt;br /&gt;book3.put()&lt;br /&gt;book4.put()&lt;br /&gt;book5.put()&lt;/pre&gt;We declare our "link" entities, &lt;code&gt;libs&lt;/code&gt; and &lt;code&gt;books&lt;/code&gt;, first. Next we create two library instances, &lt;code&gt;lib1&lt;/code&gt; and &lt;code&gt;lib2&lt;/code&gt;, and assign &lt;code&gt;libs&lt;/code&gt; to &lt;code&gt;library_list&lt;/code&gt; to create a one-to-many relationship from &lt;code&gt;Library&lt;/code&gt; to &lt;code&gt;Libraries&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;A &lt;code&gt;Book&lt;/code&gt; entity has two relationships to setup. A one-to-many relationship to a given &lt;code&gt;Library&lt;/code&gt; entity, and a one-to-many relationship to the &lt;code&gt;Books&lt;/code&gt; entity. These are established through the &lt;code&gt;library&lt;/code&gt; and &lt;code&gt;book_list&lt;/code&gt; properties, respectively.&lt;br /&gt;&lt;br /&gt;After we store our data, we use the collections in our &lt;code&gt;Library&lt;/code&gt; and &lt;code&gt;Book&lt;/code&gt; models to create two objects that we will pass to our template.&lt;br /&gt;&lt;pre class="code"&gt;libs_books = libs.ref_libs.order('name')&lt;br /&gt;books_libs = books.ref_books.order('author').order('-title')&lt;br /&gt;&lt;br /&gt;template_values = {&lt;br /&gt;    'libs_books': libs_books,&lt;br /&gt;    'books_libs': books_libs&lt;br /&gt;}&lt;/pre&gt;Both &lt;code&gt;libs_books&lt;/code&gt; and &lt;code&gt;books_libs&lt;/code&gt; contain many-to-many relationships between libraries and books. But &lt;code&gt;libs_books&lt;/code&gt; references &lt;span style="font-weight: bold;"&gt;books from libraries&lt;/span&gt;, allowing you to sort by library, and &lt;code&gt;books_libs&lt;/code&gt; does the opposite, referencing &lt;span style="font-weight: bold;"&gt;libraries from books&lt;/span&gt;, letting you sort by books. This is certainly more clumsy and more work than our SQL counterpart, which just needs an ORDER BY clause to sort either way.&lt;br /&gt;&lt;br /&gt;On to the template. To output books by library, we have to iterate over every library &lt;code&gt;lib&lt;/code&gt; in &lt;code&gt;libs_books&lt;/code&gt;, and then iterate over every &lt;code&gt;book&lt;/code&gt; referenced to &lt;code&gt;lib&lt;/code&gt;.&lt;br /&gt;&lt;pre class="code"&gt;{% for lib in libs_books %}&lt;br /&gt;    {% for book in lib.books %}&lt;br /&gt;        &amp;lt;tr&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;{{ lib.name }}&amp;lt;/td&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;{{ lib.address }}&amp;lt;/td&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;{{ lib.city }}&amp;lt;/td&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;{{ book.title }}&amp;lt;/td&amp;gt;&lt;br /&gt;            &amp;lt;td&amp;gt;{{ book.author }}&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;/tr&amp;gt;&lt;br /&gt;    {% endfor %}&lt;br /&gt;{% endfor %}&lt;/pre&gt;Because of the way references are setup in &lt;code&gt;libs_books&lt;/code&gt;, we are able to order the output based on the libraries, as you can see in the first table below.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/gaemany.png" alt="results" /&gt;&lt;br /&gt;&lt;br /&gt;The second table above shows the output from &lt;code&gt;books_libs&lt;/code&gt;, which we use to &lt;span style="font-weight:bold;"&gt;order by books&lt;/span&gt;. Here's how we generate the data in the template:&lt;br /&gt;&lt;pre class="code"&gt;{% for book in books_libs %}&lt;br /&gt;    &amp;lt;tr&amp;gt;&lt;br /&gt;        &amp;lt;td&amp;gt;{{ book.title }}&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;td&amp;gt;{{ book.author }}&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;td&amp;gt;{{ book.library.name }}&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;td&amp;gt;{{ book.library.address }}&amp;lt;/td&amp;gt;&lt;br /&gt;        &amp;lt;td&amp;gt;{{ book.library.city }}&amp;lt;/td&amp;gt;&lt;br /&gt;    &amp;lt;/tr&amp;gt;&lt;br /&gt;{% endfor %}&lt;/pre&gt;We don't have to use nested loops, and we simply use &lt;code&gt;book.library&lt;/code&gt; as a normal reference (not a back-reference) to get the library associated to the given book. The reason we don't have to nest is because a &lt;code&gt;Book&lt;/code&gt; entity has a &lt;span style="font-weight: bold;"&gt;many-to-one&lt;/span&gt; relationship with a &lt;code&gt;Library&lt;/code&gt; entity, so each book is already attached to a &lt;code&gt;Library&lt;/code&gt;. &lt;code&gt;Library&lt;/code&gt; entities have a &lt;span style="font-weight: bold;"&gt;one-to-many&lt;/span&gt; relationship to &lt;code&gt;Book&lt;/code&gt; entities, so every time you get &lt;code&gt;lib&lt;/code&gt;, you have to find it's &lt;span style="font-style: italic;"&gt;many&lt;/span&gt;, which requires the second loop.&lt;br /&gt;&lt;br /&gt;There you have it. A first blush example, to be sure, but I think it conveys the core steps required to duplicate the behavior of a relational many-to-many JOIN.</description><link>http://blog.arbingersys.com/2008/04/google-app-engine-many-to-many-join.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-8657086044755058007</guid><pubDate>Sun, 27 Apr 2008 01:39:00 +0000</pubDate><atom:updated>2008-05-02T08:39:37.566-06:00</atom:updated><title>Google App Engine: One-to-many JOIN</title><description>&lt;img src="http://blog.arbingersys.com/images/gae.jpg" align="left" alt="GAE" /&gt; By now, no doubt, most developers have heard about the Google App Engine (GAE). And even if you didn't get one of the 10K free accounts, you might still have downloaded and started messing around with the SDK.&lt;br /&gt;&lt;br /&gt;Google touts the platform's ease of development, and stepping through the samples reinforce that it is, in fact, quite easy. However, it doesn't take long to discover what will probably be the &lt;span style="font-weight: bold;"&gt;biggest hurdle for developers entrenched in the relational database paradigm&lt;/span&gt;: The Google Datastore. It's &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; a relational database, and it's not an OOP wrapper to a relational database. It's a web-specialized data storage mechanism, accessed through classes called Models, and objects called Entities.&lt;br /&gt;&lt;br /&gt;I'm willing to bet that most of the developers playing with the SDK will first really "get" this when they move past the simple "one table" queries in the samples, and try to do a basic JOIN query. Although there is a SQL&lt;span style="font-style: italic;"&gt;like&lt;/span&gt; syntax called Gql -- as stated in the &lt;span style="font-style: italic;"&gt;Docs&lt;/span&gt; -- there is no JOIN.&lt;br /&gt;&lt;br /&gt;To get this functionality, you have to use &lt;code&gt;db.ReferenceProperty&lt;/code&gt; to link one object to another. &lt;span style="font-weight: bold;"&gt;Here's a short demonstration of how it's done.&lt;/span&gt; I figure this is much needed, since there seems to be no good examples for it in the Google documentation. (The best information I could find was in the GAE discussion group.)&lt;br /&gt;&lt;br /&gt;Below, I've listed &lt;b&gt;example.py&lt;/b&gt; in its entirety (don't worry, it's short), and I'll refer to each pertinent section by the line numbers. (You can download the entire sample &lt;a href="http://www.arbingersys.com/dnlds/gappjoin.tar.gz"&gt;here&lt;/a&gt;. Put it under the SDK folder, and run it like any of the GAE samples.)&lt;br /&gt;&lt;pre class="arb-code"&gt;1  import os&lt;br /&gt;2  import cgi&lt;br /&gt;3  import wsgiref.handlers&lt;br /&gt;4&lt;br /&gt;5  from google.appengine.ext import webapp&lt;br /&gt;6  from google.appengine.ext import db&lt;br /&gt;7  from google.appengine.ext.webapp import template&lt;br /&gt;8&lt;br /&gt;9  class MainPage(webapp.RequestHandler):&lt;br /&gt;10    def get(self):&lt;br /&gt;11&lt;br /&gt;12      url = EnteredUrl(url="http://domain.com/page.html")&lt;br /&gt;13      url.put()&lt;br /&gt;14&lt;br /&gt;15      match1 = AffinityUrl(&lt;br /&gt;16          url="http://domain.com/dir/page1.html",&lt;br /&gt;17          affinity = .83,&lt;br /&gt;18          entered_url=url&lt;br /&gt;19      )&lt;br /&gt;20      match1.put()&lt;br /&gt;21&lt;br /&gt;22      match2 = AffinityUrl(&lt;br /&gt;23          url="http://domain.com/dir/page2.html",&lt;br /&gt;24          affinity = .8301,&lt;br /&gt;25          entered_url=url&lt;br /&gt;26      )&lt;br /&gt;27      match2.put()&lt;br /&gt;28&lt;br /&gt;29      matched_urls=url.matched_urls.order('-affinity')&lt;br /&gt;30&lt;br /&gt;31      aff_entries = AffinityUrl.all().order('url')&lt;br /&gt;32&lt;br /&gt;33      template_values = {&lt;br /&gt;34          'url' : url.url,&lt;br /&gt;35          'matched_urls': matched_urls,&lt;br /&gt;36          'aff_entries': aff_entries&lt;br /&gt;37        }&lt;br /&gt;38&lt;br /&gt;39      path = os.path.join(os.path.dirname(__file__), 'index.html')&lt;br /&gt;40      self.response.out.write(template.render(path, template_values))&lt;br /&gt;41&lt;br /&gt;42  class EnteredUrl(db.Model):&lt;br /&gt;43      url = db.StringProperty(required=True)&lt;br /&gt;44&lt;br /&gt;45  class AffinityUrl(db.Model):&lt;br /&gt;46      url = db.StringProperty(required=True)&lt;br /&gt;47      affinity = db.FloatProperty(required=True)&lt;br /&gt;48      entered_url = db.ReferenceProperty(EnteredUrl,&lt;br /&gt;49          required=True, collection_name='matched_urls')&lt;br /&gt;50&lt;br /&gt;51  def main():&lt;br /&gt;52      application = webapp.WSGIApplication(&lt;br /&gt;53                                         [('/', MainPage)],&lt;br /&gt;54                                         debug=True)&lt;br /&gt;55      wsgiref.handlers.CGIHandler().run(application)&lt;br /&gt;56&lt;br /&gt;57  if __name__ == "__main__":&lt;br /&gt;58      main()&lt;br /&gt;&lt;/pre&gt;The above stores a URL someone has entered, and then stores other URLs that match it by some degree (the "affinity"). The affinity is a numeric score. This is a simple one-to-many relationship, and to get at the data using standard SQL, we'd write something like:&lt;br /&gt;&lt;pre class="code"&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;entered_url.url,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;affinity_url.url,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;affinity_url.affinity&lt;br /&gt;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;entered_url&lt;br /&gt;JOIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;affinity_url&lt;br /&gt;ON&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;entered_url.KEY=affinity_url.FOREIGN_KEY&lt;/pre&gt;Here are the steps using the GAE Datastore.&lt;br /&gt;&lt;br /&gt;Lines 42-49.&lt;br /&gt;First, let's define the data Model. &lt;code&gt;EnteredUrl&lt;/code&gt; defines a single string property, &lt;code&gt;url&lt;/code&gt;, for the obvious reason. &lt;code&gt;AffinityUrl&lt;/code&gt; defines a string property for &lt;code&gt;url&lt;/code&gt;, as well as a float &lt;code&gt;affinity&lt;/code&gt; property, for storing the score.&lt;br /&gt;&lt;br /&gt;Lines 48-49.&lt;br /&gt;Also, &lt;code&gt;AffinityUrl&lt;/code&gt; defines a &lt;code&gt;db.ReferenceProperty&lt;/code&gt; named &lt;code&gt;entered_url&lt;/code&gt;, which refers to an &lt;code&gt;EnteredUrl&lt;/code&gt; object. This is the link between our two data objects, and how we effectively do a JOIN. The &lt;code&gt;collection_name&lt;/code&gt;, &lt;span style="font-style: italic;"&gt;matched_urls&lt;/span&gt;, is used to refer to the collection of &lt;code&gt;AffinityUrl&lt;/code&gt; objects that will be linked.&lt;br /&gt;&lt;br /&gt;Lines 12-13.&lt;br /&gt;When the page is loaded in the browser we create an &lt;code&gt;EnteredUrl&lt;/code&gt; entity named &lt;code&gt;url&lt;/code&gt;, setting its &lt;code&gt;url&lt;/code&gt; property to a string value.&lt;br /&gt;&lt;br /&gt;Lines 15-27.&lt;br /&gt;We setup two &lt;code&gt;AffinityUrl&lt;/code&gt; objects, and assign them both a url and a numeric score. Additionally, we point &lt;code&gt;entered_url&lt;/code&gt; to our &lt;code&gt;EnteredUrl&lt;/code&gt; object, &lt;code&gt;url&lt;/code&gt;. We have just linked one object (&lt;code&gt;url&lt;/code&gt;) to many (&lt;code&gt;match1&lt;/code&gt;, and &lt;code&gt;match2&lt;/code&gt;).&lt;br /&gt;&lt;br /&gt;Line 29.&lt;br /&gt;This line queries the data in the one-to-many way, and stores it in an object, &lt;code&gt;matched_urls&lt;/code&gt;, which I pass through to the template for iteration and output. This is where the collection name we defined in the &lt;code&gt;db.ReferenceProperty&lt;/code&gt; attributes is used. Note that the collection name, &lt;span style="font-style: italic;"&gt;matched_urls&lt;/span&gt;, is called like a method from &lt;code&gt;url&lt;/code&gt;, since &lt;code&gt;url&lt;/code&gt; is the object being referenced.&lt;br /&gt;&lt;br /&gt;Line 31.&lt;br /&gt;Additionally, for illustration, I query the &lt;code&gt;AffinityUrl&lt;/code&gt; object data and save it in &lt;code&gt;aff_entries&lt;/code&gt;. Just as in SQL, where you can JOIN tables, or query them individually, the App Engine allows you to do both. (Hopefully, you've realized by now that although they look and are accessed differently, these linked entities are behaving quite a lot like relational database tables.)&lt;br /&gt;&lt;br /&gt;In the template, I output the data from &lt;code&gt;matched_urls&lt;/code&gt; by getting each &lt;code&gt;AffinityUrl&lt;/code&gt; object in the collection, and displaying that URL. Note that because of the &lt;code&gt;.order('-affinity')&lt;/code&gt; call, we are displaying the URLs with the closest affinity at the top (descending order).&lt;br /&gt;&lt;pre class="code"&gt;&amp;lt;table&amp;gt;&lt;br /&gt;{% for affurl in matched_urls %}&lt;br /&gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;{{ affurl.url }}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;{% endfor %}&lt;br /&gt;&amp;lt;/table&amp;gt;&lt;/pre&gt;Load this up in your browser, and refresh a few times, and this is what you get:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/gappjoin.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;You may have noticed from the code that I also pass all the data stored in the &lt;code&gt;AffinityUrl&lt;/code&gt; model (line 31) to the template as well. This is output in the second table, above.&lt;br /&gt;&lt;br /&gt;Because I've refreshed the page several times, I've generated and stored the &lt;code&gt;match1&lt;/code&gt; and &lt;code&gt;match2&lt;/code&gt; objects multiple times to the Datastore. This highlights something strikingly different about the Datastore and a SQL table. SQL statements like the one I give will display all the entries that match between &lt;code&gt;EnteredUrl&lt;/code&gt; and &lt;code&gt;AffinityUrl&lt;/code&gt;, even if entries in &lt;code&gt;AffinityUrl&lt;/code&gt; are duplicated. As you can see, even though we have duplicate &lt;code&gt;AffinityUrl&lt;/code&gt; entities stored, the reference from the &lt;code&gt;EnteredUrl&lt;/code&gt; entity is smart enough to realize that &lt;em&gt;they are&lt;/em&gt; duplicates, and only displays the ones that are unique. &lt;em&gt;Update: please see the comments for a correction of the previous statements. The Datastore is creating new entities each time with a unique ID...&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;The Datastore takes a little getting used to, especially for those experienced in the standard relational data models. (Good ol' &lt;span style="font-style: italic;"&gt;paradigm shift&lt;/span&gt;.) The GAE documentation feels unfinished or at least rushed, which is unfortunate. I personally think they should have concentrated more on giving good examples that demonstrate mapping relational concepts to Datastore concepts, since the majority of developers looking at the GAE will be old hands at the relational stuff.&lt;br /&gt;&lt;br /&gt;I'm sure they'll get there eventually. In the meantime, I hope you found this tutorial useful.</description><link>http://blog.arbingersys.com/2008/04/google-app-engine-one-to-many-join.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-6916742281390725689</guid><pubDate>Tue, 15 Apr 2008 04:42:00 +0000</pubDate><atom:updated>2008-04-23T09:28:13.500-06:00</atom:updated><title>I'm Trying To Quit... Commercial Software, Pt. 1</title><description>&lt;img src="http://blog.arbingersys.com/images/tryquit-ico.png" alt="Trying to quit" align="left" /&gt; This experiment started out simply enough. It was 2007, and I got a new laptop. I had been running Quickbooks 2004 for our checking accounts, and Office 2003 for our meager office tools needs. I decided this software would stay on my old laptop (now my wife's), and I would try Free Open Source Software (FOSS) alternatives on the new one. I was bored with Office, and fed up with Quickbooks, anyway, so why not?&lt;br /&gt;&lt;br /&gt;From there, the experiment broadened, and I decided to see if Linux/FOSS could keep me from ever having to boot into a proprietary system (Windows), or use proprietary software. I decided to keep notes, and now I seem to have enough material to start sharing the experience.&lt;br /&gt;&lt;br /&gt;This is where it begins. I replace Quickbooks with GnuCash, and Microsoft Office with OpenOffice on my new laptop, which is running Windows XP.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;GnuCash&lt;/h4&gt;Since I wasn't sure of anything, I didn't move our checking accounts out of Quickbooks. My wife and I were simply doing the laptop shuffle anyway, so it was just easier to leave everything where it was, and continue to maintain our registers on the old laptop.&lt;br /&gt;&lt;br /&gt;However, &lt;span style="font-weight: bold;"&gt;we wanted to start a monthly budget&lt;/span&gt;, and I decided to let GnuCash step up and take a shot. Installing GnuCash was as easy as any other Windows application. Simply download the installer and run through the prompts. No sweat.&lt;br /&gt;&lt;br /&gt;After doing a minimal amount of reading, and marginally more button punching and tab poking, I figured out that I would have to first create a register, and then apply a budget estimation to it.&lt;br /&gt;&lt;br /&gt;So I setup a register called &lt;span style="font-style: italic;"&gt;Monthly Budgeting&lt;/span&gt;. We decided on a monthly dollar amount, and I made this the initial deposit. Then, I began entering our receipts.&lt;br /&gt;&lt;br /&gt;Here's what the register looks like:&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/tryquit2.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;So far, nothing surprising or mind-boggling. GnuCash felt a lot like Quicken. There's only so much variation a register is going to have, after all. This is good, because it means that the learning curve from one product to the next is minimal.&lt;br /&gt;&lt;br /&gt;After finishing all my month's entries, I did some more poking around, and finally got the budget estimate working. &lt;span style="font-style: italic;"&gt;Hint: Select the Budget tab, click "Options" to set your intervals etc, and then click "Estimate".&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here's our budget after a few months of keeping track. The budget outline for the &lt;span style="font-style: italic;"&gt;Monthly Budgeting&lt;/span&gt; register is displayed horizontally, each month showing whether you are under budget (positive dollar amount), or over (negative) for that period.&lt;br /&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/tryquit.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;The only problem I've had was with the backup. Quickbooks has an easy backup feature, and the backup is stored in a single file. I've been backing up GnuCash by copying all the files from its directory to a flash card.&lt;br /&gt;&lt;br /&gt;This seems to work okay, but at one point GnuCash (or I, or both)  got confused, and I had to restore from the backup directory, and in the end I lost about a month's worth of entries. The backup could be a &lt;a href="http://svn.gnucash.org/docs/HEAD/backuppolicy.html"&gt;little easier, I think&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;GnuCash has worked out well.&lt;/span&gt; I've since added my business register to it, and it has all the standard features that you would at least find in Quicken. I'm not an accountant, so I can't really say whether GnuCash could replace Quickbooks for a business. I can say, however, that it seems like a pretty painless way &lt;span style="font-weight: bold;"&gt;to not pay &lt;/span&gt;for software for managing your personal check registers and budgets.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;OpenOffice&lt;/h4&gt;This will be pretty short. I barely ever use MS Word for anything, but occasionally need Excel. My wife uses Word the most, but not in any way that OpenOffice (or even Wordpad) couldn't handle.&lt;br /&gt;&lt;br /&gt;So far, Calc has been sufficient for my spreadsheet needs. There was barely a learning curve, and like I said, I don't make too many heavy demands on a spreadsheet. MS Access is another story, but for me that's more of something that I might use in development (say, of a .NET application, because it was convenient), so I'm not including it here.&lt;br /&gt;&lt;br /&gt;Like GnuCash, I think OpenOffice &lt;span style="font-weight: bold;"&gt;ranks high&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; enough in quality and design&lt;/span&gt; to work fine for a very large percentage of home users, and even for a lot of offices. As time progresses, whatever gaps there may be will only get narrower.&lt;br /&gt;&lt;h4 class="arb-subhead"&gt;So...&lt;/h4&gt; As you might have figured out by now, this experiment is not a feature-by-feature scrutiny of competing products. I'm just using software the way I would normally, which is essentially, &lt;span style="font-weight: bold;"&gt;"I don't care about feature X, until I need feature X"&lt;/span&gt;. I think most people work this way, unless they have a specific reason to become an expert. I'm not an accountant, doubt I will ever be an accountant, so I don't put a whole lot of time learning every arcane feature available in Quickbooks. I learn enough to do what I want, and won't go further until I need to.&lt;br /&gt;&lt;br /&gt;In this experiment, FOSS is effectively graded on whether or not it can substitute all or most of my proprietary software needs, in the way in which I use software. It is highly subjective, and human nature, like laziness and apathy, is very much a part of it, as you will see.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;(Next up: My old laptop dies, and we have to get another one. I decide to try Linux along with Vista, and see how little I actually have to use Vista.)&lt;/span&gt;</description><link>http://blog.arbingersys.com/2008/04/im-trying-to-quit-commercial-software.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-5249195366622406922</guid><pubDate>Thu, 10 Apr 2008 14:25:00 +0000</pubDate><atom:updated>2008-04-10T14:36:48.846-06:00</atom:updated><title>Insert or Update With a Single SQL Statement</title><description>&lt;img src="http://blog.arbingersys.com/images/sql.png" alt="sql" align="left" /&gt;Ever come across the situation while developing data-driven web applications when you needed to &lt;span style="font-weight: bold;"&gt;create a new record&lt;/span&gt; if one doesn't exist, but if one &lt;span style="font-style: italic;"&gt;does&lt;/span&gt; exist, then you need to &lt;span style="font-weight: bold;"&gt;update it instead&lt;/span&gt;?&lt;br /&gt;&lt;br /&gt;I certainly have, and I must admit with some shame that in the past I've handled it in the most obvious, and least elegant and efficient way, by&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(255, 102, 102);"&gt; querying SQL for the existence of the record&lt;/span&gt;,&lt;br /&gt;&lt;span style="color: rgb(204, 153, 51);"&gt;checking the result set in my code by looping and assigning a variable&lt;/span&gt;, &lt;span style="color: rgb(153, 0, 0);"&gt;&lt;br /&gt;&lt;span style="color: rgb(102, 0, 0);"&gt;checking the variable for a value&lt;/span&gt;&lt;/span&gt;&lt;span style="color: rgb(102, 0, 0);"&gt;, and if one doesn't exist, then doing the insert&lt;/span&gt;. &lt;span style="color: rgb(51, 102, 102);"&gt;&lt;br /&gt;Otherwise, doing the update&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;There are a couple problems here. First, it's a lot more code than necessary. Second, it requires two calls to SQL instead of one.&lt;br /&gt;&lt;br /&gt;You can eliminate this by making SQL do the conditional logic for you, via &lt;code&gt;IF EXISTS&lt;/code&gt;. Here's the sample:&lt;pre class="code"&gt;IF EXISTS(&lt;br /&gt; SELECT 1&lt;br /&gt; FROM MY_TABLE&lt;br /&gt; WHERE ITEM='somevalue' AND ENTERDATE='12/31/1999')&lt;br /&gt;    &lt;span style="color: rgb(0, 153, 0);"&gt;--Update Statement&lt;/span&gt;&lt;br /&gt;    UPDATE MY_TABLE&lt;br /&gt;    SET ITEM='anothervalue'&lt;br /&gt;    WHERE ITEM='somevalue' AND ENTERDATE='12/31/1999'&lt;br /&gt;ELSE&lt;br /&gt;    &lt;span style="color: rgb(0, 153, 0);"&gt;--Insert Statement&lt;/span&gt;&lt;br /&gt;    INSERT INTO MY_TABLE&lt;br /&gt;    (ITEM, ENTERDATE)&lt;br /&gt;    VALUES&lt;br /&gt;    ('somevalue', '12/31/1999')&lt;br /&gt;&lt;/pre&gt;&lt;code&gt;EXISTS&lt;/code&gt; lets you run a query statement, and if a value is returned, it outputs &lt;span style="font-weight: bold;"&gt;true&lt;/span&gt;. Otherwise, it outputs &lt;span style="font-weight: bold;"&gt;false&lt;/span&gt;. Couple that to &lt;code&gt;IF/ELSE&lt;/code&gt;, and you can see how useful this particular SQL clause is.&lt;br /&gt;&lt;br /&gt;The query inside &lt;code&gt;EXISTS&lt;/code&gt; returns 1 if the parameters in the &lt;code&gt;WHERE&lt;/code&gt; clause match, and returns nothing otherwise. What we return really doesn't matter. We're interested mainly in the parameters. If the parameters match something, then we will update them. Otherwise (&lt;code&gt;ELSE&lt;/code&gt;), we insert them into the table.&lt;br /&gt;&lt;br /&gt;Pretty simple. We just add our code parameters to the above statement (if your language uses parameters, e.g. Perl or C#), and send it on its way. One SQL call, and a lot less logic.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Update: I should have been clearer. This is TSQL, and will not work, in say, MySQL. (Thanks anonymous commenter!)&lt;/span&gt;</description><link>http://blog.arbingersys.com/2008/04/insert-or-update-with-single-sql.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-3211562709574021378</guid><pubDate>Wed, 19 Mar 2008 15:41:00 +0000</pubDate><atom:updated>2008-03-19T10:12:56.651-06:00</atom:updated><title>Reverse Callback Templating</title><description>&lt;img src="http://blog.arbingersys.com/images/perl-com-logo.gif" alt="perl.com" align="left" /&gt;I've just had my &lt;a href="http://www.perl.com/pub/a/2008/03/14/reverse-callback-templating.html"&gt;first article ever&lt;/a&gt; published on &lt;a href="http://www.perl.com/"&gt;Perl.com&lt;/a&gt;. It covers a template module I've written -- in Perl, obviously -- called &lt;a href="http://search.cpan.org/%7Egilad/Template-Recall-0.11/"&gt;Template::Recall&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Template systems provide a way to separate concerns, that is, design from logic. I won't cover it here, because that would be more than a tad redundant. If this topic interests you, here's the article link:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.perl.com/pub/a/2008/03/14/reverse-callback-templating.html"&gt;http://www.perl.com/pub/a/2008/03/14/reverse-callback-templating.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Also, you might want to read this conversation on Perlmonks.com:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.perlmonks.org/?node_id=674225"&gt;http://www.perlmonks.org/?node_id=674225&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You'll see that template systems are a much debated topic. And if I may venture a personal observation, the Perl language has covered the topic more than any other language out there, and in much greater depth.</description><link>http://blog.arbingersys.com/2008/03/reverse-callback-templating.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-5397825363214294682</guid><pubDate>Wed, 20 Feb 2008 15:11:00 +0000</pubDate><atom:updated>2008-02-24T21:11:41.911-07:00</atom:updated><title>My Two Perls</title><description>&lt;img src="http://blog.arbingersys.com/images/twoperls.png" alt="My Two Perls" align="left" /&gt;Perl's greatest blessing and greatest curse, in my opinion, is CPAN. CPAN is an unbelievably rich repository of modules that do everything imaginable. I can't think of another language that has a resource like it. But using CPAN on the most widely used desktop platform available, Windows, presents some problems. Here is one developer's Perl on Windows saga.&lt;br /&gt;&lt;br /&gt;Historically, I've always run ActiveState Perl. It's a great Windows distribution, and ActiveState has done a lot of work to make it very user friendly, especially by creating PPM, the Perl Package Manager. As opposed to the standard CPAN installation mechanism, which generally expects you to "make" your modules, sometimes compiling sources, PPM provides pre-compiled packages, so it's no hassle at all to install them. It's just a download/copy operation, really. The problem here is that if ActiveState's PPM repository doesn't have the module you want, you're back to compiling from source.&lt;br /&gt;&lt;br /&gt;At some point (as I became nerdier, I guess), I decided to play around with compiling my own version of Perl and bundling it with a few important web modules from CPAN (i.e. CGI, CGI::Ajax, DBI, SOAP::Lite, Template Toolkit, etc), along with Apache/mod_perl, and MySQL. I decided to make this a distribution, and named it &lt;a href="http://www.arbingersys.com/hostsites/zangweb/"&gt;&lt;span style="font-weight: bold;"&gt;zangweb&lt;/span&gt;&lt;/a&gt;. It was intended to give a Windows developer everything he needs to start programming in Perl/Apache/MySQL with as little effort possible.&lt;br /&gt;&lt;br /&gt;zangweb Perl replaced ActiveState on my machine for some time. I no longer had the convenience of PPM, so I just went ahead with the standard CPAN way of installing modules. For most modules, this wasn't too big of a headache. You just need to be sure to have a working development environment, one with nmake.exe available, and most modules installed without difficulty. Generally, I did something like&lt;br /&gt;&lt;pre class="code"&gt;c:\&gt;vcvars32&lt;br /&gt;c:\&gt;perl -MCPAN -e shell&lt;br /&gt;&lt;/pre&gt;and installed from the CPAN prompt.&lt;br /&gt;&lt;br /&gt;However, modules like PerlMagick, or any others that had complex C/C++ builds and originally been developed for *nix, &lt;span style="font-style: italic;"&gt;did not&lt;/span&gt; build easily. They took a lot of work, and while I thought it was kind of fun, from a hobbyist standpoint, I don't know if under other circumstances I would have wanted to go through all that trouble.&lt;br /&gt;&lt;br /&gt;Nonetheless, zangweb worked well, and I was pretty content. Then Perl 5.10 was released, and it was available from ActiveState in short time. I wanted to try 5.10, naturally, and as usual, the path of least resistance was ActiveState. I downloaded it and ran it alongside zangweb Perl at work. On my own laptop, I decided I would try a different configuration: ActiveState Perl 5.10, and standard Apache and MySQL installations. Kind of as a comparison to see how valuable zangweb really was.&lt;br /&gt;&lt;br /&gt;I realized the only thing that made zangweb more valuable was &lt;span style="font-style: italic;"&gt;all the work I had done to get those web CPAN modules compiled and installed&lt;/span&gt;. Yet again, it boiled down to the modules, and the difficulty that came with installing them on Windows. For instance I want to have PerlMagick. I have it for &lt;a href="http://www.arbingersys.com/hostsites/zangweb/extensions.html"&gt;zangweb&lt;/a&gt;.&lt;span style="font-style: italic;"&gt; &lt;/span&gt;So far, ActiveState doesn't for v5.10:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ppm.activestate.com/BuildStatus/5.10-P.html"&gt;http://ppm.activestate.com/BuildStatus/5.10-P.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But you might get lucky, and find some kind soul who has created and bundled it in a PPM friendly package:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.google.com/search?hl=en&amp;amp;q=filetype%3Appd+perlmagick&amp;amp;btnG=Google+Search"&gt;http://www.google.com/search?hl=en&amp;amp;q=filetype%3Appd ...&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;But I don't want to have to rely on the kindness of strangers to get my "must have" modules.&lt;br /&gt;&lt;br /&gt;Recently, I wanted to do some charting in Perl. After looking around at the modules, I decided I wanted to use GD::Graph. This relies on &lt;a href="http://www.libgd.org/"&gt;libgd&lt;/a&gt;. At the time of this writing, they don't have a compiled binary for Windows for the latest revision. So now I've got compiling ahead of me once again.&lt;br /&gt;&lt;br /&gt;After trying unsuccessfully to get it to compile natively on Windows, it dawned on me: Since CPAN is designed so much in the *nix way of doing things, why not make my second, "alternate Perl" run under an emulation of the Linux system? All the tools that are usually expected by these kinds of libraries, bash, configure, make, etc., are there, so surely I'd have a much easier time getting these modules on my machine this way.&lt;br /&gt;&lt;br /&gt;No way to know until you try. I installed Cygwin, which came with Perl 5.8 already bundled. GD::Graph expects you to have libgd already compiled, so I went through the steps to do this, using my freshly installed Cygwin bash shell.&lt;br /&gt;&lt;br /&gt;This is where the story gets remarkably pleasant.&lt;br /&gt;&lt;br /&gt;I downloaded the libgd source, and after reading the README, downloaded the libraries it required, i.e. libpng, and freetype. These two compiled no problem. I jumped back over to the libgd source folder, did its configure and make steps, and after waiting a while for things to compile (something I'm not real fond of, I must admit), had a working version of libgd. The CPAN install of GD::Graph was a breeze after this, and soon I was charting in Perl, happy as could be.&lt;br /&gt;&lt;br /&gt;Soon enough, I began to wonder why I wasn't just using Cygwin Perl as my main, and perhaps only, Perl distribution. I tried to think of anything I was doing with Perl that was only available to a Win32 distribution. (Yes, I know, that is kind of funny in retrospect.)  Nothing came up.&lt;br /&gt;&lt;br /&gt;The only thing I wondered about now was whether running Perl under emulation would be significantly slower than a natively compiled version. I know it should be slower. The more important question was would it be slow enough to matter?&lt;br /&gt;&lt;br /&gt;The quickest, most basic way that I could think to check was to make Perl count. So I ran the following with each of my Perls:&lt;br /&gt;&lt;br /&gt;ActiveState:&lt;br /&gt;&lt;pre class="code"&gt;perl -e"$a=time; for($i=0;$i&lt;=100000000;$i++){} print time-$a"  21 &lt;/pre&gt;Cygwin:&lt;br /&gt;&lt;pre class="code"&gt;perl -e'$a=time; for($i=0;$i&lt;=100000000;$i++){} print time-$a'  19&lt;/pre&gt;Cygwin was faster by about 2 seconds. This satisfied me, initially. At least I knew that there wasn't an embarassing difference in performance. Curious, now, however, I found some good benchmark tests on the web, primarily for comparing the &lt;a href="http://shootout.alioth.debian.org/gp4/benchmark.php?test=all&amp;amp;lang=python&amp;amp;lang2=perl"&gt;performance of different languages&lt;/a&gt;, but definitely useful for what I was trying to do. I downloaded the &lt;span style="font-weight: bold;"&gt;nsieve&lt;/span&gt; Perl code. This performs the &lt;a href="http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes"&gt;Sieve of Eratosthenes&lt;/a&gt;, and is a way of finding primes.&lt;br /&gt;&lt;br /&gt;Here are the results:&lt;br /&gt;&lt;br /&gt;ActiveState:&lt;br /&gt;&lt;pre class="code"&gt;perl C:\cygwin\home\nsieve.pl 7&lt;br /&gt;Primes up to  1280000    98610&lt;br /&gt;Primes up to   640000    52074&lt;br /&gt;Primes up to   320000    27608&lt;br /&gt;&lt;br /&gt;11&lt;br /&gt;&lt;/pre&gt;Cygwin:&lt;br /&gt;&lt;pre class="code"&gt;perl nsieve.pl 7&lt;br /&gt;Primes up to  1280000    98610&lt;br /&gt;Primes up to   640000    52074&lt;br /&gt;Primes up to   320000    27608&lt;br /&gt;&lt;br /&gt;11&lt;br /&gt;&lt;/pre&gt;They both ran in 11 seconds. I'm reasonably satisfied that Cygwin, for most of my development purposes, will be fast enough.&lt;br /&gt;&lt;br /&gt;So that leaves me with a nagging question. Why am I running two Perls? Unless there was a specific case where I need ActiveState -- performance or compatibility with some poorly designed app -- why not just run the Perl that works with CPAN?&lt;br /&gt;&lt;br /&gt;Then &lt;b&gt;My Two Perls&lt;/b&gt; can become &lt;b&gt;My One CPAN-Compatible Perl&lt;/b&gt;. I like the sound of that, as a matter of fact. Because really, that's what it's been about all along.</description><link>http://blog.arbingersys.com/2008/02/my-two-perls.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-3916104024443743726</guid><pubDate>Mon, 14 Jan 2008 04:30:00 +0000</pubDate><atom:updated>2008-01-17T12:29:45.125-07:00</atom:updated><title>High Level Languages Are Magic</title><description>&lt;img align="left" style="border:0;margin:3px" src="http://blog.arbingersys.com/images/magic1.png" alt="" border="0" /&gt; After pondering the recent flap about how CS departments  aren't providing a sufficient education by starting students in Java and ignoring lower level languages [&lt;span style="font-size:85%;"&gt;&lt;a href="http://www.stsc.hill.af.mil/CrossTalk/2008/01/0801DewarSchonberg.html"&gt;link&lt;/a&gt;, &lt;a href="http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html"&gt;link&lt;/a&gt;, and  &lt;a href="http://www.codinghorror.com/blog/archives/001035.html"&gt;link&lt;/a&gt;&lt;/span&gt;], it seems to me that the problem can be boiled down to the simple fact that &lt;span style="font-weight: bold;"&gt;high level languages do too much work for you&lt;/span&gt;. They make it unnecessary to think about the low level things that cause the code work. It becomes easy to think of those things as "magic", and by and large dismiss them. Magic is an important productivity booster, but should be implemented only after understanding, to some degree, the little cogs that help it arrive.&lt;br /&gt;&lt;br /&gt;High level languages do work hard for you, and I consider this an ultimate good, because I have a lot of work to do, and want to &lt;a href="http://blog.arbingersys.com/2007/12/test1.html"&gt;produce results as quickly as possible&lt;/a&gt;. One of the mantras of Perl is that given a context, it will simply &lt;a href="http://www.perlfoundation.org/perl_5_10_now_available"&gt;Do The Right Thing&lt;/a&gt;. Java and C# make it unnecessary to think much (if at all) about pointers. This makes my life a whole lot easier. But it still helps to understand lower level concepts, for instance when considering the performance of various objects in a language, like &lt;a href="http://groups.google.com/group/microsoft.public.dotnet.framework.performance/browse_thread/thread/200db2dbab439309/302cbd93506eba51"&gt;StringBuilder in C#&lt;/a&gt;*.&lt;br /&gt;&lt;br /&gt;I think magic is a danger even beyond CS departments. It's also inherent in productivity tools like Visual Studio, which will probably be learned on the job. If you only learn to use the magic, but don't understand that &lt;span style="font-style: italic;"&gt;it isn't really magic&lt;/span&gt;, then you're headed for trouble.&lt;br /&gt;&lt;br /&gt;I have a contractor who works for me developing ASP.NET applications. Out of college he didn't know C#/ASP.NET, but at a previous job had picked it up &lt;span class="me"&gt;à la &lt;/span&gt;Visual Studio. I was just getting into ASP.NET myself when he hired on, and was a little confused about how the [auto] postback worked. I thought it would be quickest to ask someone with experience, so I did. But he didn't know, even though he was already producing fairly complex web applications for us. In his view, it was just a feature of ASP.NET, and beyond that was not important, as long as he could turn it on or off in the Properties of the various controls.&lt;br /&gt;&lt;br /&gt;I had reasoned that it must be JavaScript, unless ASP.NET installed some sort of binary control on the sly. Sure enough, &lt;a href="http://www.xefteri.com/articles/show.cfm?id=18"&gt;it turned out to be JavaScript&lt;/a&gt;. I decided then and there that our contractor was at a disadvantage because he &lt;span style="font-weight: bold;"&gt;understood web development primarily through Visual Studio&lt;/span&gt;, and this hindered him from realizing that ASP.NET was made to fit a set of (effectively lower level) standards, not the other way around.&lt;br /&gt;&lt;br /&gt;We do a lot of programming in ASP.NET using Visual Studio, and it &lt;span style="font-style: italic;"&gt;is&lt;/span&gt; a productivity booster. We're building more powerful applications in shorter time frames, and with less effort. Its magic is definitely appreciated. But seeing beneath the magic is what allows us to really understand and fix bugs, and build robust, maintainable applications.&lt;br /&gt;&lt;br /&gt;Magic is for productivity. It's for those who have gotten the education, and the education is gotten by understanding the little cogs and how they relate to one another.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;* This discussion, I might add, jumps right into the low level arguments of memory management, showing just how far removed you really are from those little cogs...&lt;/span&gt;</description><link>http://blog.arbingersys.com/2008/01/high-level-languages-are-magic.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-942193483948288558</guid><pubDate>Fri, 21 Dec 2007 16:32:00 +0000</pubDate><atom:updated>2007-12-27T09:51:41.657-07:00</atom:updated><title>The Case for Flat-Threaded Discussions</title><description>As I stated in a &lt;a href="http://blog.arbingersys.com/2007/12/mr-spolsky-and-work-is-life-principle.html"&gt;previous entry&lt;/a&gt;, I've recently built and released an open source "conversation" system called &lt;a href="http://sylbi.arbingersys.com/"&gt;Sylbi&lt;/a&gt; (currently in beta). This system was based on the idea that &lt;span style="font-weight: bold;"&gt;blogs with comments&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;forums&lt;/span&gt; differ very little, and there was no reason why you couldn't build a system that could be both a forum &lt;span style="font-style: italic;"&gt;and&lt;/span&gt; a blogging platform.&lt;br /&gt;&lt;br /&gt;Because Sylbi provides the ability to have discussions, that is, multiple people respond to each other's posts over time, it had to deal with how to display those conversations. The two most common ways for doing this are the &lt;span style="font-style: italic;"&gt;flat&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;threaded&lt;/span&gt; models. For a detailed and intelligent commentary on the virtues of these methods, see &lt;a href="http://www.codinghorror.com/blog/archives/000733.html"&gt;this post&lt;/a&gt; from Coding Horror, and &lt;a href="http://www.joelonsoftware.com/articles/BuildingCommunitieswithSo.html"&gt;this one&lt;/a&gt; from Joel On Software.&lt;br /&gt;&lt;br /&gt;As I began thinking about this problem, I decided that there is a third method for displaying conversations, one that I feel is preferable to the other two: &lt;span style="font-weight: bold;"&gt;threading without indention&lt;/span&gt;, or as I like to call it &lt;span style="font-style: italic;"&gt;flat-threaded&lt;/span&gt;. Here is my conclusion, posted on the official "blog" for the Sylbi project. (You can read &lt;a href="http://sylbi.arbingersys.com/demo/conversation.cgi?rm=blog&amp;amp;uid=5&amp;amp;id=52&amp;amp;pid=213"&gt;the full post here&lt;/a&gt;, which talks about this as well as the other unique features of Sylbi.)&lt;blockquote&gt;It is my opinion that threading a conversation, that is, grouping replies to a post immediately below that post, provides the most logical organization method. Slashdot discussions are threaded, as are those on reddit. However, I think that indenting replies adds no real value, and instead actually makes the conversation more difficult to read. Sylbi threads conversations, but uses no indentation. So as you scan posts from top to bottom, post replies are clustered together, but you must use the content of the posts to determine the grouping. I refer to this as a "flat-threaded" conversation. Sylbi provides the means to quote previous posts, if this should be necessary.&lt;br /&gt;&lt;br /&gt;Here's why I think this view works. Books are written from top to bottom. If an author refers to something that occurred in a previous chapter, you rely on your memory and comprehension to understand the reference. If the reference is subtle enough, an author may quote himself. Where a conversation is concerned, I think that memory and comprehension don't need to be aided by indentation, and where a reference may require it, you can easily provide a quote.&lt;/blockquote&gt;I am committed to "eating my own dogfood", and so am using Sylbi while I work on it. I have a &lt;a href="http://sylbi.arbingersys.com/demo/index.cgi"&gt;live version&lt;/a&gt; running on my web hosting provider and use it to identify problems with my code as well as my assumptions.&lt;br /&gt;&lt;br /&gt;One of my initial tests was of the flat-threaded view, and I created &lt;a href="http://sylbi.arbingersys.com/demo/conversation.cgi?topic=Eating%20Our%20Own%20Dogfood&amp;amp;tid=2&amp;amp;id=1&amp;amp;pid=1"&gt;this conversation&lt;/a&gt; (which I unfortunately made a little difficult to read by using tons of self-references) and began using it to probe the concept. This was a discussion, so as I coded, I tested by adding to it, and eventually, this analogy fell out:&lt;br /&gt;&lt;blockquote&gt;Consider a real conversation amongst a group. A topic is started by Alice, and Bob and Charlie discuss it with her for a length of time. Then, Bob touches on an individual point of Alice's initial topic, and a segue is created. Let's say that only Charlie and Bob discuss this point. Alice is silent. But she hasn't said everything she wants about the initial topic, so after they are finished, she brings them back to the topic, and they discuss it further. Bob's segue "held place" for additional comments by Charlie and Bob, and then the original topic was resumed. Viewed in a linear sense, this is exactly what a flat-threaded conversation does.&lt;br /&gt;&lt;/blockquote&gt;The &lt;span style="font-style: italic;"&gt;holds place&lt;/span&gt; comment above is in reference to the (at least logical) "fairness" of grouping responses together. Because it is likely that a response to a post may come days after other posts have been made, and earlier posts are pushed down as this latecomer is inserted below the post it's a response to. For example:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Initial post (entry) E &lt;span style="color: rgb(51, 153, 153);"&gt;[day 1]&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Response (to E) R1 &lt;span style="color: rgb(51, 153, 153);"&gt;[day 1]&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Response (to R1) R3 &lt;span style="color: rgb(255, 0, 0);"&gt;[day 2]&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Response (to E) R2 &lt;span style="color: rgb(51, 153, 153);"&gt;[day 1]&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;A response to a post becomes a &lt;span style="font-weight: bold;"&gt;subordinate post&lt;/span&gt;, as R3 is a subordinate to R1 above. R1 comes before R2, because it was posted earlier. So any responses to R1 get inserted directly below it, ahead of other, potentially earlier posts (R2). So R1 &lt;span style="font-style: italic;"&gt;held place&lt;/span&gt; for R3, and it &lt;span style="font-weight: bold;"&gt;had the right to&lt;/span&gt; since it was made earlier. This is a sort of "first come, first serve &lt;span style="font-style: italic;"&gt;for all my children&lt;/span&gt;" mentality. But it serves an important purpose: to keep direct responses together, which provides better cohesion, I think.&lt;br /&gt;&lt;br /&gt;Of course, there is a caveat. The &lt;span style="font-style: italic;"&gt;holds place&lt;/span&gt; idea is susceptible to gaming. For instance, if you want to have your entry appear higher up in the list of responses, you could respond to a higher level response, &lt;span style="font-weight: bold;"&gt;even if the content of your post is not particularly relevant&lt;/span&gt; to that one.&lt;br /&gt;&lt;br /&gt;Taking our example above, let's say that it's days later, and there are over 100 responses. You want to post, but hate the idea of being all the way at the bottom of the list. So you pick the first response below the initial entry, and respond to it, but really, you just want to sound off on the original entry. Because you are responding to R1, the system inserts you at the &lt;span style="font-weight: bold;"&gt;bottom of the subordinate list for R1&lt;/span&gt;, which puts you higher in the list than other posts that followed the rules.&lt;br /&gt;&lt;br /&gt;This is somewhat mitigated, however, by the fact that in 100 responses with no indention, it is difficult to be entirely clear which post is actually subordinate to which, and therefore where your post is going to appear vertically. It will be much more reasonable to simply respond to a post when you feel that the content of that post requires one.&lt;br /&gt;&lt;br /&gt;On the other hand, this may simply be a risk involved with human communication, and a small one at that. Further on in the dogfooding conversation above, I observed that real human conversation is far from trouble free.&lt;br /&gt;&lt;blockquote&gt;Alice starts a topic with Bob and Charlie. A segue is created, and Alice interjects that they are getting off the subject, and Bob and Charlie return from their tangent. Or they don't, and Alice's conversation is hijacked. I've also seen this conversation pattern (and been involved in it from probably all perspectives).&lt;br /&gt;&lt;br /&gt;So I think that, basically, when talking about the "natural" flow of conversation and the mantra that trying to mimic this in a forum [is good], it should be noted that real conversation is not necessarily a smooth or clean or non-anarchic interaction. It can be, but it can also be an incredible mess, incredibly trite, or some mix of both.&lt;/blockquote&gt;Ultimately, I think the flat-threaded method provides a slightly better view of online conversations by trying to be as &lt;span style="font-style: italic;"&gt;contextual&lt;/span&gt; as possible, and simplifying the presentation. However, just like real conversations, much depends on the humans.</description><link>http://blog.arbingersys.com/2007/12/case-for-flat-threaded-discussions.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-238400570662466642</guid><pubDate>Sat, 15 Dec 2007 23:20:00 +0000</pubDate><atom:updated>2007-12-17T15:16:37.237-07:00</atom:updated><title>Dormant Sticky Memory and Layered Comprehension</title><description>&lt;p style="color: rgb(0, 0, 0);"&gt;     I recently finished reading &lt;em&gt;Descartes: The Project of Pure Enquiry&lt;/em&gt; by Bernard     Williams. As soon as I read the last page, I moved back to chapter 2, and started     again from there. This is because I had retained and comprehended only about 50% of the book. Through the years, &lt;span style="font-weight: bold;"&gt;as I've learned better how to learn&lt;/span&gt;, immediately rereading has become an invaluable device for me, especially with a subject where I lack familiarity or educational background. (Like philosophy.)&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;If you had asked me on page 303 (the last one) to recall or explain anything from chapter 2, I would have been hard pressed to give you an answer. Just now, having finished reading the chapter again, I'd say that I grasped it nearly in full.&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;What I found really interesting, however, was how those things that I wouldn't have been able to recall at the end of the book &lt;span style="font-weight: bold;"&gt;jumped out from somewhere in the back of my mind&lt;/span&gt; the moment I read them again. For instance, there is an argument about "false lemmas" that uses an analogy about owning a Ford. After rereading the first few sentences, I could recall the full  argument in most of its detail.&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;So there must be some aspect of memory that works like a hard drive. (There is: it's called long term memory.) It just dumbly writes the "file" there in one of its sectors, where it resides unknowingly until something recalls it and loads it into short term memory (RAM), where you can actively use it.&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;Here's a useful little graphic from Wikipedia (&lt;span style="font-size:85%;"&gt;note: this model is criticized for being too simplistic, but it fits pretty well with how memory works &lt;span style="font-style: italic;"&gt;upon personal reflection&lt;/span&gt;, so it's still a useful visualization, I think&lt;/span&gt;):&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;&lt;br /&gt;&lt;img src="http://blog.arbingersys.com/images/Multistore_model.png" alt="" border="0" /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;When I first read the book, I had very little stored on the subject of Descarte's &lt;span style="font-style: italic;"&gt;Cogito ergo sum&lt;/span&gt;. Mr. William's book is a thorough analysis of the subject using modern logic, with the benefit of centuries of debate preceding him.  In short, it was a &lt;span style="font-weight: bold;"&gt;pretty steep curve to dive into&lt;/span&gt;. This is why I think that on my first pass I retained and ultimately comprehended so little.&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;On the second pass, it was quite different. I had obviously retained more than I thought, but since it wasn't coupled with strong comprehension, it seems to have been just rather "dumbly" stored. I doubt that if I had never read the book again, I would have been able to explain the "false lemmas" argument. Perhaps I would have recalled hearing about it somewhere, but it would have been foggy.&lt;br /&gt;&lt;/p&gt;&lt;p style="color: rgb(0, 0, 0);"&gt;But as I &lt;span style="font-style: italic;"&gt;re&lt;/span&gt;read, my mind already had some notion of the concepts, and so comprehension occurred more rapidly and to a fuller extent than before. You might say that my &lt;span style="font-weight: bold;"&gt;comprehension came about in a layered manner&lt;/span&gt;. A hazy concept lay in memory, was fortified by reprocessing the original text, and then stored again (to disk!) as a much more useful item.&lt;/p&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;This makes me think of my early days learning to program, when there were plenty of concepts I was unclear about, and I was rereading all the time. I was playing around with QBASIC on a DOS computer, then tried my hand at Turbo Pascal. Languages ultimately without a future.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;But I learned the "primitives" of programming from those languages: variables, looping, conditionals, routines, etc. &lt;span&gt;This is a layer of comprehension and sticky memory &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;still employed today&lt;/span&gt;. In fact, it's quite clear to me that despite the plethora of languages available, with all their different syntax and conceptual leanings, the actual number of concepts you need to understand &lt;span style="font-weight: bold;"&gt;really well&lt;/span&gt; are not that numerous. And once you've obtained and stored those layers, further comprehension occurs much faster.&lt;br /&gt;&lt;br /&gt;For example, once you understand C pointers and how they work, all reference work in any language, whether Perl, C#, Java, Python, is easy to understand. The nuance presented by the language is just another, usually small, comprehension layer that must be added.&lt;br /&gt;&lt;br /&gt;As new programming paradigms appear, I notice that I am able to grasp them much more quickly than I did the primitives from my early stages of instruction, even though those concepts are usually much more abstract and difficult. This is because, I think, &lt;span style="font-weight: bold;"&gt;like the second reading of my book&lt;/span&gt;, necessary, prior concepts are lying dormant in their sectors, ready to be loaded and rehearsed. Except it's more like the &lt;span style="font-style: italic;"&gt;n&lt;/span&gt;th reading, where &lt;span style="font-style: italic;"&gt;n&lt;/span&gt; is a pretty high number.&lt;br /&gt;&lt;br /&gt;So if you're new to programming, are overwhelmed by concepts and language choices, or feel like you're learning at much too slow a pace, never fear: if you stick with it and do the work, you will soon notice your comprehension and retention accelerate.&lt;br /&gt;&lt;/span&gt;</description><link>http://blog.arbingersys.com/2007/12/dormant-sticky-memory-and-layered.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-7288535708053890354</guid><pubDate>Thu, 13 Dec 2007 18:03:00 +0000</pubDate><atom:updated>2008-01-17T13:01:50.507-07:00</atom:updated><title>Sometimes Faster From Scratch: An ASP.NET Hit Counter</title><description>Some users of a SharePoint site I maintain wanted to add a hit counter to their statically created HTML newsletters. After a little looking, I decided it would probably be best to use the FrontPage hit counter that is included with IIS/FrontPage Server Extensions. It was already there, after all, and should be easy to implement. Well, it was easy to implement, except for one major snag: it wouldn't fire unless you were authenticated.&lt;p&gt;  &lt;img style="border:0; padding:3px" src="http://blog.arbingersys.com/images/hitcnt1.png" alt="" /&gt;&lt;br /&gt;Try as I might, I wasn't able to figure out how to get it to work for anonymous     users. Google     didn't turn up much. I even posted on the Microsoft newsgroups, and got no response.     I spent a day and a half searching, trying this and that, and was no further along than     when I had started. No, I take that back. I was &lt;strong&gt;further behind&lt;/strong&gt;     than when I had started, because I had exhausted most of the possibilities I had     started out with.&lt;/p&gt; &lt;p&gt;     Because I was sort of stuck, I did a cost-benefit analysis (which makes it sound much     more scientific and rational than it actually was) of continuing to troubleshoot     the problem. I came to the conclusion that I had already spent more time troubleshooting     than it would have taken for me to develop a custom ASP.NET solution from scratch.&lt;/p&gt; &lt;p&gt;     So I switched gears, and began searching for code samples on the web. Within minutes,     I found one in Visual Basic that did most of what I wanted. Of course, I program in     C#, so I merely used it as a reference. I improved it a little, too.&lt;/p&gt;     &lt;p&gt; &lt;a href="http://www.arbingersys.com/dnlds/HitCounter.zip"&gt;(You can download the VS2005 project here)&lt;/a&gt;&lt;/p&gt; &lt;p&gt;     &lt;strong&gt;     Here's how it works:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;     The project consists of a single Default.aspx page, and an Access database, hitcount.mdb,     for tracking URLs and hits.&lt;/p&gt; &lt;p&gt;     The Default.aspx page is passed an address, in the form&lt;/p&gt; &lt;p class="code"&gt;Default.aspx?url=some/path/on/a/server.htm &lt;/p&gt; &lt;p&gt;     or even &lt;/p&gt;&lt;p class="code"&gt;Default.aspx?url=http://some/path/on/a/server.htm&lt;/p&gt; &lt;p&gt;     In truth, you could just pass in some arbitrary text and it would use that as an     identifier. Using a full URL, however, ensures that you don't get duplicates.&lt;/p&gt; &lt;p&gt;     If the hit counter finds the URL in the database, it gets the count, increments     it, converts it to a font (Arial), converts the font to an image, and returns as     Content-Type "Image/Gif".&lt;/p&gt; &lt;p&gt;     If it doesn't find the URL, it inserts it, and returns a count of 1. It's that simple. &lt;/p&gt; &lt;p&gt;     The only thing you may need to change for it to work on your server is line 50:&lt;/p&gt; &lt;p class="code"&gt; string datapath = Server.MapPath("\\hitcount\\hitcount.mdb");&lt;/p&gt; &lt;p&gt;     Set this to the directory on your web server where the database will live.&lt;/p&gt;&lt;p&gt;To use this in your static HTML pages, simply add a line like the following:&lt;/p&gt;&lt;p class="code"&gt;&amp;lt;img src="Default.aspx?url=some/path/on/a/server.htm" /&amp;gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;em&gt;A note on security: there is no check in the code to prevent someone from "borrowing"         the hitcounter and using it to store hits for their own pages. This might be a negligible         concern, but you could add some code to check the IP address where the request is coming         from if you are worried by this.&lt;/em&gt;&lt;/p&gt;</description><link>http://blog.arbingersys.com/2007/12/sometimes-faster-from-scratch-aspnet.html</link><author>noreply@blogger.com (JA)</author></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-6752176030157989585.post-8270974428113904220</guid><pubDate>Sat, 08 Dec 2007 18:01:00 +0000</pubDate><atom:updated>2007-12-13T11:02:08.458-07:00</atom:updated><title>Mr. Spolsky and the Work Is Life Principle</title><description>Mr. Joel Spolsky recently published a talk he gave to the Yale Computer Science     department, and in it proceeds to expound in his &lt;a href="http://www.joelonsoftware.com/items/2007/12/04.html"&gt;all or nothing manner&lt;/a&gt; why it sucks     to be an "in-house" programmer. That is, a programmer who earns his or her pay by     working for a non-software publishing company. The &lt;strong&gt;problem with his     reasoning&lt;/strong&gt; is that it seems to imply that there is &lt;strong&gt;no satisfaction at all to be     gotten from being an in-house programmer&lt;/strong&gt;. Life, in my experience anyway, fails to work in such an open or shut way. His arguments have some merit, but are just too far on the end of the spectrum. I work for a school district. Obviously software development is not the primary function; however, there are many reasons why my job doesn't suck.  &lt;p&gt;     Another more subtle implication falls out as well: The attitude that the only real     satisfaction you are going to get from life will be your job. This may just be a     result of the black or white rhetoric Mr. Spolsky uses. Certainly there     is satisfaction to be gotten from your work, and maybe a large amount.     But other things yield satisfaction as well.&lt;/p&gt; &lt;p&gt;     &lt;strong&gt;You never get to do things the right way. You always have to         do things the expedient way.&lt;/strong&gt; This is true in some cases, but not all.     There have certainly been projects that I've worked on that solved a one-time problem     and were never used again. Or that weren't given enough thought and had a horde of changes     to make later. Or were handed to me on the assumption that the users wanted it when in     fact no research had been done to actually find out if they did, and it turned out     that they &lt;em&gt;didn't&lt;/em&gt;. But not in every case. &lt;strong&gt;We've also hit the bullseye&lt;/strong&gt; and     wowed the crowd, and not simply by luck, but by     careful analysis. Especially as I settled in and learned how things operate, and     what is ultimately important to the distirict. These are things you can only really     learn over time. And usually,     there has been time to go back and refactor those projects     that gained traction. (Whether I've been too lazy when I had the chance     is another story.) &lt;/p&gt; &lt;p&gt;     My boss &lt;em&gt;has&lt;/em&gt; made decisions about technology without really     listening to my thoughts. For instance, he went full bore on SharePoint without     me really buying into it. In the end, we do a lot of customization for SharePoint,     and my opinion of it has changed. But usually, and especially in the technologies     used for development, I get to have my way. If I want to use Perl, Ruby, Java, .NET,     it's usually left up to me. The reason for this is that my boss and the people who     use our software &lt;strong&gt;really don't care how we do our job&lt;/strong&gt;, just that     we do it. This may sound callous, but it's actually not. I don't particularly care     how a teacher teaches, &lt;strong&gt;as long as my child learns&lt;/strong&gt;. I sense that     in a company that produces software, a manager will have a much deeper knowledge     of underlying technologies, and as a result may be more difficult to sway.&lt;/p&gt; &lt;p&gt;     &lt;strong&gt;You [don't] get to make beautiful things.&lt;/strong&gt; This is certainly arguable.     Some applications we've produced were quite attractive, and using them was so     intuitive and easy that no training was required. You may think the function of     the software was boring (generating reports for student test scores based on demographics),     but the software itself was attractive. Software is for doing things,     and usually those things, by themselves, are nothing you are going to consider &lt;em&gt;beautiful&lt;/em&gt;     or &lt;em&gt;artistic&lt;/em&gt;. Because software is a tool, just like a car, or a portable     MP3 player. Even if it's the most beautiful MP3 player in the word, it     still has a function that can be mimicked by less beautiful players. The "beauty",     if any exists, is in the way the tool &lt;strong&gt;looks&lt;/strong&gt; and &lt;strong&gt;behaves&lt;/strong&gt;.     This can apply to in-house software as well as any other.&lt;/p&gt; &lt;p&gt;     &lt;strong&gt;Management doesn't care about you.&lt;/strong&gt; This is another cut-and-dry statement that just doesn't map to real life. My job in the school district falls under "support services", along with food service (school lunches), maintenance, business services, etc. Teaching is the district's primary function. Yet I have never run into the attitude that I simply don't matter. When principals and teachers are shown how they can look up their student's progress throughout the school year within seconds, they usually appreciate the support services I provide. And don't think management isn't paying attention to that.&lt;/p&gt; &lt;p&gt;     Some management may be more     callous than others, I realize, but in my experience, most people are     not so simple minded that they don't understand that the primary function of an organization     usually requires many secondary functions in order to operate, and they know that those func