Thursday, November 10, 2011

PostScript and Temporary Goodbye

So I honestly thought I'd be talking about Erlang and various processing tactics relating to it. I was also potentially planning to do and talk about some more work on clomments, the main off-hours project I've been trying to keep at. As an absolute last resort, I was going to dive into some more Elisp, or maybe finally talk about the shell-ui project. It didn't occur to me that the week would see me dusting off an old implementation of postscript for some new code.

I haven't actually written new code for it, but I have gone over what was there and flipped through the few resources I could find. Postscript, the language, is actually more versatile than I gave it credit for the last time around. To the point that I might actually want to revisit postscript.plt as opposed to just porting what I had over to CL.

Going through the first of the resources I mention above, it actually occurred to me that I could likely accomplish some of my goals by typing .ps directly. I'm going to try that first, but still keep the embedding option in my back pocket to hedge against some issues. First impressions are that the stack is going to annoy the ever-loving fuck out of me. I remember trying out Forth a while ago and not really minding that aspect of it[1], but I honestly can't see how to write a procedure that takes more than two or three parameters without doing some serious head-scratching. If you want the argument order to be at all sensible, it seems like you need to chain exch, dup and pop like a fiend, with an index or two thrown in for good measure. That means functional programming in this language is at least slightly hobbled from the get-go, but it does force you to make the smallest possible procedure to avoid being shot in the foot inadvertently.

The other reason that implementing PostScript in another language would probably be a good idea is, ironically, performance. Not writing performance, obviously, but processing performance at the printer end. If you take a look at that third resource I link to and open it in your editor of choice, you'll notice that the author deliberately chooses one and two letter identifiers for their procedures.

Ordinarily, I'd agree that that's awful, but keep in mind that every extra character in an identifier is one more that the printer will have to parse before outputting. Shortcutting grestore to gr or lineto to li doesn't seem like much, but compounded by the number of invocations per page, pages per document copy and then by copies per hour, I could see that getting scary pretty fast where frequent output is concerned. Now, that's really not much of a reason for me to actually apply this directly; I'd much rather write with human-readable function names and have an intermediate machine compress them down to a more compact format before sitting it in front of the printer. That's basically what I see using the lisp-based generator for; if I plan it out properly, the result will be a much easier language to work in that actually has more efficient output than a hand tuned chunklet of PS.

I was going to promise to post more related content soon, but frankly, as you can see from the new addition to the language bar, you aren't likely to see me for a month or so.


Footnotes

1 - [back] - Maybe it had some syntactic help; I no longer remember, never having done any serious development in it.

Friday, November 4, 2011

Objective Lisp

Stand back! I have dramatic pause an idea!

