Here are some of the libraries I reach for when I want to do stuff:
- alexandria for general utilities
- chirp for twitter
- yason for processing/generating json
- cl-ppcre for regular expressions
- cxml for processing/generating XML
- Postmodern for database work
- Hunchentoot for web work
- drakma as a HTTP client
- cl-pdf and Vecto for making PDFs and PNGs
- function-cache for memoization and caching
What are some of the libraries you use for day-to-day programming tasks? Email me and I'll summarize next week.
EDIT: Indeed, as people on Reddit pointed out, installing Lein is simply downloading a script and running it. Installing CIDER from MELPA was also easy. The two, however, aren’t completely compatible at the moment because CIDER from MELPA wants nREPL 0.2.7 at least and Lein only pulls in 0.2.6 (even though, I believe the current is 0.2.10).
It has been five years since I last tried Clojure. I feel like I should try it again.
I don’t want to beat my head against Leiningen for even ten minutes this time. Is there some way to reasonably use Emacs + Clojure without have to install and configure CLASSPATHS and Mavens and Ants and JDKs?
It seems SWANK-CLOJURE has been deprecated in favor of CIDER. The CIDER doc says how to configure Leiningen or Boot for use with CIDER. Is there some way that I can avoid Leingingen and Boot? Or some way that I can click one ‘Install’ button and have Leiningen and Boot work?
This year, I have had the dubious pleasure of being the Local Organizer for the European Lisp Symposium 2015, which is now exactly one month away; in 31 days, hordes of people will be descending on South East London to listen to presentations, give lightning talks and engage in general discussions about all things Lisp - the programme isn't quite finalized, but expect Racket, Clojure, elisp and Common Lisp to be represented, as well as more... minority interests, such as C++.
Registration is open! In fact, for the next nine days (until 29th March) the registration fee is set at the absurdly low price of €120 (€60 for students) for two days of talks, tutorials, demos, coffee, pastries, biscuits, convivial discussion and a conference dinner. I look forward to welcoming old and new friends alike to Goldsmiths.
Progress of the GitLab installation and migration scripts has been very good. With some last tests to go, we’re confident we can complete installation and migration on Friday March 20th, 2015. The window 08:00h - 12:00h UTC has been designated to perform installation and migration. For more details continue reading below the fold.
During that window existing git repositories with public visibility (i.e. http or gitweb access — git:// access not counted) will be migrated into GitLab 1. There’s no impact on Subversion, CVS, mercurial, darcs or bazaar repositories. Users with git repositories without http or gitweb access, who want to use GitLab for their project are kindly requested to contact the site admins.
After the migration, migrated repositories will be removed from their current physical path locations. Repositories will only be accessible through GitLab and stored in a location managed by GitLab. Users who want to make backups should do so by keeping a local clone of their git repository(-ies).
As part of the migration, GitLab accounts will be created for all users of the common-lisp.net system. Each user with a .forward file in their home directory on the system will receive an account confirmation e-mail. This confirmation request is valid for 48 hours. All accounts must be confirmed before use - the system blocks accounts until confirmed. Your account can be used immediately after confirmation, even during the migration window. As part of the migration, SSH keys found in the user’s home directory will be imported into the user’s GitLab account. You will receive an e-mail to confirm this happened.
As part of the migration, Gitlab groups will be created, mirroring the common-lisp.net “project” concept. Users currently part of a project by virtue of being member of a unix group on common-lisp.net will be assigned GitLab group membership for the mirror group in the role of Owner. Notification mails are sent out due to the migration process. Projects that wish to use GitLab’s more fine-grained permisssions2 can do so after migration completes.
As part of the migration, existing git repositories will be imported into GitLab under the group or account which mirrors the common-lisp.net project or account. All repositories will have a public visibility. Each repository on common-lisp.net becomes a project in GitLab. This means each repository gets an issue tracker and wiki set up. Any project that wishes not to use those can turn those off in the Settings page for the GitLab project after the migration. Due to the nature of the migration process, the Administrator account will be a member of all GitLab projects and groups. This can be corrected after the migration.
After the migration, gitweb access won’t be available anymore. It will be replaced by URL redirection to GitLab. git:// protocol support won’t be available anymore either.
While the migration is in progress, SSH access to the system will be blocked to prevent repositories being updated while migrated.
Should you want to start a new project in GitLab, please ask the site admins to create a group for it.
Please note that this is just one of the steps in the restructuring plan for common-lisp.net. Further steps as indicated in 3 will be executed later and will affect repositories for other version control systems as well as other services.
In case of questions please follow up to clo-devel at common-lisp.net: the announcements mailing list is closed for posting.
Lispjobs — Software Developer for Quantum Processor Development group, D-Wave Systems, Vancouver, British Columbia@2015-03-16 20:02 · 11 days ago
D-Wave is seeking an experienced Software Developer to join the Processor Development group. The successful candidate will work closely with physicists to develop and optimize measurement routines used to calibrate D-Wave’s quantum processor.
You will be self-driven, but comfortable working closely with others. You will share responsibility for designing, implementing, testing and maintaining the suite of software necessary to support the testing and operation of D-Wave's quantum computing hardware.The software is implemented in Common Lisp (SBCL) and is an integral part of the quantum computing system. It is used for a variety of purposes including calibration, operation, testing and benchmarking.
See the full announcement: http://www.dwavesys.com/careers/software-developer
The AHRC-funded research project that I am a part of, Transforming Musicology, is recruiting a developer for a short-term contract, primarily to work with me on database systems for multimedia (primarily audio) content. The goal for that primary part of the contract is to take some existing work on audio feature extraction and probabilistic nearest-neighbour search indexing, and to develop a means for specialist users (e.g. musicologists, librarians, archivists, musicians) to access the functionality without needing to be experts in the database domain. This of course will involve thinking about user interfaces, but also about distributed computation, separation of data and query location, and so on.
There are some potentially fun opportunities during the course of the contract, not least working with the rest of the Transforming Musicology team. The post is based at Goldsmiths, and locally we have some people working on Systems for Early Music, on Musicology and Social Networking, and on Musical Memory; the Goldsmiths contribution is part of a wider effort, with partners based at Oxford working on Wagnerian Leitmotif and on a Semantic Infrastructure, at Queen Mary working on mid-level representations of music, and in Lancaster coordinating multiple smaller projects. As well as these opportunities for collaboration, there are a number of events coming up: firstly, the team would hope to have a significant presence at the conference of the International Society for Music Information Retrieval, which will be held in Málaga in October. We don't yet know what we will be submitting there (let alone what will be accepted!) but there should be an opportunity to travel there, all being well. In July we'll be participating in the Digital Humanities at Oxford Summer School, leading a week-long programme of workshops and lectures on digital methods for musicology. Also, in November of 2014, we also participated in the AHRC's "Being Human" festival, with a crazy effort to monitor participants' ; there's every possibility that we will be invited to contribute to a similar event this year. And there are other, similar projects in London with whom we have friendly relations, not least the Digital Music Lab project at City University and the EU-funded PRAISE project at Goldsmiths.
Sound interesting? Want to apply? There's a more formal job advert on the project site, while the "person specification" and other application-related materials is with Goldsmiths HR. The closing date for applications is the 27th March; we'd hope to interview shortly after that, and to have the developer working with us from some time in May or June. Please apply!
My deep apologies to all Windows users for misfortunate unicode
character at top screen, which caused an exception at the very start
of ECL startup. Version 15.2.21 was released little
prematurely. Proper fix is released a week after problem was noticed,
because we did want to test it first, and to fix a few regressions
between 13.5.1 and 15.2.21.
This release is at least as good as 15.2.21. Enchantments include:
- ECL now builds easily with Visual Studio 2012, including builds with
- Autoconf scripts are upgraded to version 2.69,
- Fixed stack direction, so builds against upcoming gcc 5.0 should
- Broken --with-sse=yes configure flag works once again,
- Fixed stable-sort bug (corner case on sorting strings),
Big thanks to everyone, who helped with testing and development :-)
Regarding other news, we're still on a move. We barely settled on
gitorious and now it's aquired by gitlab, so we're moving forward, to
gitlab. But not everything at once. Full migration will be performed
at 14th march, but repositories are already synced, and wiki is moved
as well (some links are broken, but it's a matter of a few
edits). Anyone interested on participating, is invited to join a team
at https://gitlab.com/embeddable-common-lisp .
I often persist Common Lisp services via screen. This works pretty well. One downside is the handling of paredit keys through an ssh+screen pipeline: by default, a key like C-right produces the text 5C in my buffer.
To fix, I’ve been copying these lines around to my .emacses:
(define-key input-decode-map "\M-[1;5A" [C-up]) (define-key input-decode-map "\M-[1;5B" [C-down]) (define-key input-decode-map "\M-[1;5C" [C-right]) (define-key input-decode-map "\M-[1;5D" [C-left]) (define-key input-decode-map "\M-[1;3A" [M-up]) (define-key input-decode-map "\M-[1;3B" [M-down]) (define-key input-decode-map "\M-[1;3C" [M-right]) (define-key input-decode-map "\M-[1;3D" [M-left]) (define-key input-decode-map "\M-[1;9A" [M-up]) (define-key input-decode-map "\M-[1;9B" [M-down]) (define-key input-decode-map "\M-[1;9C" [M-right]) (define-key input-decode-map "\M-[1;9D" [M-left])
With those keys configured, my most-used paredit commands work as expected.
This might not be the best fix, but I couldn’t find anything better via search. If you know better, tell me.
- arrow-macros — arrow-macros provides clojure-like arrow macros and diamond wands. — MIT
- buffalo — A LALR(1) parser generator for Common Lisp — MIT/X11
- burgled-batteries — Lisp-Python interface — MIT
- burgled-batteries.syntax — Embedded Python syntax for burgled-batteries — MIT
- cl-libyaml — A binding to the libyaml library. — MIT
- cl-mlep — cl-mlep is a Common Lisp Machine Learning library for Educational Purposes. — MIT
- cl-netstring-plus — A simple library for sending and receiving messages with a netstring-like encoding
- cl-reddit — Reddit client api library — BSD
- cl-yaml — A YAML parser and emitter. — MIT
- cl4store — 4store — BSD
- clavier — Clavier: A Common Lisp validation library — MIT
- clipper — File attachment library. — MIT
- clods-export — Common Lisp OpenDocument spreadsheet export library — MIT
- common-doc-plump — Translate a Plump DOM into a CommonDoc document and back. — MIT
- descriptions — A domain model meta level description library — MIT
- eazy-process — Yet Another Portable Library for Process Handling / Subshell Invokation — MIT
- hl7-client — hl7-client - send HL7-Messages over TCP/IP with MLLP — BSD
- hl7-parser — Decoder and Encoder for ER7 formatted HL7-Messages — BSD
- hyperluminal-mem — High-performance serialization library, designed for untrusted data — LLGPL
- linewise-template — Linewise file/stream processor for code generation etc. — BSD-3-Clause
- lisp-namespace — Provides LISP-N --- extensible namespaces in Common Lisp. — LLGPL
- media-types — Query and compare media types. — MIT
- ryeboy — Riemann client — BSD
- shellpool — A library for running external programs from Common Lisp — MIT/X11-style
- transparent-wrap — A signature-preserving wrapper generator for functions and macros. — LLGPL
- trivial-debug-console — A library for a well-behaved debug console
- vertex — A markup language with TeX syntax. — MIT
- workout-timer — Workout timer — MIT
Removed projects: 3bil, cl-binaural, cl-openstack, cluck, lisphys, oct.
I removed 3bil because it is effectively unmaintained and apparently unused. cl-openstack is no longer maintained; a replacement library will soon be available. I can't build cl-binaural, cluck, lisphys, or oct on SBCL any more.
Sorry to skip a release in February. A holiday and a number of library problems conspired to hold off the release. As a result there are a larger number of updates this month than usual. If you have any problems, please don't hesitate to discuss them on the mailing list or email me directly.
To get this update, use (ql:update-dist "quicklisp").
I believe there is one design decision in MGL-MAT that has far reaching consequences: to make a single matrix object capable of storing multiple representations of the same data and let operations decide which representation to use based on what's the most convenient or efficient, without having to even know about all the possible representations.
This allows existing code to keep functioning if support for diagonal matrices (represented as a 1d array) lands and one can pick and choose the operations performance critical enough to implement with diagonals.
Adding support for matrices that, for instance, live on a remote machine is thus possible with a new facet type (MAT lingo for representation) and existing code would continue to work (albeit possibly slowly). Then one could optimize the bottleneck operations by sending commands over the network instead of copying data.
Contrast this with what I understand to be the status quo over on the Python side. The specialized Python array libs (cudamat, gpuarray, cudandarray) try to be drop-in replacements for - or at least similar to - numpy.ndarray with various degrees of success. There is lots of explicit conversion going on between ndarray and these CUDA blobs and adding new representations would make this exponentionally worse.
Torch (Lua) also has CUDA and non-CUDA tensors are separate types, and copying between main and GPU memory is explicit which leads to pretty much the same problems.
All of this is kind of understandable. When one thinks in terms of
object.method()), this kind of design will
often emerge. With muliple-dispatch, data representation and
operations are more loosely coupled. The facet/operation duality of
MGL-MAT is reminiscent of how CLOS classes and generic functions
relate to each other. The anology is best if objects are allowed to
shapeshift to fit the method signatures.
Speaking of multiple-dispatch, by making the operations generic functions following some kind of protocol to decide which facets and implementation to use would decouple facets further. Ultimately, this could make the entire CUDA related part of MGL-MAT an add-on.
Here’s something that cost me some time debugging today:
(progn (setf (logical-pathname-translations "bug") (list (list "bug.lisp.*" "/tmp/bug.lisp"))) (print (list :bug (probe-file #p"bug:bug.lisp"))) (terpri))
The above form will print
(:BUG NIL) in SBCL, even if /tmp/bug.lisp exists. That’s because
#p"bug:bug.lisp" is approximately equivalent to
#.(parse-namestring "bug:bug.lisp"), which happens at read time. Since the BUG logical host isn’t defined by then, SBCL parses the string as a pathname with a name of “bug:bug” and a type of “lisp”.
Using the string
"bug:bug.lisp" instead of the form
#p"bug:bug.lisp" defers namestring parsing until runtime. By then, the logical host is defined and the translation kicks in, and the probe-file returns #p”/tmp/bug.lisp” as expected.
(Namestring syntax and parsing is, of course, implementation-dependent. In Clozure CL, a colon is always interpreted as a logical host delimiter, and the progn form above signals an error. To use a colon in a “normal” pathname, it must be prefixed with a backslash in its namestring.)
Amazon.com is the leading online retailer in the United States with over $75 billion in global revenue. At Amazon, we are passionate about using technology to solve business problems that have big customer impact. CORTEX is our next generation platform which handles real-time financial data flows and notifications. Our stateless event-driven compute engine for dynamic data transforms is built entirely in Clojure and is crucial to our ability to provide a highly agile response to financial events within the organization. We leverage AWS to operate on a massive scale and meet high-availability, low-latency SLAs. If you have over 7 years of experience as a hands-on software developer, have worked with distributed systems, and have experience in start-ups or possess a "start-up mentality", keep reading.
- Obsess over software performance and challenge yourself and others to deliver highly scalable, low latency, reliable and fast computation platforms?
- Possess great ideas and know how to solve problems, but also follow through with a clean and maintainable implementation?
- Have a high bar for coding excellence and a passion for design and architecture?
- Want to work with really cool technology such as Clojure, JVM, and AWS tools?
- Either live in or desire to live in the greater Seattle area and want to work somewhere that you can have a large scale impact but still work on a small team?
If your answers are yes, this may be the role for you! Combining a startup atmosphere with the ambition to build and utilize cutting-edge reactive technology, the Cortex team at Amazon is looking for a passionate, results-oriented, and innovative Sr. Software Engineer who wants to move fast and have fun, while being deeply involved in solving business integration problems across various organizations within Amazon.
- 3+ years of experience designing, building, deploying, operating, scaling, and evolving distributed systems and high-volume transaction application in a 24/7 environment
- 7+ years of industry experience in software development in Java or C++
- Exceptional customer relationship skills including the ability to discover the true requirements underlying feature requests, recommend alternative technical and business approaches, and lead engineering efforts to meet aggressive timelines with optimal solutions
- Bachelor’s Degree in Computer Science or related field or equivalent work experience
- Background or strong interest in Clojure
- Experience with cloud technologies from AWS
- Proficiency in a Unix/Linux environment
- Graduate degree (MS/PhD) a plus
- Experience mentoring and developing junior SDEs
If interested, please send your resume to firstname.lastname@example.org.
New release, which is mainly current state of git HEAD (plus a few fixes). Contains numerous bug-fixes in comparison to 13.5.1 and is last, which follows date-based version convention. It's time to finally release ECL 1.0 ;-).
Development moves to gitorious (https://gitorious.org/embeddable-common-lisp), same as wiki (previous content is inaccessible now, but once subscription is renewed, I'll start to migrate content from there). In GIT topic - current permissions will be preserved. Just drop me a line with gitorious login, and I'll add person on corresponding permission level to project.
Mailing list and website are staying at SF for now, but I'd really like to switch the latter to something more manageable. Also, SF have lately problems with stability, what is quite annoying.
More on maintainer topic:
My name is Daniel Kochmański (you may meet me on IRC and over internet under the nick "jackdaniel") and I'm willing to spare at least a few hours a week for this amazing project to keep it alive. More on progress ideas and myself might be found on mailing list archive - I ask for comments, suggestions and discussion (and forgiveness for some potential dumb ideas I might propose), to develop them better. Also, I do ask for help.
While I will try to set up Linux/Unix environment to check builds and try to fix problems on various operating systems (I'm thinking about putting vagrant in use), I have no access to neither Windows or OSX environments, so I will have no clue, if any commit will break builds on these. Testers for these platforms are crucial imo.
Badder because clicking on a name will produce a permalink such as
this: *DOCUMENT-MARK-UP-SIGNATURES*. Clicking on locative types such
[variable] on the page that has just been linked to will take you
to the file and line on github where
I’m hiring a hacker or two for a startup.
Please send CV, portfolio & phone number.
Devon Sean McCullough
(Write him for location, description, etc)
Some deem it unfortunate, others are not bothered by it at all, but the fact remains that Common Lisp does not have a standard GUI toolkit. It does have a native toolkit called McCLIM, but due to general outdated-ness it is not a very attractive choice. Generally I'm not one to linger long on decisions when it comes to learning something, so after quickly evaluating the options I chose to try CommonQt, a library to allow using the Qt framework with CL.
The first thing I wrote with it was a primitive GUI for a chat client, but while I did finish it, I never went far with it. That is, until Parasol came along. Parasol makes heavy use of Qt, and unfortunately working with CommonQt forces you to write in a rather un-lispy style. This isn't surprising, since Qt itself is a C++ framework and thus matching idioms probably isn't as easy.
Fortunately for us, CommonQt already goes a long way of bridging the gap, but not quite far enough. In an effort to bring GUI writing with Qt closer to home, I created Qtools. In this entry we're going to make use of CommonQt and Qtools to show off what writing a basic GUI in CL can look like.
What we're going to do for this mini project here is write a primitive Twitter client. It'll have a dialog to let users log in via twitter and a main window to display new statuses, as well as let you post some. To make this all possible we'll make use of Chirp and the aforementioned Qtools. In order to understand this tutorial you'll need a moderate understanding of Common Lisp, some prior knowledge of UI programming, and a lack of fear to look things up in the hyperspec, Qt docs, and other documentation. Let's get to it.
(ql:quickload '(:chirp :qtools))
Now, as usual we'll create a new package for ourselves to live in.
(defpackage #:titter (:use #:cl+qt) (:export #:main)) (in-package #:titter) (named-readtables:in-readtable :qtools)
Here you'll notice two deviations from the norm. First, we're not
:use-ing the standard CL package, but rather CL+QT, which is a package from Qtools that provides convenient access to CL as well as Qt functionality. Second, we need the
in-readtable statement to make use of CommonQt's reader extension for Qt methods.
Now we'll finally start with writing our own UI. Defining top-level widgets happens with
define-widget, which exactly mirrors
defclass, with the exception of some extensions that are irrelevant for this tutorial.
(define-widget login (QDialog) ())
This will be our dialog to log in with. You can already test it now, but you won't get much beyond a blank window.
(with-main-window (w (make-instance 'login)))
Time to get on to the meat of a widget, its contents. Logging in to twitter can't happen via password anymore unless you get special permission from twitter to do so. We'll instead use twitter's oAuth PIN method. To give that to the user, we'll need to show them a link, let them type in a PIN and have a button to confirm or something.
(define-subwidget (login url) (#_new QLabel login) (#_setTextFormat url (#_Qt::RichText)) (#_setTextInteractionFlags url (#_Qt::TextBrowserInteraction)) (#_setOpenExternalLinks url T))
That's quite a few new things here so let's go through them.
define-subwidget as you probably expect defines a widget on our login widget, called
url. This initializes to a QLabel instance with our main widget set as parent.
#_new is the CommonQt equivalent to the
new operator in C++. While widgets defined on the CL side need to be initialised as usual using
make-instance, Qt-native classes need to be instantiated using
#_new. Next in the body we set a couple of properties of our label using C++ methods with the
#_ reader macro. Make sure to type the method names in their exact case or CommonQt won't be able to find them. These property changes are necessary to allow clickable URLs.
Don't launch your widget quite yet or you'll be disappointed to find it as bleak and empty as before. We'll get to that in a minute, but first let's define the rest of our components real quick.
(define-subwidget (login pin) (#_new QLineEdit login) (#_setPlaceholderText pin "PIN")) (define-subwidget (login go) (#_new QPushButton "Login" login))
Alright, that was easy. Now, the subwidgets won't appear on your main widget magically as the system could not have any idea how you want them to be placed. For this we need layouts.
(define-subwidget (login layout) (#_new QVBoxLayout login) (#_setWindowTitle login "Login to Twitter") (#_addWidget layout url) (let ((inner (#_new QHBoxLayout))) (#_addWidget inner pin) (#_addWidget inner go) (#_addLayout layout inner)))
Rather simple layout stuff by GUI standards. A vertically oriented layout to hold our label and a horizontal layout that holds the PIN text field and button. Now you may launch your widget again and marvel at the impressively unexciting UI.
In order to make things react in Qt you need to employ their system of slots and signals. Slots are signal receptors and signals are identifiers as well as data-carriers for events. So, when a button gets clicked a signal is fired. Whatever slot is connected to the button on that signal then gets called with the signal properties for arguments. Since we have a button in our form, let's make a slot for it.
(define-slot (login done) () (declare (connected go (released))) (#_QMessageBox::information login "OOoOo" "¯\(°_o)/¯"))
What we've done here is defined a slot on our widget called
done, which takes no arguments and is connected to the
released signal (which provides no properties). You'll notice here that Qtools uses declarations like a sly fox in order to make things a bit easier and lispier. Firing up the widget now will already give you the expected effect.
This is all good and well, but it has rather little to do with Twitter, so we'll change that. First, we need to fetch the URL to have the user authenticate with and display it on the label.
(defun set-url (widget) (let ((url (chirp:initiate-authentication :api-key "D1pMCK17gI10bQ6orBPS0w" :api-secret "BfkvKNRRMoBPkEtDYAAOPW4s2G9U8Z7u3KAf0dBUA"))) (#_setText widget (format NIL "Please enter the pin from <a href=\"~a\">twitter</a>." url))))
Then we need to change our login slot definition to actually make use of this function.
(define-subwidget (login url) (#_new QLabel login) (#_setTextFormat url (#_Qt::RichText)) (#_setTextInteractionFlags url (#_Qt::TextBrowserInteraction)) (#_setOpenExternalLinks url T) (set-url url))
But, we're only half-way there. We still need to actually evaluate the PIN that the user passes back to get the proper authentication credentials. We'll do that in our
(defvar *logged-in* NIL) (define-slot (login done) () (declare (connected go (released))) (setf *logged-in* NIL) (#_setCursor login (#_new QCursor (#_Qt::WaitCursor))) (handler-case (chirp:complete-authentication (#_text pin)) (error (err) (#_QMessageBox::critical login "Error!" "Failed to login.") (#_setText pin "") (set-url url) (#_setCursor login (#_new QCursor (#_Qt::ArrowCursor)))) (:no-error (&rest args) (declare (ignore args)) (setf *logged-in* T) (#_close login))))
So, what happens here? First we have a variable to keep track of the login status and then we do some cursor displaying to let the user know that stuff is happening the back. Next we have error handling in case our authentication fails for some reason, which just resets things to let the user try again. However, if we succeed the widget closes itself and thus returns. To verify that everything logged in smoothly after you've tried it, you can use
So in, little under 50 lines we wrote a complete login dialog for our application. While we're fired up like that, let's move on to writing the actual client. We'll want a field to type new status updates into, a button to submit the tweet, and a list to hold new tweets from our home timeline.
(define-widget client (QWidget) ()) (define-subwidget (client status) (#_new QLineEdit client) (#_setPlaceholderText status "What's old?..")) (define-subwidget (client tweet) (#_new QPushButton "Tweet!" client)) (define-subwidget (client timeline) (#_new QListWidget client) (#_setWordWrap timeline T) (#_setTextElideMode timeline (#_Qt::ElideNone))) (define-subwidget (client layout) (#_new QVBoxLayout client) (#_setWindowTitle client "Titter") (let ((inner (#_new QHBoxLayout))) (#_addWidget inner status) (#_addWidget inner tweet) (#_addLayout layout inner)) (#_addWidget layout timeline))
Mostly similar to what we had before, modulo widgets and properties. Now we need another big function to take care of submitting a tweet. This happens as before in a slot since we need to handle a button press.
(define-slot (client tweet) () (declare (connected tweet (released))) (cond ((<= 1 (chirp:compute-status-length (#_text status)) 140) (#_setCursor client (#_new QCursor (#_Qt::WaitCursor))) (handler-case (chirp:statuses/update (#_text status)) (error (err) (#_QMessageBox::critical client "Error!" (format NIL "Failed to tweet: ~a" err))) (:no-error (&rest args) (declare (ignore args)) (#_setText status ""))) (#_setCursor client (#_new QCursor (#_Qt::ArrowCursor)))) (T (#_QMessageBox::information client "Huh?" "Tweet must be between 1 and 140 characters long!"))))
Here we have a simple check to make sure the status has the allowed length (chirp takes care of URLs for us), sends out a new status update, and handles the potential errors. Simple, verbose stuff. Looking at our main window now
(with-main-window (w (make-instance 'client)))
We'll be able to send tweets, but nothing appears in the list. For that we need to cast some more advanced spells. To handle adding new items to our list we'll define our own signal and slot.
(define-signal (client new-tweet) (string string)) (define-slot (client new-tweet) ((user string) (status-text string)) (declare (connected client (new-tweet string string))) (format T "~&Got new tweet from ~a: ~s" user status-text) (#_addItem timeline (format NIL "@~a: ~a" user status-text)))
As you can see, the signal definition holds a type argument list. We'll want to transmit the username and the status text and connect the slot to the widget itself. We'll use that to emit the signal once we get new tweets.
Since the main thread will be occupied with the UI we need to launch an additional thread to take care of incoming tweets. However, we also need to make sure that the thread shuts down with the UI as well and only launches after the UI is already available. To do this we'll define a general launch function.
(defun main () (let ((thread)) (with-main-window (w (make-instance 'client)) (setf thread (bt:make-thread #'(lambda () (chirp:start-stream :user #'(lambda (message) (when thread (process-message message w) T))) (format T "~&Shutting down tweet stream")) :initial-bindings `((*standard-output* . ,*standard-output*))))) (setf thread NIL)))
Aside from the
with-main-window form, the guts here is the
start-stream chirp function which will handle stream communication for us for as long as messages come through and our handler function returns with a non-NIL value. Thus we can check for thread termination and let everything clean up nicely once the UI exits. However, this makes use of one function we haven't defined yet,
process-message. Let's change that.
(defun process-message (message client) (format T "~&Message: ~a" message) (when (typep message 'chirp:status) (signal! client (new-tweet string string) (chirp:screen-name (chirp:user message)) (chirp:xml-decode (chirp:text-with-expanded-urls message)))))
Here we emit a signal to our
client using the
new-tweet signal and the mentioned arguments. Chirp takes care of URLs and entities. If you launch the client now using the
main function, you should see your own status update, as well as everything that happens on your home timeline. That means we're pretty much done already! As a final addition, let's make the
main also handle logging in.
(defun main () (unless *logged-in* (with-main-window (w (make-instance 'login)))) (when *logged-in* (let ((thread)) (with-main-window (w (make-instance 'client)) (setf thread (bt:make-thread #'(lambda () (chirp:start-stream :user #'(lambda (message) (when thread (process-message message w) T))) (format T "~&Shutting down tweet stream")) :initial-bindings `((*standard-output* . ,*standard-output*))))) (setf thread NIL))))
Aaand done, ship it.
There isn't much else to the general concepts of UI programming with Qt other than widgets, signals, and slots. Everything else lies in knowing about the respective classes and methods, which is more vocabulary than concept. However, I hope that this quick introduction proved interesting and neat enough for you to take making UIs with Common Lisp into your list of feasible things.
I'd always welcome suggestions and ideas for extensions or modifications to Qtools to make working with Qt even more lispy than it is currently.
Thank you for your time.
You may read the source code in one piece here.
Additional note for the curious: You might be wondering how this all works in combination with Qt. As you know from your C/C++ experience, it uses different method naming conventions and types and all that wahoo. And indeed, the culprit for hiding this from you is Qtools. It translates types and method names into their C++ equivalents behind your back. This goes a long way towards bridging the gap. As an exercise, we'll take a look at the entire transformation sequence of a simple slot definition.
(define-slot (widget foo) ((text string)) (print text))
The first thing that happens is that Qtools translates this into (surprise!) a method definition:
(defmethod %widget-slot-foo ((widget widget) (text string)) (declare (slot foo (string))) (with-slots-bound (widget widget) (print text)))
Here we see another instance of using declarations to bridge the gap. You can of course also use
defmethod directly if you prefer, and for some scenarios you really might. This also reveals why we need to
cl+qt rather than
cl, since Qtools needs to shadow the default
defmethod. However, no worries, you can still use it as normal, the only difference is the extra declaration handling. Now, this method definition needs to be purified, as CL itself won't accept the slot declaration:
(progn (eval-when (:compile-toplevel :load-toplevel :execute) (progn (set-widget-class-option 'widget :slots '("foo(const QString&)" %widget-slot-foo)))) (cl:defmethod %widget-slot-foo ((widget widget) (text string)) (with-slots-bound (widget widget) (print text)))
And even more interesting things happened now! First what you see is Qtools' widget external redefinition capabilities. Using
set-widget-class-option we can change the class definition form of the widget outside of its
define-widget form. In this case we set a new
:slot value (which is a CommonQt
qt-class option). Here we also see that Qtools correctly translated the name and arguments of our slot definition into the equivalent name for the C++ side and links it to the method we define. The method that remains is a standard CL method definition. The
with-slots-bound is a special form that performs a
with-slots on all available slots of the class. Subwidgets get translated to class slots and using
with-slots-bound they become automatically available through their respective symbols. This was added mostly because using accessors to refer to subwidgets becomes so ludicrously tedious, repetitive, and verbose that binding them all by default is the much less painful alternative.
Qtools offers quite a bit more than is outlined here such as additional type translation, menu definition, and finalization to name some. Take a look at the docs to see what it has in store.
I haven't come across this anywhere yet, but I think it's worth writing a quick entry about, just so that it's referable. So, writing tests is a common enough occurrence in programming and Common Lisp is no exception. The vast amount of testing frameworks is both a sign of the repeated desire to have a comfortable way to write tests and the general ‘I can do it better’ syndrome prevalent in Common Lisp. However, this blog is not about those things, but about another, much easier aspect: Running tests.
Having an easy way to run your tests, possibly even automated, is great. Most frameworks don't go into that, so the first instinct of any test writer is to just dump all tests into a file and have a function to run them. Hopefully the tests will be segregated into their own package or system. Still, it's far less than stellar to have to know what the test system is called, load it manually and then run some project-specific test function.
Luckily, if you're using ASDF for your systems there's a way to make this all streamlined and convenient. The first thing you will want to do is define a separate system for your tests that depends on whatever testing framework you use and the system to test, of course. That way the tests won't have to be loaded if the user doesn't need them. Then, in the system definition of your main project you add a new property to connect the two:
(asdf:defsystem my-system ... :in-order-to ((asdf:test-op (asdf:test-op :test-system))))
What this does is tell ASDF that if you perform the
test-op on your system, it will delegate that to calling
:test-system, which should be adapted to whatever you named your test system, naturally. This means that you can now call
(asdf:test-system :my-system) and have it automatically load and test your proper test system. But, we aren't quite there yet, there's one last thing we need to do, which is to tell ASDF how to execute our test suite.
In order to do this we'll need a method on
asdf:perform, the function responsible for performing any kind of ASDF operation on a component or system. This method definition should be in the source of your test system and can either call or directly replace your main test function:
(defmethod asdf:perform ((op asdf:test-op) (sys (eql (asdf:find-system :test-system)))) (run-tests))
Once that's in, you can freely call
asdf:test-system and it should just work. Doing it this way is beneficial both because it gives users a streamlined interface to perform tests and because it is neatly integrated with the rest of the build system and thus automatable.
Edit: As Orivej Desh helpfully pointed out to me in an e-mail, there's an alternative way to link the
test-op to your test running function. Instead of adding the
defmethod you can add a
:perform property to your test system:
(asdf:defsystem test-system ... :perform (asdf:test-op (o c) (uiop:symbol-call :test-system-package :run-tests)))
Seeing this, there's yet another alternative of doing things, which is to put everything into your main system:
(asdf:defsystem my-system ... :in-order-to ((asdf:test-op (asdf:load-op :test-system))) :perform (asdf:test-op (o c) (uiop:symbol-call :test-system-package :run-tests)))
However, I'm not a fan of this last approach as it requires you to put information of the test system (the name of the main test function) into your main system. Using the
:perform property in your test system is definitely a cleaner way to do it than to add your custom
Library does not build without warnings. As mentioned a little while ago, ql:quickload normally muffles warnings, even, unfortunately, for non-Quicklisp projects. The Quicklisp dist build environment does not muffle any warnings, and any that occur will break the build for the library. Make sure you use the :verbose t option to ql:quickload to see any warnings that crop up during compilation.
Library does not build at all. I think this happens when someone sees a library that seems cool, they find it is absent from Quicklisp, and they request its addition without trying it first. Please try it first! It's easy to try libraries: fetch the code, put it into ~/quicklisp/local-projects/, look for *.asd files, and use ql:quickload to load one. If it doesn't load, it may prove difficult for me to try to add it to Quicklisp. And if it doesn't have *.asd files, I can't add it to Quicklisp at all.
Library depends on another library that is not available in Quicklisp. It's fine to request the addition of multiple related libraries. It helps if you specify the order in which they need to be added to work.
Library system name conflicts with existing system. This happens sometimes when a library bundles its own private copy of a library already present in Quicklisp. In that case, it is usually best to unbundle the private copy, but I can also work around it on my end if necessary. Conflict also happens when someone just doesn't know that a system name is already in use. To check for conflicts, use (ql-dist:provided-systems t) to get a list of existing systems in Quicklisp.
Inspired by p3rl.org, I made l1sp.org in 2008 to make it easy to link to the Common Lisp HyperSpec. The HyperSpec is fantastic work, but the URLs are not all that memorable (with good reason). Its canonical location has also occasionally changed. It was once hosted on Xanalys’s website, but now it’s on LispWorks.com, and although it seems unlikely, it may move again in the future.
l1sp.org has memorable links. l1sp.org/cl/car will take you to the page in the CLHS that defines “car”. l1sp.org/cl/22.214.171.124.2 will take you to section 126.96.36.199.2, Constraints on the COMMON-LISP Package for Conforming Programs. l1sp.org/cl/glossary/function_designator will take you to the glossary entry for “function designator”.
I intend to host and maintain l1sp.org indefinitely. If the CLHS moves from LispWorks to some other domain, l1sp.org will be updated to match. (If, in the future, I can no longer host or maintain it, the source code is on github, and I would be happy to transfer the domain to someone new.)
If you want to link to CL stuff, consider using l1sp.org.
Verisk Health builds a smarter healthcare ecosystem through analytics. Our 1,500+ global professionals work at the intersection of high tech, healthcare, and "big data" in order to realize audacious aspirations for our healthcare system. Be it eliminating fraud, waste, and abuse; guiding population health management with data-driven insights; improving revenue cycles for our clients; or re-envisioning support systems for new models of healthcare delivery, we hold ourselves to a single standard: having immediate and outsized impact for our clients, and by extension, the broader health community. To find out more about us click on the link below.
- 4 yr. college degree majoring in Computer Science, Electrical Engineering, or related field.
- 5 yr. experience as a full time professional software developer designing and building both system-level and application software using ANSI Common Lisp required.
- 5 yr. experience with expert system development, employing both forward and backward chaining rule systems required.
- 3 yr. experience building CLOS based object-oriented and knowledge-based systems required.
- 3 yr. experience building practical applications of Artificial Intelligence required.
- 5 yr. experience following a structured Software Development Methodology that has a defined software development life-cycle required; with recent Agile experience preferred.
- 3 yr. experience with Source Control Management software required, CVS or Subversion is preferred.
- 1 yr. experience working with natural language authoring environments preferred.
- 2 yr. experience building Ontologies preferred.
- 2 yr. experience writing and refining software requirements and experience writing and developing from software requirements required.
- 1 yr. experience using Oracle and writing SQL is preferred.
- Excellent verbal and written communication skills required.
- Experience with object oriented programming and design required.
Principle Responsibilities and Essential Duties:
- Updates job knowledge by researching new technologies and software products; participating in educational opportunities; reading professional publications; maintaining personal networks; participating in professional organizations.
- Implements new features and change requests based on requirements and technical design specifications.
- Unit tests software. Architects and designs new software functionality.
- Triage, debugs and troubleshoots software issues. Participates in code reviews by reviewing and providing feedback of others work.
- Creates software system and integration test plans. Executes software test plans for system and integration testing.
- Release Management: builds and packages releases for deployment.
- Creates technical documentation: software requirements and technical design specifications.
First, make sure the license terms of the code allow for its redistribution. I can't add things with restrictive licenses or with licenses that are missing or unclear.
Second, make sure it works on more than just one implementation of Common Lisp. Quicklisp is for portable libraries. (In the future, I hope to make it easy to create separate new dists specifically for implementation-specific code, but even then, the main Quicklisp dist will be for portable libraries.)
As a side-effect of how I build and test the dist, it must also work on SBCL on Linux/AMD64. That means, unfortunately, that a great portable library that works on three different Windows CL implementations, but not on Linux, cannot be added. I hope to fix this limitation in the future.
Third, make sure the library has certain ASDF system definition metadata: :license, :author, and :description. It also should have a README file in some form or another. A note about the README: it should give at least short overview of what the library is for. "The Foo library is an implementation of Ruby's Snorfle in Common Lisp" is not a good overview; give me an idea of what it actually does, instead, e.g. "The Foo library fetches and parses movie showtime information." It's good to also provide information about how to report bugs and how to contact the author.
Fourth, make sure it builds with ASDF, rather than an external-to-Lisp build mechanism. I can't add libraries that require special configuration or action outside of ASDF. For example, if you have to edit a source file to configure library or resource directories before building, I can't add it to Quicklisp. If the library can be loaded with just (asdf:load-system ...), it's good.
Finally, let me know about it. I prefer to track requests via github's issue system, but you can also send me an email as well. It suffices to write something like "Please add the foo library, which is available from http://www.example.com/foo.tgz. The homepage is http://www.example.com/foo/."
It's important to note that I don't consider a library's quality or purpose when adding it to Quicklisp. It doesn't matter if you're submitting your own library. If you want it added, and it fits the above criteria, I will almost certainly add it.
There are a few exceptions: projects that require complicated or obscure foreign libraries, projects that can only be downloaded via some ad-laden link system like SourceForge, projects that use CVS, and anything else that makes it difficult for me to fetch or build the project.
When you open a github issue for a library, I'll occasionally update the issue's status. I will add issue comments if I have any problems building, or if any required bit of information (license, ASDF metadata, README) is missing.
Barring any problems, when the github issue for a library is closed, the latest Quicklisp dist has been released and it includes the new library. (Sometimes I mess this up, so if it seems like the library is still missing after a dist update, feel free to get in touch.)
How about updates? Many libraries do not need any extra work to get updated regularly in Quicklisp. For example, if a library can be downloaded from an URL like "http://example.com/cool-project/cool-project-latest.tgz", Quicklisp will detect when a new file is posted. For libraries downloaded from version control systems like git, updates are also automatically fetched. Only when a library uses a fixed URL per version is it necessary to open a github issue for updates.
Quicklisp dist updates happen about once per month. If the library is updated upstream, those updates will only be reflected after the next Quicklisp dist update. Each dist update freezes the state of the Quicklisp library "world" until the next monthly update.
If you'd like to see the process in action, watch the quicklisp-projects issue page for a month to see how things typically work.
If you have any questions about the process, feel free to get in touch.
Google AutoValue: what in Lisp would take a few hundred lines max in Java is over 10000 lines not counting many, many libraries. Just WOW!
Thus, Java has macros too, it's just that they are 10 to 100 times more programmer-intensive than Lisp macros. I feel like I'm back in the dark ages.
Even for "normal" programming without new macros, a program I wrote both in Java and in Clojure was about 4 times bigger in Java (and that's despite using AutoValue). I also took ten times longer to write and debug the Java program (despite having written the Clojure program before, so no hard thinking whatsoever needed), with a frustrating edit-compile-run cycle many orders of magnitude slower. Part of the difference is my being much more experienced in Lisp than in Java, but even accounting for that, Java is slower to develop with.
The Java code is also much harder to read, because you have to wade through a lot of bureaucracy — each line does less, and so may be slightly faster to read, yet takes no less time to write, debug, modify, test, because of all the details that need be just right. Yet you must read and write more Java, and it's therefore harder to get the big picture, because there is less information available by screenful (or mindful) and much more noise. The limitation on available information is not just per screenful but also per file, and you find you have to jump constantly through so many files in addition to classes within a file; this is a lot of pain, even after accounting for the programming environments that alleviate the pain somewhat. Thus the very slight micro-level advantage of Java in readability per line is actually a big macro-level handicap in overall program readability.
Lack of both type aliasing and retroactive implementation of interfaces also means that type abstraction, while possible with generics and interfaces (themselves very verbose, though no more than the rest of the language), will require explicit wrappers with an immense amount of boilerplate, if not reimplementation. This strongly encourages programmers to eschew type abstraction, leading to more code explosion and much decreased maintainability.
Also, because function definition is so syntactically cumbersome in Java, programs tend to rely instead on big functions with a lot of side-effects, which yields spaghetti code that is very hard to read, understand, debug, test or modify — as compared to writing small conceptually simple functions that you compose into larger ones, as you would in a functional programming language.
The lack of tuple types is also a big factor against functional programming in Java: you'll need to declare a lot of extra classes or interfaces as bureaucracy just because you want a couple functions to pass and return a few values together (some people instead use side-effects for that — yuck). You could use a generic pair, but that leads to horrible types with many<layers<of<angle,brackets>>> which is very hard to read or write, and doesn't scale to larger tuples; of course, the need to declare types everywhere instead of having them inferred by the compiler means that even with tuples of arbitrary size, you'll need to spell out long unwieldy types more often that you'd like. Ignorants complain about the number of parentheses in Lisp, but just because of the size increase, there are a lot more parentheses in my Java program than in my Lisp program, and if we are to include all curly, angle and square brackets, that will be another many-fold increase.
Java 8 makes the syntax for functional programs slightly easier, and AutoValue makes it slightly less painful to bundle values together, but even with these improvements, Java remains extremely verbose.
The standard library is horrible, with side-effects everywhere, and a relatively poor set of primitives. This leads to the ugly habit of having to resort to "friend" classes with lots of static methods, which leads to a very different style of invocation and forces more bureaucratic wrapping to give things a unified interface. The lack of either CLOS-style generic functions or Clojure-type protocols mean you can't add decent interfaces to existing data-structures after the fact, making inter-operation with other people's code harder, whether you decide to adopt your own data-structure library (e.g. a pure functional one) or just try to extend existing ones. Lack of multiple inheritance also means you have to repetitively repeat a lot of boilerplate that could have been shared with a common mixin (aka trait class).
All in all, Java is just as heavily bureaucratic as I expected. It was developed by bureaucrats for bureaucrats, mediocre people who think they are productive when they have written a lot of code for a small result, when better tools allow better people to write a small amount of code for a big result. By analogy with programming languages said to be a variant of something "on steroids", I'd say that Java is a semi-decent programming language on valium. As to what template is sedated, I'd say a mutt of Pascal and Smalltalk. But at least it's semi-decent, and you can see that a lot intelligent people who understand programming language design and implementation have worked on it and tried to improve upon the joke of a language that Java was initially. Despite the bureaucracy, the sheer amount of talent thrown at the language has resulted in something that manages to not be bad.
This hard work by clever people makes Java so much better than Python, an attractive nuisance with lots of cool features that lead you into a death by a thousand cuts of small bad decisions that amplify each other. Superficially, Python looks like a crippled Lisp without macros and with a nice toy object system — but despite a lot of very cool features and a syntax that you can tell was spent a lot of time on (yet still ended up with many bad choices), Python was obviously written by someone who doesn't have a remote clue about semantics, resulting in a lot of pitfalls for programmers to avoid (there again with side-effects galore), and an intrinsically slow implementation that requires a lot of compile-time cleverness and runtime bureaucracy to improve upon.
In conclusion, I'd say that Java is a uniformly mediocre language that will drag you down with bureaucracy, which makes it rank well above a lot of overall bad languages like Python — but that's a very low bar.
Does this rampant mediocrity affect all industries? I'm convinced it does — it's not like these industries are fielded by better people than the software industry. Therefore it's an ever renewed wonder to me to see that the world keeps turning, that civilization endures. "A common man marvels at uncommon things; a wise man marvels at the commonplace." — Confucius
I just started systematically managing Quicklisp HTTP logs, so I will soon present information like this on a regular basis.
The promise of MGL-PAX has always been that it will be easy to generate documentation for different libraries without requiring extensive markup and relying on stable urls. For example, without PAX if a docstring in the MGL library wanted to reference the matrix class MGL-MAT:MAT from the MGL-MAT library, it would need to include ugly HTML links in the markdown:
"Returns a [some-terrible-github-link-to-html][MAT] object."
With PAX however, the uppercase symbol MAT will be automatically linked to the documentation of MAT if its whereabouts are known at documentation generation time, so the above becomes:
"Returns a MAT object."
The easiest way to tell PAX where to link is to let it generate the documentation for all libraries at the same time like this:
(document (list mgl-mat:@mat-manual mgl:@mgl-manual))
This is the gist of what MGL-PAX-WORLD does. It has a list of stuff to document, and it creates a set of HTML files. Check out how its output looks on github pages. Here is a good example of cross-links. It's easy to play with locally: just get the gh-pages branch and call UPDATE-PAX-WORLD.
I uploaded a new release of Clasp today (January 25th, 2015) that brings a lot of stability improvements, improved performance, improved compatibility with the Common Lisp standard and ASDF, SLIME and Quicklisp support.
It requires a reinstallation of externals-clasp. We are working on eliminating the need for externals-clasp.
- ASDF support has been added – it can be accessed using (require :asdf)
- SLIME support has been added and the changes to SLIME have been uploaded to the standard SLIME repository. You should be able to access it the standard way you install and upgrade slime.
- Quicklisp support has been added. I’ve submitted the changes to quicklisp to Zach Bean (the developer of quicklisp) and they should be available soon.
- Improvements in performance of the compiled code (Linux code is at least 2x faster).
- Improvements in stability.
- Almost all Common Lisp symbols have been implemented (18 left, you can see them using (core:calculate-missing-common-lisp-symbols).
- Example code for Clasp/C++ interoperation is available.
- Weak-key-hash-tables, weak-pointer, weak-key-mapping types have been implemented in both garbage collectors.
This release focused on getting SLIME working because I want to incorporate a new compiler front end for Clasp and I want to do it in this extremely powerful programming environment.
Before bed last night, I was trying to sort out in my head the lineage and historical inter-relations between BKNR, TBNL, HTML-TEMPLATES, CL-WHO, and Hunchentoot. When my alarm went off this morning, I was having a dream about the Lisp community.
In my dream, there were about 50 Lispers on a prison plane. It was pretty much a commercial 737, but all of the passengers were wearing prison jumpsuits and the plane would never land. We were all just on the plane. We refueled in flight. (The dream had nothing to say about how we restocked with food or emptied the septic tanks. *shrug*)
There was a cage around/over the last three rows of the left side of the plane. The door to the cage was not locked, but everyone knew that you were only allowed to go into the cage if Edi let you. Edi Weitz was the top dog in this prison. The only reason the rest of us were still alive is because Edi hadn’t seen a reason to have us taken care of yet.
Didier Verna was the only person who was allowed to approach the cage if Edi were in it.
There wasn’t much turbulence, but we were flying through a thunderstorm. This had Nick Levine sitting at a window seat, looking out over the wing, nervously bobbing back and forth, and sipping whiskey with ice from a plastic cup.
The storm had Zach Beane pacing back and forth in the aisle.
Cyrus Harmon was sitting doing a crossword puzzle from the in-flight magazines, giggling to himself about Faré.
Faré was standing near the front of the plane throwing little packets of peanuts at everyone. He wasn’t trying to hurt people with them, but he was delighting in watching them bounce of the heads of people who weren’t expecting them (especially the back of Zach’s head when he was fifteen or twenty rows away).
Robert Goldman was trying to get some sleep, but was unable to do so because of the lack of leg-room, the bright cabin lights, and all of the packets of peanuts careening past.
There were a number of other Lisp-folk in this dream. For some of them, I don’t recall exactly what they were up to. For a few, it would probably be impolitic of me to say.
For older items, see the Planet Lisp Archives.
Last updated: 2015-03-27 00:52