Recently, late in a risky project, I chose to abandon my previous efforts with another language and use Haskell to solve a difficult problem. This has been one of the best professional decisions I’ve made, and much of the success must be attributed to Haskell, its community, and functional programming in general.
However, diving into paid work with Haskell has meant focusing on production and deployment concerns at some expense to learning the language itself. It’s time to document the former and plan to remedy the latter.
Developing and Deploying with Haskell
Having installed GHC and the Haskell Platform, I have a stable Haskell environment, and I want to keep it that way. So far, I’ve used cabal only to install cabal-dev, pointfree, hlint, and ghc-paths (for GHCi) into my user package database:
$ cabal install cabal-dev $ cabal install pointfree $ cabal install hlint $ cabal install ghc-paths
For the project, I’ve used cabal-dev in place of cabal to install libraries and compile the executable:
$ mkdir -p /path/to/project $ cd /path/to/project $ cabal init -n --is-executable ... $ cabal-dev install postgresql-simple $ cabal-dev install datetime ... $ cabal-dev configure ... $ cabal-dev build && cabal-dev ghci .\ main ... $ cabal-dev build && cabal-dev install ./cabal-dev/bin/Main ...
As well, I’m using the Haskell wiki Applications and libraries section as a guide to studying GHC and its tool environment. First up is the Prelude:
The most important Haskell library is called the Prelude. It is implicitly imported by default, and includes the most commonly used functions. Make sure you know what they do and how to use them effectively.
Of course, I’ll always have a tab open to Hoogle.
I removed the GHC and Haskell Platform Slackware packages and
deleted root’s and my
~/.ghc directories. Then, I built
a Slackware package for hscolour using the Slackbuilds
I installed the Slackware packages for GHC, hscolour, and Haskell Platform,
in that order.
Yesterday, I spent a few hours with a PostgreSQL database, eliminating almost 100 lines of SQL and PL/pgSQL spread over three functions and involving six explicit locks, three nested transactions at different isolation levels, and a trigger. I replaced it all with something like the following:
insert into r0 (a0, a1, a2) select a0, a1, a2 from r1 where (a0, a1, a2) not in (select a0, a1, a2 from r0) and ... order by a3, a4, a5 limit 1 returning a0, a1, a2;
Simple, declarative, and guaranteed to be correct and atomic. That’s why I use PostgreSQL (as poor an implementation of the Relational Model as SQL is), and that’s why I can no longer settle for less in my general purpose programming language.
Nor can I tolerate deliberate ignorance any further. Evidence, reason, and math trump aesthetics and “pragmatism.”
I need Haskell and it’s community.
I haven’t used Haskell for almost a year, and it’s time for a refresher.
As challenging as the Haskell language is, I’ve found that installation and tooling have been greater barriers to entry for my purposes. The Haskell Platform has made great strides to address the former, but I’ve been out of touch with progress on the latter. I’m especially concerned about cabal hell.
I came across Bob Ippolito’s article, which looks to provide a reasonable starting point for a current Haskell installation.
Previously I wrote that lparallel 2.3.2 failed to compile for me under SBCL 1.0.50. James was kind enough to contact me by email to say that he has fixed that. I’m guessing we’ll see that in a future Quicklisp update.
In the near future, I plan to write about my grokking and use of lparallel in a commercial project.
The Haskell Platform script was intended for version 2012.2.0.0, so I edited it to change the version to 2012.4.0.0.
CL-USER> (ql:quickload "lparallel") To load "lparallel": Load 1 ASDF system: lparallel ; Loading "lparallel" [package alexandria.0.dev]........................ [package bordeaux-threads]........................ [package lparallel.util].......................... [package lparallel.thread-util]................... [package lparallel.raw-queue]..................... [package lparallel.cons-queue].................... [package lparallel.vector-queue].................. [package lparallel.queue]......................... [package lparallel.biased-queue].................. [package lparallel.counter]....................... [package lparallel.spin-queue].................... [package lparallel.kernel]........................ [package lparallel.kernel-util]................... [package lparallel.ptree]......................... [package lparallel.promise]....................... [package lparallel.defpun]........................ [package lparallel.cognate]....................... [package lparallel]......... ; file: /home/mjf/quicklisp/dists/quicklisp/software/lparallel-20130312-git/src/spin-queue/cas-spin-queue.lisp ; in: DEFUN PUSH-SPIN-QUEUE ; (LPARALLEL.SPIN-QUEUE::CAS ; (LPARALLEL.SPIN-QUEUE::NODE-CDR ; (LPARALLEL.SPIN-QUEUE::SPIN-QUEUE-TAIL LPARALLEL.SPIN-QUEUE::QUEUE)) ; NIL LPARALLEL.SPIN-QUEUE::NEW) ; --> EQ ; ==> ; (COMPARE-AND-SWAP ; (LPARALLEL.SPIN-QUEUE::NODE-CDR ; (LPARALLEL.SPIN-QUEUE::SPIN-QUEUE-TAIL LPARALLEL.SPIN-QUEUE::QUEUE)) ; NIL LPARALLEL.SPIN-QUEUE::NEW) ; ; caught ERROR: ; during macroexpansion of (SB-EXT:COMPARE-AND-SWAP (NODE-CDR #) NIL ...). Use ; *BREAK-ON-SIGNALS* to intercept: ; ; Invalid first argument to COMPARE-AND-SWAP: (NODE-CDR (SPIN-QUEUE-TAIL QUEUE)). ; ; compilation unit aborted ; caught 1 fatal ERROR condition ; caught 1 ERROR condition ; Evaluation aborted on #.
We deploy applications on more recent versions of SBCL, so there was no reason to not upgrade. However, warned by Christophe Rhodes’s post, I chose to avoid version 1.1.6. I downloaded 1.1.5 and tweaked my copy of the Slackbuilds SBCL script to build it.
Following the upgrade, I was able to load lparallel and run its tests successfully.
Work has kept me from writing about this until now. In late January, I volunteered to run a “Computer Programmers” cluster for thirteen grade seven and eight students at a local early and middle years school. We would meet for an hour six times over five weeks.
On the first day, to keep the focus on programming rather than computers (and to break the ice), we played a game inspired by NASA’s Curiosity rover. After watching short video about rover driver Vandi Verma, students paired up and took turns playing the roles of programmer and rover. The goal, initially, was to program a rover to circumnavigate the rectangular room. Then, programmers tested their rovers on different levels of the terraced octagonal pit in the middle of the room.
A programmer wrote the program on a sheet of paper in the rover language. When handed the paper, the rover began to interpret the program, reading it from top to bottom, following each instruction before going on to the next. Additionally, a rover would listen for its programmer to say “REBOOT”, a powerful command that would terminate the current program and instruct the rover to return to home base.
Rover 1.0 had a very limited language:
Rover steps forward, one foot immediately in front of the other.
Rover turns left degrees.
Rover turns right degrees.
Rover stops whatever it’s doing and returns to home base.
Everyone succeeded in driving her rover around the perimeter of the room:
(STEP) (STEP) ... (STEP) (TURN-RIGHT 90) (STEP) (STEP) ... (STEP) (TURN-RIGHT 90) (STEP) (STEP) ... (STEP) (TURN-RIGHT 90) (STEP) (STEP) ... (STEP)
However, navigating the different levels of the pit required a major rewrite, and this was all the more tedious given the limited Rover 1.0 language.
We upgraded the rover language, adding three very useful operations:
A test that is true if the rover’s front foot touches an obstacle and false otherwise.
(IF test then-command else-command)
Do the test. If it’s true, do then-command. If it’s not, do else-command.
(REPEAT count commands)
Do the list of commands, in order, then do them again, a total of count times.
With these in hand, most students eliminated the individual step commands for each side of the room:
(REPEAT 40 (STEP)) (TURN-RIGHT 90) (REPEAT 35 (STEP)) (TURN-RIGHT 90) (REPEAT 40 (STEP)) (TURN-RIGHT 90) (REPEAT 35 (STEP))
Some recognised that that pit was a regular octagon and used nested loops:
(REPEAT 8 (REPEAT 16 (STEP)) (TURN-RIGHT 45))
Finally, following a little demonstration and Socratic method, a few of them discovered how to handle the room or any of the levels of the pit with a single change:
(REPEAT 40 (IF (SENSE-OBSTACLE) (TURN-RIGHT 45) (REPEAT 4 (STEP))) (REPEAT 32 (IF (SENSE-OBSTACLE) (TURN-RIGHT 45) (REPEAT 4 (STEP)))
What has any of this to do with Lisp… besides the parentheses? In this case, it’s about the parentheses: the students neither asked nor complained about them.
I edited and used the SlackBuilds PostGIS script to install PostGIS 1.5.8 on Slackware 13.37. The script specifies PostGIS 1.5.2, but there is a bug that prevents compiling that version against PostgreSQL versions 9.1 and greater.