(defpackage :objective-lisp
    (:nicknames :ol)
    (:use :cl)
  (:shadow #:+ #:- #:* #:/ 
           #:member #:null
           #:= #:string= #:eq #:eql #:equal #:equalp
           #:length #:map #:mapcar #:concatenate)
  (:export #:+ #:- #:* #:/ 
           #:member #:null
           #:= #:string= #:eq #:eql #:equal #:equalp
           #:length #:map #:mapcar #:concatenate))
(in-package :objective-lisp)

;;; not, strictly speaking, relevant to what I want to discuss,
;;; but I figure I've already gone off the deep end just by
;;; writing this, so I may as well make it worth my while

(defun nullp (object) (cl:null object))

;;; or is it...
(defun memberp (item list &key key)
  (cl:member item list :key key :test #'=))

;; imagine other functions here

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmethod + ((num number) &rest nums)
  (assert (every #'cl:numberp nums))
  (apply cl:+ (cons num nums)))

(defmethod - ((num number) &rest nums)
  (assert (every #'cl:numberp nums))
  (apply cl:- (cons num nums)))

(defmethod * ((num number) &rest nums)
  (assert (every #'cl:numberp nums))
  (apply cl:* (cons num nums)))

(defmethod / ((num number) &rest nums)
  (assert (every #'cl:numberp nums))
  (apply cl:/ (cons num nums)))

(defmethod length (seq) (cl:length seq))

(defmethod map ((fn function) (l list) &rest lists)
  (assert (every #'listp lists))
  (apply #'cl:mapcar fn (cons l lists)))

;; and defined for other types here

(defmethod concatenate ((str string) &rest strings)
  (assert (every #'stringp strings))
  (apply #'cl:concatenate 'string (cons str strings)))

;; and again

(defmethod = (a b) nil)
(defmethod = ((a number) (b number)) (cl:= a b))
(defmethod = ((a string) (b string)) (cl:string= a b))
(defmethod = ((a character) (b character)) (cl:char= a b))
(defmethod = ((a symbol) (b symbol)) (cl:eq a b))
(defmethod = ((a cons) (b cons)) (cl:equalp a b))
(defmethod = ((a array) (b array)) (cl:equalp a b))
(defmethod = ((a structure-object) (b structure-object)) (cl:equalp a b))

;;; really, I should do the same for all the various
;;; comparison functions (>, >=, <=, <), but this is
;;; already longer than I'd like

Now, why would we want to do this terrible thing? Well, we probably wouldn't. I wouldn't ever straight-facedly recommend someone does this in any sort of production environment, but it does sand down some rather annoying corners[1].

What looking at CLOS this way gets you is the easing of a few syntactic binds at the cost of some performance.

  • For starters, we now have a generic = that can be called in pretty-much any situation[2]. That lets us define memberp and other functions without having to pass in a test function (= handles dispatch itself). It also lets us define map/concatenate and similar functions without specifying what the output type is expected to be[3].
  • As I note, it saves us from having a separate =, char= and string= and similar comparison operations.
  • Finally, if we define new types (such as matrix), we are not prevented from giving them a + or * method (and we can specifically define how to go about mapping, concatenateing or lengthing them, if it makes sense, as well as defining the correct new equality test without having to name it matrix=).

You could also do things like override + so that it performs concatenation for strings[4], though I'd shrink from going further and applying it to sequences in general, just because it's somewhat ambiguous.

Because all these calls are handled by the type system, you'd also get compile-time warnings about operations that aren't defined.

Incidentally, I know I'm not even remotely the first person to think of doing something like this on a lark. As usual, I just wanted to get an idea far enough out of my head to take a good look at it.

This has been a completely random thought about Lisp. Thank you for your time.


Footnotes

1 - [back] - In case you care, I stubbed my metaphorical toe on them a little while ago by trying to implement matrix operations in CL as a learning exercise only to find that I couldn't actually give them a + operation. It would have to be something like add or matrix-add. No world-ending obstruction, but slightly less than satisfying. At the time I implemented it as matrix-add.

2 - [back] - leaving aside the identity vs. pointer question that may actually need to be addressed differently in some cases

3 - [back] - Although, to be fair, we do lose the ability to do something like (concatenate 'string "Hello " (list #\t #\h #\e #\r #\e #\!))

4 - [back] - Since that seems to be well accepted in most languages by this point.

Monday, October 24, 2011

John McCarthy

Sunday, October 16, 2011

Ruby vs. Python Briefly

Ok, so I figure it's about time to live up to the title of this blog, since I've spent the vast majority of the language discussion firmly planted in parentheses. Aside from the fact that my company is starting a project in Erlang, I've also been scripting Python and Ruby pretty heavily.

They're actually not very different languages. Neither is perfect from my perspective[1], and neither sucks. If I had to, I could get work done in both (and having gone through the Ruby chapter in 7 Languages in 7 Weeks, I'm more inclined to look at Ruby for my next big project than I used to be). To start with, here's a boiled down, no-nonsense table that represents my perspective.

...is more annoying than...

len([1, 2, 3])
[1, 2, 3].length
"foo " + bar + " baz"
or
"foo %s bar" % bar
"foo #{bar} baz"
", ".join(["one", "two", "three"])
["one", "two", "three"].join ", "
map(lambda a: a + 1, [4, 3, 2, 1])
## still makes more sense 
## than join or len, though
[4, 3, 2, 1].map {|a| a + 1}
a = [4, 3, 2, 1].sort()
a[0]
[4, 3, 2, 1].sort[0]
nothing.jpg foo.methods.sort
require 'optparse'
require 'pp'
require 'fileutils'
import optparse, fileutils
## I also prefer the more granular 
## symbol access I get with python
sudo apt-get install ruby-full
irb
python

...is about as annoying as...

def aFunction(foo, bar):
    #do stuff
    return baz
def a_function(foo, bar)
  #do stuff
  baz
end
with tempfile.NamedTempFile() as tmp:
    tmp.write("Test test\n")
    ##more stuff    
    tmp.flush()
    popen(["lp", "-d", "a-printer", tmp.name()])
Tempfile.open() do |tmp|
   tmp.write("Test test \n")
   ## more stuff
   tmp.flush
   system("lp", "-d", "a-printer", tmp.name)
end

So I am slightly biased, but like I said earlier, not enough to actually decry either language. The biggest point in Ruby's favor is its handling of blocks (as seen in that tempfile pseudo-code). I like having an expression that says "Create an entity, do this stuff and then clean up", without having to clean up myself. Python doesn't like that.[2] Gotta admit, I boggled at the join syntax the first time around. Rhetorically, who the hell decided it makes sense that a join operation is something you do to the delimiter, rather than the list? In my opinion, it would even make more sense to make it a standalone function a-la len.

I really like the syntactic whitespace in Python.

def something():
    foo()
    bar()
seems like it's cleaner than the Ruby equivalent. Except that when I want to return the result of bar (which I do quite often, given that I much prefer functional programming to OO), I need to do so explicitly. Ruby has me waste an additional line on end, but returns implicitly. While I'm at it, Python libraries seem to be heavily anti-functional programming. They do the standard "OO" thing of exposing functionality via classes, but they also typically have a heavy reliance on side effects, which makes it harder than it ought to be to compose things. A recent example I had to go through involved using pyPDF and reportlab to process existing PDFs. You can do it, but the amount of fiddling involved is nontrivial if you want to decompose the problem properly because you need to do so by setting up multiple instances of PdfFileReader/canvas and making destructive changes to them.

Also, not represented in the table is how much easier it is to install python packages in Debian. While gem frequently errors on something, I've yet to find a package I need that I can't either apt-get or retrieve using python-setuptools. That's worth something (in fact, it's worth enough that I've been procrastinating on a ruby port of get-youtube-series, which used only default components in Python, but requires several installs in Ruby).

The last thing that table doesn't encompass is the version situation. That's a fairly major one from my perspective, but I'm not sure how serious it actually is. Python 3 has been out for quite a while, but it's not uncommon to see "Supports Python 2.7" on various frameworks/utilities. Squeeze still provides 2.6.6, Django still requires 2.[4-7] and Google app-engine is still asking for 2.5 (with 2.7 being supported as an "experimental" feature). That's less than encouraging. By contrast, Ruby 1.9 is fairly widely supported (though the Debian repos are still at 1.8.7). That just doesn't seem to bode well for the next version, regardless of how enthusiastic Rossum is about it.


Footnotes

1 - [back] - Though, to be clear, my opinion is that Ruby gets a damn sight closer than Python.

2 - [back] - Thank you Brendan Miller for pointing me to the with statement (documented here, here and here) which does emulate blocks well enough for my purposes.

Monday, October 10, 2011

Testing googlecl Posting

If it worked, you should be able to read this.

This post was posted via Emacs with help from the googlecl project

ScreenWM Follow-up

So I had enough theorizing last week, and am currently putting the setup through the Compaq Test™.

Preliminary observations are good; now that I've fixed the minor ui annoyances pertaining to screen and blog-mode, this is a very comfortable editing environment. I don't actually have the wireless drivers installed on this machine yet, so I'll have to plug into the router later in order to post this piece, but it's quite snappy considering the hardware I'm actually working with[1].

Hell, slime works pretty well too. Except that paredit[2] seems to have it in for me in various ways. It's nothing I can't work around with some judicious re-binding, but it's extensive enough that I don't want to attempt it today.

I started with a fresh install of Debian Squeeze[3] and basically just ran the following

## Basic dev tools
apt-get install emacs slime git-core mplayer lynx screen openssh-server gnupg
apt-get install sbcl python-setuptools ruby-full erlang

## app configuration
wget http://beta.quicklisp.org/quicklisp.lisp
su inaimathi -c "sbcl --load install.lisp"

## I. Fucking. Hate. Caps. Lock.
sed -i 's/XKBOPTIONS=""/XKBOPTIONS="ctrl:nocaps"/g' /etc/default/keyboard
/etc/init.d/console-setup reload

in order to get everything running the way I like. install.lisp contains

(load "quicklisp.lisp")

(quicklisp-quickstart:install)
(ql:add-to-init-file)

(ql:quickload :linedit)
(linedit:install-repl)

(with-open-file (s (merge-pathnames ".sbclrc") :direction :output :if-exists :append :if-does-not-exist :create)
  (format s ";;; Check for --no-linedit command-line option.
(if (member \"--no-linedit\" sb-ext:*posix-argv* :test 'equal)
  (setf sb-ext:*posix-argv* 
        (remove \"--no-linedit\" sb-ext:*posix-argv* :test 'equal))
  (when (interactive-stream-p *terminal-io*)
    (require :sb-aclrepl)
    (ql:quickload \"linedit\")
    (funcall (intern \"INSTALL-REPL\" :linedit)
             :wrap-current t)))"))

(ql:quickload (list :drakma :cl-who :cl-ppcre :cl-fad :hunchentoot :clsql :cl-smtp :cl-base64 :ironclad :trivial-shell))
(quit)

Which just configures quicklisp and linedit to run whenever I start sbcl. After that , it was just a matter of importing my Emacs settings[4], and .screenrc file. I didn't end up keeping the fancy settings I was thinking about last week, by the way. It currently contains, in its entirety

screen -t emacs emacs -nw

startup_message off

bind S split -v
bind s split
bind R remove
bind ^e screen emacs -nw
bind ^w screen webjump

markkeys "h=^b:l=^f:$=^e"

which is as basic as it could possibly be, except for the line that calls a program named webjump. That's actually a convenience script of my own devising that simulates my conkeror webjumps from the desktop machine. It reads

#!/usr/bin/ruby

require 'uri'

print "Webjump: "
input = gets.chomp.split(" ", 2)

def get_url(input)
  jump = input[0]
  query = URI.escape(input[1])
  jumps = {
    "youtube" => "http://www.youtube.com/results?search_query=#{query}\&aq=f",
    "stockxchange" => "http://www.sxc.hu/browse.phtml?f=search\&txt=#{query}\&w=1\&x=0\&y=0",
    "google" => "http://www.google.com/search?q=#{query}\&ie=utf-8\&oe=utf-8\&aq=t",
    "wikipedia" => "http://en.wikipedia.org/wiki/Special:Search?search=#{query}\&sourceid=Mozilla-search",
    "gmail" => "http://mail.google.com"  
  }
  jumps[jumps.keys.find{|k| k =~ /#{jump}/}]
end

url = get_url(input)
if url
  system("lynx", url)
else
  puts "Can't find webjump '#{input[0]}'"
end

which is quite useful when I need to search for something quickly. I'm thinking about changing it such that it just takes a command-line option for which webjump to use so that I could actually keybind google-search as opposed to webjump (I've observed that "go something something" is used far more commonly than any of the others).

Like I said, that's it. It's an extremely minimal system, and it doesn't have any kind of multi-monitor support, but it gives me the important little comforts I've been used to (like tabbing between applications and convenient, keyboard-based browsing) without the need to start up an instance of X[5]. That greatly increases the universe of useable machines for me.

The only things I'm still missing:

  • a klavaro-analogue I still have no way of practicing typing from the command line (which is kind of ironic)
  • more shell-friendly bindings for paredit
  • multi-monitor support which I have no idea where to even start on


Footnotes

1 - [back] - An old Presario R3000 with a 1.4ghz processor and 256MB ram.

2 - [back] - Which I've installed, and actually gotten to like under X, at the recommendation of a friend from the Toronto Lisp User Group. It's actually fantastic, but there are various key that just barf when you try using it from terminal. The default bindings for slurp, barf, forward and back s-exp operations are outright ignored, and it does something funky to my home and end keys so that they insert odd square-bracket escape sequence instead of doing what they say on the key. It's paredit because, all of the above works just fine in other modes.

3 - [back] - Since the Compaq still had a copy of Parabola running from last time.

4 - [back] - Including the steadily-growing blog-mode, which I've added several functions to since I started writing this piece.

5 - [back] - Also, conveniently, lynx doesn't let me waste any time on Reddit, since I can't actually post or upvote from it.

Thursday, October 6, 2011

Screen for StumpWM/Xmonad Users - GNU Screen as a window manager

The first part is exposition. If you're just interested in how to set up Screen as a StumpWM analogue, skip to the next heading.

I've been thinking about window management again, for my own purposes and bouncing around between combination of Xmonad, StumpWM, XFCE and GNOME (as well as trying the two tiling managers standalone).

Using XFCE and GNOME standalone really wasn't going to do anything for me, I already knew that. Using the lightweights on theiir own had a few minor annoyances that I tried to fix by running them together.

Out of the box, neither Xmonad nor StumpWM

  • support a nautilus/thunar style file display (and I sometimes need it)
  • auto-connect to my wireless network
  • automatically mount external media (or watch for new drives being added and mount them as necessary)

It's becoming clear that I don't want a regular point-and-click interface by default anymore, except for one or two specialized tasks for which nautilus --no-desktop should suffice.

I also don't really use removable media anymore. Maybe my memory is a bit clouded, but it seems that I used a lot more USB keys, DVDs and CDs back when I was a Windows/OS X user. It's possible that I was just being stupid, but it seemed like the easiest way of sharing data between two different machines[1]. That flat out doesn't happen anymore. We only have Linux machines in the house now (split between Debian, Parabola and Ubuntu, in order of descending quantity), so when I want to share data between them, I use scp, or possibly rsync depending on the specific situation. I don't do backups to DVD or CD anymore; I just use hard drives and the only computer that needs to play DVD media is in the livingroom[2]. I also don't install things from CDs, except for Debian itself.

Finally, connecting to my wireless network isn't automatically handled, and I do still need to do that with my netbook, but I can work around it[3]. Granted, I could have just memorized how to do it via iwconfig and friends, but this way is simpler from the interface perspective.

Bottom line; I don't need a desktop environment anymore. I'm good with the plain window manager. So it looks like GNOME is coming off my own desktop this weekend and Stump is getting re-instated as the manager of choice. The thing is, I also have a few old machines lying around that chug noticeably under any sort of graphic interface. And it turns out that if I'm willing to ditch nautilus, and fend for myself in terms of mounting media/connecting to networks, then I can go all the way to terminal.

I've been using GNU Screen as a way of deploying Lisp applications, but looking over the keybindings and man page, it looks like it can serve as a respectable alternative to a tiling window manager.

Screen WM

The default control combination is C-a instead of C-t, and the keys are significantly different, and you can't extend it in Lisp[4], but it looks like a fairly simple .screenrc file can turn it into Stump-Lite. Here's a quick breakdown, assuming the default bindings:

  • C-a ? shows you the help screen.
  • C-a c starts a new terminal in the same session (when you re-attach later, you'll have both of these)
  • C-a | splits the screen vertically (note that screen doesn't automatically start a second terminal). Equivalent to C-t S
  • C-a S splits screen horizontally. Equivalent to C-t s
  • C-a TAB moves to the next split
  • C-a X removes the current split
  • C-a C-a pulls the other terminal. Equivalent to C-t C-t
  • C-a n/C-a C-n cycles to the next terminal (C-a p/C-a C-p cycles backward)

In other words, out of the box, you've got the same basic window management shortcuts this way. And if you feel like remembering extra keys, feel free to commit the above to memory. As for me, my .screenrc file is going to look something like

startup_message off

bind S split -v
bind s split
bind R remove
bind ^e screen emacs -nw

On a machine where I plan to use terminal exclusively, I'll also add

escape ^t
bind ^t other

to mirror the StumpWM keys I'm already used to.

Incidentally, that last line in part one is what got me convinced that screen could credibly replace X for my purposes (assuming I'm working anywhere other than my dual-screen setup). It seems like you can wire up arbitrary shell commands and bind them to keypresses (use exec instead of screen if you don't want to start a new window for them). I left it out, but you can also put regular screen calls in .screenrc like so

split
resize 60
screen -t lynx lynx
screen -t emacs emacs -nw
focus
screen -t top top
focus

in order to customize your startup routine. I'm sure I could get more complex than that, but it illustrates the point. That snippet starts me off with a horizontal split. The top frame is emacs, the bottom frame is top and lynx is running in the background.

The stuff that I'll be missing this way is

  • A dmenu-like command (it seems like you can't have screen prompt for user input to then use in a keybinding; I'll have to do more research. The only thing I'd do with this is setup some lynx webjumps in any case.)
  • X windows (so no GIMP, gitk or a graphic browser on my dev machine, which is actually a good thing on balance since that'll reduce Reddit use)
  • Resizing mode (you can resize windows in screen, but you do it by typing in a height/width in lines/cols to set the width to, rather than the Stump resize mode where you can incrementally tweak windows)
  • A run-or-raise equivalent (the C-t C-e binding as above will actually start a new emacs every time rather than switching to it if one already exists)

Given how my .stumpwmrc is shaping up, I don't think this'll be a big sacrifice. The thing I think I'll miss most is actually gitk.

I'll let you know how it goes.


Footnotes

1 - [back] - Whether they were both mine and sitting in my room, or not and lying on a table in the OCAD student lounge.

2 - [back] - And has a standard GNOME 2 setup out of deference to my wife, who hasn't taken the Computer Nerd prestige class remaining a regular nerd.

3 - [back] - Been meaning to do a writeup on that little UI layer I'm slowly using to coat my shell experience. pack and unpack have already left me smiling several times.

4 - [back] - :(

Sunday, October 2, 2011

Old Machines Redux

I was actually just contemplating my current backup setup here, when I came across two things. First, a thread asking about these experiences, and second, another discarded machine.

It even had the Windows License key sticker still attached. No severed monitor cable to laugh at this time, but still.

Booting this one up showed me an, actually, respectable 1.7 Ghz processor and a full gig of Ram. Cracking the case also yielded a couple of surprises. One, whoever last used this machine had it hooked up to a vacuum cleaner running in reverse for at least six months. The amount of dust was insane. To the point that I had to don a facemask/goggles and clean it out outside[1]. Two, this thing actually contained an old Micro ATX motherboard[2].

I was surprised, because it had the standard, giant tower typical to desktops of the past few years. One trip to the local computer store provided an appropriate Micro/Mini ATX case.

Transplanting the board over was straightforward, except for two things. First, since this was a found machine, I didn't have that little reference card for what each of the case pins does, so I had to create my own based on what the current case hookup looked like. The USB connectors also took some guesswork since the two cases actually had different types of plugs for them[3].

Second, I don't happen to have a Mini-ATX-sized ethernet card lying around, so the existing one had to tolerate some minor mods.

At more or less this time, one of my cats decided it was a good time to put their tail in the path of my chair wheels. I had to take ten minutes or so to calm the little guy down before going further.

I tested whether the thing boots before arranging all that hardware in the case. There really isn't much room in these, it doesn't even look like I can get a second hard drive in unless I want to leave it hanging outside somewhere. The drive that was in the original case was fucked (which I assume is why this unit was disposed of), so I had to pop in one of my spares. It ended up getting a 40GB Western Digital. In the process of picking a new drive, I realized that two of the three stashed ones were out too. I'll strip them for magnets later, I guess.

This brings my lifetime hard drive failure record by brand up to

  • 3/6 - Maxtor
  • 1/1 - Fujitsu
  • 0/4 - OCZ
  • 2/43 - Western Digital

Which actually isn't too shabby overall.

This time, I decided to throw a copy of Parabola on it.

The installation was entirely uneventful except for the hard-drive and cat-related problems I've mentioned already. The only challenging part was actually folding everything down into a case that small. There's just a birds nest of wires in there, but it boots and runs properly.

I'm not entirely sure what I'll use this one for, since we have a media PC in the living room already. I might just get a VGA to RCA converter and toss it in the bedroom with our CRT. The other option is to use it as a random dev box to play around with.


Footnotes

1 - [back] - No pictures of that sadly, though I can assure you that the dust is now providing nesting materials for no fewer than nine neighborhood birds.

2 - [back] - As an aside, it seems that "Micro ATX" is a larger form factor than "Mini ATX", which I thought was a little odd. I assume that the team developing the "Mini ATX" was done second and had to settle for the less impressive name

3 - [back] - The old one had one chunky plug that fit over the entire USB pin-set while the new one actually had a separate wire per port and each one had a separate plug for the ground pin

Saturday, September 24, 2011

"Mapping CLOS", or "Yak Shaving for Fun and No Profit"

Just a quick update today; I have more work than I think I can handle for the foreseeable future. A combination of problems I've never solved at work, some odd contract requests and several projects I've gotten myself into in my personal time. You already know about the CLSQL/Hunchentoot crash course, and the clomments system. You probably don't know about the last two, one of which sort of chains off of clomments (and the other I'm conveniently ignoring in this post other than that sentence).

The background is that I wanted conversation threads in clomments. As in, when you see a comment, you should be able to reply to it. You can store that kind of thing in a relational database, but it feels a bit hackish. Especially if you want the system to scale out. I've looked at a couple of nosql databases in the past and have been looking for an excuse to use them in practice. Well, it turns out that storing hierarchical data is relatively simple in them. I picked CouchDB for no particular reason (quicklisp has support for cl-mongo, clouchdb and chillax), and tried out a few things. Unfortunately, clouchdb has at least one annoying bug that prevents me from using it. It wouldn't normally, I've already sent a one-liner patch to the author, but he's currently working at one of these Disney-style-IP places that own your intellectual property, dna and all derivative works of both (I'm paraphrasing), so he can't actually merge that patch any time soon.

Ok, so I guess I'm using chillax then, which kind of saddens me because I'd prefer a project that used the same naming convention as the project I'm using it for, but that's beside the point. In either case, interacting with Couch from CL is kind of a pain in the ass. It involves tons of alists and many traversals of the same, which isn't horrible, but definitely not as pleasant as interacting through CLOS the way I have been in CLSQL. Looking around, there doesn't seem to be an ORM-style thing already built for Couch or Mongo, so I figured I'd try my hand at one as part of the clomments project. I don't have anything workable yet, I've just been playing around so far, but the first leg of research has turned up a rather annoying implementation detail that segues nicely into the title of this article.

Do you know how to map over a CLOS object?

If you've never thought about it before, feel free to go investigating. Hopefully, you have a better time than I did. If you just want to do it in SBCL, it's actually fairly simple

(defun slot-names (class)
  (mapcar #'sb-pcl:slot-definition-name
          (sb-pcl:class-slots class)))

(defun map-slots (fn instance)
  (loop for slot-name in (slot-names (class-of instance))
        collect (funcall fn slot-name (slot-value instance slot-name))))

The problem, as the astute among you have already noticed, is sb-pcl. That's actually an SBCL-only CLOS library that implements various functions found in the spec. Other lisps don't make the same decisions, so if you want to make these functions portable, you need to do some more work. Either going what I call "the insane route" (which I took some cues from) and conditionally define each function that deals with a class, or the clsql-sys route in which you don't specify a package for these functions, but rather :use different modules conditionally as part of your package definition. That looks like

(defpackage #:clos-utils
    (:use #:common-lisp)
  (:shadowing-import-from 
   #+openmcl-native-threads #:ccl
   #+cmu #:pcl
   #+sbcl #:sb-pcl
   #+lispworks #:hcl
   #+allegro #:mop
   #+clisp #:clos
   #:class-slots #:slot-definition-name))

(in-package :clos-utils)

(defun slot-names (class)
  (mapcar #'slot-definition-name
          (class-slots class)))

(defun map-slots (fn instance)
  (loop for slot-name in (slot-names (class-of instance))
        collect (funcall fn slot-name (slot-value instance slot-name))))

That's still not fully portable, by the way. openmcl apparently calls class-slots class-instance-slots if it doesn't have openmcl-native-threads. But it's reasonably close to portable. Having come down off a week or so of Python/Ruby scripting, that...was a lot more work than I expected to do for a task like this. Hopefully this saves someone else out there some time (or causes someone to contact me, pointing out a much easier way of doing it). Anyhow, that's phase one of creating CLOS-based bindings for CouchDB, which will then let me succinctly work on clomments which should eventually increase the amount of Freedom on the net by some small increment.

Tuesday, August 30, 2011

Intermission

Break time.

I have been researching various options for image sizing, and I do plan on finishing up the crash course sometime soon, but I saw a thread that sent me off thinking about something else.

Someone on Reddit linked to an article about static site generators (and their effect on software freedom), in which the writer posits that it would be really nice to have a Free Software competitor to Disqus.

Well, I'm not going to say "delivered" yet, because this is the merest hint of an attempt at a solution, but I threw something together in the couple of hours I could spare between postfix woes and various marketing initiatives at the company.

I won't try to go over any of the code, that was through the github link, in case you missed it, but I want to formalize a little of my thought process on where this actually needs to go to be a real competitor, just so that I can remember when I go back to work on it this weekend.

The Idea

Is, simply enough, to offload the comment system for a given page to a third party server. Either so that the maintainer of that page doesn't have to fart around with databases, or for that extra performance kick (since the first server no longer needs to serve up dynamic content at all), or because software as a service is in again, I really don't know.

Anyway, the point is, instead of keeping your own comment database locally, you just echo a static page with a line or two of trixy javascript, and your comments get pulled in on the clients' time.

So the basic feature set here is pretty sparse:

  • Track comments on a per-page basis
  • Allow adding/liking/disliking/reporting of individual comments
  • Allow replying to individual comments (not strictly necessary, but nice)

It wouldn't be any fun if that was all though. It would also be nice if

  • you could submit to [social site of choice] through one button click
  • you could edit your comments
  • spam sites could be tracked
  • spam/low-rating comments could be omitted/hidden by default

Finally, to support the Free Software objective, it needs to:

  • be AGPL (so that anyone can run their own for their friends if they feel like)
  • be written in a Free language (which is no problem at all)
  • allow full data exports (so that you could move pages between servers if you wanted)

This is a reasonably simple problem. Not trivial, but it looks like it would take a couple of weeks of serious work to knock out something useful. To the point that I have no idea how building a company around doing it is even possible. The only thing I can imagine is that the data storage and security around it is somehow more challenging than organizing the actual functionality (which is consistent with my observations of other software).

Well, that's that. Kick the tires, but don't blow my server up, and I'll do some more hacking on it later in the week.

EDIT:

It's been brought to my attention that people would like to get stuff running right now. Ok, didn't really plan for it but here goes (assuming you're on Debian)

  1. apt-get install sbcl mysql-server cl-sql
  2. git clone https://github.com/Inaimathi/clomments.git
  3. create a database and user and change the definition of *db-spec* in package.lisp to match
  4. install quicklisp Is there still a lisper that doesn't use this? I'm getting kind of sick of mentioning it.
  5. cd clomments; sbcl --eval "(ql:quickload :clomments)" If you're on a 64 bit machine, you may get some warnings starting up. Continue through them, and it should be fine (it's to do with cffi bindings for clsql)
  6. Once in SBCL
    (create-view-from-class 'comment)
    (create-view-from-class 'page)
  7. Go to http://localhost:4242/test in a browser

I promise I will streamline this as soon as I get the regulation 4 hours of sleep I'm entitled to per week.

Tue, 30 Aug, 2011
EDIT:

Also, I'm perfectly aware why there are extra spaces this time; it's because I'm starting to use regulation xhtml markup instead of relying on Blogger's seemingly flaky spacing feature. It seems like you can only set it globally for a given blog though, so I can't just switch over one post at a time. I'll need to go through my archives and wrap everything in <p> tags first.

Tue, 30 Aug, 2011