Search This Blog

Sunday, January 30, 2011

;; I often find that the default behaviour of exceptions in emacs/slime is a bit
;; annoying.
;; Up pops up a debugger window, but that moves the focus away from where
;; you're working, and the new window then needs dismissing, and has often
;; buggered up the size of other windows, etc...
;; Also when playing with experimental clojure versions, the behaviour is a bit
;; random, and often the expression just gets swallowed, with no clue as to what
;; it was:
;; Anyway I wrote this little macro, which turns an exception into a return value:
(defmacrotryc[ & e]
`(try ~@e
(catch Exception a# a#)))
(tryc (java.io.File ".")) ;; #<ClassCastException java.lang.ClassCastException: java.lang.Class cannot be cast to clojure.lang.IFn>
;; Obviously you should only use this at the top level!
;; I find I'm using it all the time when writing code in emacs

;; Indexing Into a Vector: A Sequence of Answers
;; The other day, I had a csv file to read (csv files are comma-separated
;; values, often produced by exporting spreadsheets, or a reasonably standard
;; human-readable format for grids of data)
;; There's a lovely library which takes all the hard work out of reading csv
;; files
;; The library's on clojars, so you use it by adding this to your maven pom.xml
;; <dependency>
;; <groupId>clojure-csv</groupId>
;; <artifactId>clojure-csv</artifactId>
;; <version>1.2.0</version>
;; </dependency>
;; and then you can use clojure-csv.core/parse-csv to read your file in as a
;; sequence. It seems to magically handle all the intricies of all the
;; different possible csv file arrangements.
;; Thankyou David Santiago, it works like a charm.
;; But then I hit a problem: I wanted to read the email-addresses from every
;; record.
;; I've rather simplified here, but let's imagine that the data after parsing
;; looked like this:
(defcsv-data
[["Name""E-mail 1""Address""E-mail 2""E-mail 3"]
["John""john@mailinator.com""3 Church St""xyz@learnclojure.com"""]
["Fred""fred@mailinator.com""7 Park Street""fred@gmail.com""fred@googlemail.com" ]])
(defheader (first csv-data))
(defdata (rest csv-data))
;; As it happened, most of the email-addresses were stored in the second column,
;; but some records had two or even three addresses, and it looked as though the
;; generating program might be labelling its columns according to the number of
;; e-mail addresses it needed to output.
;; It seemed very likely that another data set might have an "E-mail 4" column,
;; and it seemed unwise to rely on the needed columns always being 1,3,4 and
;; possibly 5. What if the program introduced another field entirely?
;; So obviously I needed a function to look up things in the header row somehow.
;; And there didn't seem to be one, although there were the interesting
;; functions keep-indexed and map-indexed, which I hadn't noticed before.
;; And I couldn't find one. So I figured that I could either write one, or I
;; could ask on Stack Overflow.
;; And so I did both, expecting to find that I'd re-invent something in the
;; standard library, or at least in contrib, that I hadn't been able to find.
;; http://stackoverflow.com/questions/4830900/how-do-i-find-the-index-of-an-item-in-a-vector
;; So the function(s) I came up with were:
(defnindices-of [f coll]
(keep-indexed #(if (f %2) %1 nil) coll))
(defnfirst-index-of [f coll]
(first (indices-of f coll)))
(defnfind-thing [value coll]
(first-index-of #(= % value) coll))
;; And here are some examples:
(indices-of #(="Name" %) header) ; (0)
(indices-of (partial re-matches #".*E-mail.*") header) ; (1 3 4)
(first-index-of #(="Name" %) header) ; 0
(find-thing "Name" header) ; 0
;; But I was a bit nervous about these solutions, because I thought I must just
;; have re-invented some sort of wheel, and also because they're happy to return
;; answers for sets and maps, where the question doesn't really make much sense
;;fine
(find-thing "two" ["one""two""three""two"]) ; 1
(find-thing "two" '("one""two""three")) ; 1
;;but these answers are a bit silly
(find-thing "two" #{"one""two""three"}) ; 1
(find-thing "two" {"one""two""two""three"}) ; nil
;; But I went back to Stack Overflow, in order to answer my own question, and
;; found a couple of much better answers.
;; Brian Carper pointed out:
(.indexOf header "Name") ; 0
;; Which is clearly the answer for searching vectors.
;; And ponzao pointed out this lovely thing, originally due to Stuart Halloway:
(require 'clojure.contrib.seq)
(first (clojure.contrib.seq/positions #{"Name"} header)) ; 0
;; positions is like indices-of, but using a set as the predicate is really clever.
;; anyway, now I could say:
(map #( % (.indexOf header "Name")) data) ; ("John" "Fred")
(map #( % (.indexOf header "E-mail 1")) data) ; ("john@mailinator.com" "fred@mailinator.com")
;; Or even, for fans of terseness and crypticity (and forgive me Lord, for I am
;; such a fan), something like:
(map #(vector (% (.indexOf header "Name"))
(for [i (clojure.contrib.seq/positions
(partial re-matches #"E-mail \d+") header)]
(% i))) data)
;; (["John" ("john@mailinator.com" "xyz@learnclojure.com" "")]
;; ["Fred" ("fred@mailinator.com" "fred@gmail.com" "fred@googlemail.com")])
;; But a little later, there was another answer from cgrand, who warned me
;; against using indices on general principles. And I agree with that, so I
;; asked what I should do in the particular case of csv files. And there was an
;; answer from Alex Stoddard, which I believe is the answer to the question that
;; I should have asked.
;; We can make a map from strings to indices
(defhmap (zipmap header (iterate inc 0)))
;; And use it like this:
(map #(% (hmap "Name")) data) ; ("John" "Fred")
;; or this:
(defe-mails (filter (partial re-matches #"E-mail \d+") header))
(map #(vector (% (hmap "Name")) (for [e e-mails] (% (hmap e)))) data)
;; or this:
(map #(vector
(% (hmap "Name"))
(for [e (filter (partial re-matches #"E-mail \d+") header)]
(% (hmap e)))) data)
;; to get:
;; (["John" ("john@mailinator.com" "xyz@learnclojure.com" "")]
;; ["Fred" ("fred@mailinator.com" "fred@gmail.com" "fred@googlemail.com")])
;; or even this (although now you do have to rely on the name coming before the e-mails):
(map #(for [e (filter (partial re-matches #"E-mail \d+|Name") header)]
(% (hmap e)))
data)
;; to get:
;; (("John" "john@mailinator.com" "xyz@learnclojure.com" "")
;; ("Fred" "fred@mailinator.com" "fred@gmail.com" "fred@googlemail.com"))
;;If we want to abstract out a pattern, then:
(defncolumns [f header data]
(let [hmap (zipmap header (iterate inc 0))
cols (filter f header)
(map #(for [e cols] (% (hmap e))) data)))
;; allows:
(columns #{"Name"} header data) ; (("John") ("Fred"))
(columns (partial re-matches #"E-mail \d+") header data) ; (("john@mailinator.com" "xyz@learnclojure.com" "") ("fred@mailinator.com" "fred@gmail.com" "fred@googlemail.com"))

Thursday, January 27, 2011

That worked a treat, £500 awarded to lucky winner who found me a fun short contract.

Now I'd like another one, so I repeat the offer:

If, within the next six months, I take a job which lasts
longer than one month, and that is not obtained through an agency, then
on the day the first cheque from that job cashes, I'll give £500 to the
person who provided the crucial introduction.

If there are a number of people involved somehow, then I'll apportion it
fairly between them. And if the timing conditions above are not quite
met, or if someone points me at a short contract which the £500 penalty
makes not worth taking, then I'll do something fair and proportional
anyway. (The thing Simon pointed me at only lasted three weeks and I paid him in full anyway, because it was neat.)

And this offer applies even to personal friends, and to old contacts who
I have not got round to calling yet, and to people who are themselves
offering work, because why wouldn't it?

And obviously if I find one through my own efforts then I'll keep the
money. But my word is generally thought to be good, and I have made a
public promise on my own blog to this effect, so if I cheat you you can
blacken my name and ruin my reputation for honesty, which is worth much
more to me than £500.

Anyhow, my CV is at http://www.aspden.com, and any advice on how it could be improved will be gratefully received.

I'll also repeat the original advert: (job-hunt.contrib 1.0)

Anyone in Cambridge need a programmer? Obviously Clojure is a
speciality, and my current obsession, but I'm also pretty good with C
(especially the embedded variety), microcontrollers, and Python, and I
have a particular facility with mathematical concepts and algorithms of
all kinds. My obsessions can be pretty quickly changed when I need them
to be.

I have a (deserved) reputation for being able to produce heavily optimised but
nevertheless bug-free and readable code, but I also know how to hack
together sloppy, bug-ridden prototypes, and I know which style is
appropriate when, and how to slide along the continuum between them.

I've worked in telecoms, commercial research, banking, university
research, a chip design company, server virtualization, a couple of
startups, and occasionally completely alone.

I've worked on many sizes of machine. I've written programs for tiny
8-bit microcontrollers, and once upon a time every IBM machine in one
building in Imperial College was running my partial differential
equation solvers in parallel in the background.

I'm smart and I get things done. I'm confident enough in my own
abilities that if I can't do something I admit it and find someone who
can.

I also have various ancient and rusty skills with things like Java, C++,
R, OCaml, Common LISP, Scheme, FORTRAN and Pascal which can be brushed up
if necessary. Like all lispers, I occasionally write toy interpreters
for made-up languages for fun.

If you're a local company using Java, who might be interested in giving
Clojure a try (motivation here, in Paul Graham's classic Beating the Averages), I'd love to try to show you what all the fuss is about.

I've never used a CV before, having always found work through word of
mouth. So I expect that it can be improved. If anyone's got any
suggestions as to how it could be better written, do please leave
comments or e-mail cv@aspden.com.