Showing posts with label Clojure. Show all posts
Showing posts with label Clojure. Show all posts

Thursday, 27 February 2020

Putting data on the map

A couple of weeks ago, someone came to me with a problem. She had data in a spreadsheet. She wanted to display it as a map, on a website. And she wanted to be able to do that dynamically - that is to say, she wanted the map on the website to update as the spreadsheet changed.

So there were clear routes to several of the parts of this problem:

1. If you need maps on a web page, you need Leaflet - it's a simply wonderful library;
2. If you want a spreadsheet that is live on the Internet, Google Sheets are a very good way to go;
3. Google Sheets allows export as Comma-Separated Values, and CSV is a very easy format to parse.

I needed to do this quickly, so I started in my comfort zone, using Dmitri Sotnikov's Luminus framework and mixing in the Day8 re-frame framework, in order to have a lot of built-in functionality client side. The joy of the Clojure development tools is that getting a working system is as easy as typing

    lein new luminus geocsv +re-frame

And from that point on you can do your development with your system live, and can see the effects of every change you make immediately.

So within a couple of days I had a polished wee system which looked good and pulled live data from a Google Sheets spreadsheet to populate a map, with different graphical pins for her different classes of records. It was highly reusable, since you can specify the document id of any public Google sheet in the query part of the URL, and that sheet will be rendered. The map automatically pans and zooms to focus on the data provided. Was I pleased? Well, sort of.

Why was I not pleased? The deliverable component is an executable jar file, and it's 58 megabytes; in the browser, client-side, a page consumes 10 megabytes of memory. That seems huge for such a simple piece of functionality. I'd satisfied the requirement, but I hadn't satisfied myself. Also, of course, it needed you to be able to run a component on your web server, and many organisations with simple web hosting can't do that.

Doing it again, better

So I started again. This time I did it entirely client-side, just ClojureScript, with no heavyweight libraries. Having solved the problem once, it was pretty easy to do it again, and within a day I had a system working. Furthermore, I added flexibility: you can supply a URL, as before; or as the text of the document element that the map overlays; or passed as a string to the function. You can see it here.

Was I pleased?

No.

Three things about this solution don't satisfy me.

Firstly, the deliverable (jar archive, for direct comparison with the original) is still 2.9 megabytes. That's only 5% of the size of the original, and includes the whole of Leaflet, the whole of [Papaparse](https://www.papaparse.com/) - a client side CSV parser, and 398 different map pin images, but it still seems big. Page load - with the three maps on the demo page - costs around 5 megabytes in the browser.

Secondly, because it's client-side only, it cannot know what pin images are available on the server; so if there is a category value in the data for which no pin image is available on the server, you will get a 'broken image' appearing on the map, which is ugly.

Thirdly and most significantly, again because it's client side only, modern cross-site scripting protections mean that it cannot pull data from a spreadsheet hosted on another site - so it doesn't strictly meet the original requirement.

Iterating again

I decided to look at whether I could make it smaller by abandoning the comfort of the Clojure environment and writing pure JavaScript. This led to a third iteration, geocsv-js, which you can see in action here. The amount of 'my code' in this version is far lighter - the output of the ClojureScript compiler for geocsv-lite comes to 6.6 megabytes uncompressed, whereas the pure JavaScript version is one file of just 8.8 kilobytes in 296 source lines of code (for comparison, the ClojureScript solution, geocsv-lite, comes to 372 source lines of code). One reason for the slightly larger size of the ClojureScript solution is that it has a better algorithm for panning and zooming the map to display the data actually entered, which I didn't port to JavaScript.

But what is bizarre and I do not yet understand is that the deliverable is not smaller but bigger than the geocsv-lite deliverable, by almost twice - 4.7 megabytes - despite using identically the same libraries. Still more surprising, the memory consumption in the browser is also higher, at around 6 megabytes.

What are the lessons learned? Well, the overhead of using ClojureScript is not nearly as much as I thought it was. There is something clearly wrong about the discrepancy between the size of the packaged deliverables - the pure JavaScript variant must be including third party data which the ClojureScript variant doesn't, although I haven't yet tracked down what this is - but the in-browser memory footprint is actually smaller, and the page load time is, as best as I can measure, identical at about five seconds.

Was I satisfied now? Well, sort of.

Merits and demerits

All three variants have merits. The first pulls the data directly from Google Sheets, which was the original requirement, and served a default pin image for any record category for which it didn't have a matching image. That made it a lot slicker to use, and more forgiving of user error. The two client-side-only variants cannot do those things, for reasons which I have not found ways to solve. But they don't need any server-side functionality beyond the dumb serving of files, so they're much easier to deploy; they are also less greedy of client side resources.

One more time with feeling

One of the reasons why I kept on hammering at this problem was that I felt it would make a really useful extension for my wiki engine, Smeagol. I've integrated the JavaScript version, geocsv-js, and in doing that I've solved a number of problems. Firstly, the look and feel and content of Smeagol pages is flexible and easily configurable by the user, so it doesn't take a geek to set up a page with the content you want and a map of the data you want to show. Secondly, the Smeagol engine, because it sits server side, can pull data from remote sites, and because it doesn't interpret that data, there's no significant risk in doing that. Thirdly, again because it sits server side, it can deal with the issue of unknown images - and, because it's a wiki engine targeting less technical users, I've deliberately made it very graceful about how it does this.

So now, instead of just a map on a web page, you get a whole, richly editable website, with existing extensions to integrate sophisticated data visualisation and photograph galleries as well as maps. And the cost of this? Surprisingly little more. A Smeagol page with one map uses exactly the same memory on the client as a geocsv-js page with one map, because Smeagol now only loads JavaScript for extensions actually being used in the page, and almost all of its own functionality runs server side. But even server side, the cost is not very much greater than for the full fat geocsv implementation - the deliverable jar file, which offers far more functionality than geocsv, is only 88 megabytes. Considering how much more usability and flexibility this offers, this is the version of geocsv I'd now offer, if someone came to me with the same problem.

Wednesday, 23 January 2019

Mad software

I was listening to Eric Normand's podcast this morning, as I was making breakfast and tidying my room; he was talking about semantics and data. It started a train of thought which I shall try to unroll.

I have blogged a lot in the past about madness and about software, but I don't think I've ever blogged about madness and software in the same essay. But the reasons I'm mad and the reasons I'm (sometimes) very good at software are related; both have their roots in autism and dyslexia, or, to put it differently, how my brain is wired.

I first wrote about post scarcity software thirteen years ago. It was a thought about how software environments should be designed if were weren't held back by the cruft of the past, by tradition and by a lack, frankly, of anything much in the way of new creative thought. And seeing that the core of the system I described is a Lisp, which is to say it builds on a software architecture which is almost as old as I am, perhaps it is infected by my own take on tradition and my own lack of creativity, but let's, for the purposes of this essay, assume not.

I started actually writing the post scarcity software environment on the second of January 2017, which is to say two years ago. It's been an extremely low priority task, because I don't have enough faith in either my vision or my skill to think that it will ever be of use to anyone. Nevertheless, it does now actually work, in as much as you can write software in it. It's not at all easy yet, and I wouldn't recommend anyone try, but you can check out the master branch from Github, compile it, and it works.

As my mental health has deteriorated, I have been working on it more over the past couple of months, partly because I have lost faith in my ability to deliver the more practical projects I've been working on, and partly because doing something which is genuinely intellectually hard helps subdue the chaos in my mind.

Having said that, it is hard and I am not sharp, and so progress is slow. I started work on big number arithmetic a three weeks ago, and where I'm up to at this point is:
  • addition seems to work up to at least the second bignum boundary;
  • multiplication doesn't work beyond the first bignum boundary;
  • subraction doesn't work, and turns out not to be as easy as just inverting addition;
  • division sort of trivially works, but only in the sense that we can create a rational number out of arbitrary bignums;
  • reading works beyond the first bignum boundary, but not up to the second (because multiplication doesn't work);
  • printing doesn't work beyond the first bignum boundary.
I knew bignums were going to be a challenge, and I could have studied other people's bignum code and have consciously chosen not to do so; but this is not fast progress.

(I should point out that in those three weeks I've also done four days of customer work, which is .Net and boring but it's done, spent two days seeing my sister, spent several days so depressed I didn't actually do anything at all, and done a bit or practical work around the croft. But still!)

In a sense, it wasn't expected to be. Writing the underpinnings of a software environment which is conceptually without limits has challenge after challenge after challenge.

But there are ideas in post scarcity which may have wider utility than this mad idea in itself. Layering homogeneities and regularities onto Clojure maps might - perhaps would - make a useful library, might would make a very useful component for exactly the sort of data wrangling Eric Normand was talking about. Yes, you can use a map - raw data soup - to represent a company. But if this map is a member of a homogeneity, 'Companies', then we know every member of it has employees, and that every employee has a salary and an email address. Regularities and homogeneities form the building blocks of APIs; to use the example Eric discussed in his podcast, the salary is the property of the employee, but the payroll is a property of the company. So in the notation I'm using for post scarcity, you'd get the payroll figure for a company by using a method on the 'Companies' homogeneity. How it computes that value is part of the general doctrine of 'Don't Know, Don't Care': the principal that people writing software at any layer in the system do not need to know, and should not need to care, about how things are implemented in the layers below them.

So, the user needing to find the payroll value for a company would enter something like this:

    (with ((companies . ::shared:pool:companies)
           (acme . companies:acme-widgets-plc))
        (companies:methods:payroll acme))

In practice, in post scarcity notation, the payroll method probably looks something like this:

    (λ (company)
        (reduce + 
            (map ::shared:pool:employees:methods:salary 
                (:employees company))))

There are issues that I haven't resolved yet about the mutability of regularities and homogeneities; obviously, in order to provide multi-user visibility of current values of shared data, some regularities must be mutable. But mutability has potentially very serious perfomance issues for the hypercube architecture, so I think that in general they should not be.

However, that's detail, and not what I'm trying to talk about here.

What I'm trying to talk about here is the fact that if I were confident that these ideas were any good, and that I had the ability to persuade others that they were any good, it would make far more sense to implement them in Clojure and promote them as a library.

But the problem with depression is that you cannot evaluate whether your ideas are any good. The black dog tells me that I'm shit, and that my ideas are shit, and that I don't really know enough to be worth listening to, and that I'm an old tramp who lives in a hut in the woods, and probably smells, and that in any case interaction with other people quickly makes me shaky and confused, and that I can never get my act together, and that I never finish anything.

And all that is objectively true, and I know that it is true. But I also know that I can (or at least have in the past been able to) build really good software, and that I can (or have been able, in the past, to) present ideas really well.

These two collections of statements about me are both true at the same time. But the difference is that I believe the first and I don't believe the second.

And behind all this is the fact that bignum arithmetic is a solved problem. I could dig out the SBCL source code and crib from that. I could read Muhammad ibn Musa al-Khwarizmi, who wrote down the algorithms (the word is a europeanisation of his name) for the general problems of long addition, multiplication and division in around 820AD. I could reread section 4.3 in Knuth.

I am bashing my head against bignum arithmetic and trying to solve it myself, not because it's the most efficient way to produce good code quickly, but because what I'm really trying to do is just distract myself and waste time while I can get on with dying.

And the reason beyond that that I'm working on a software system I know I'll never finish, which is designed to run on computers which don't even exist yet - and although I'm very confident that enormously parallel hardware will be used in future, I'm not at all sure it will look anything like what I'm envisaging - the reason I'm building this mad software is that, because it will never be finished, no-one will ever use it except me, and no-one will say how crap it is and how easily it could have been done better.

Because the other thing that I'm doing in writing this stuff, apart from distracting from the swirling chaos and rage in my head, apart from waiting to die, the other thing I'm doing is trying to give myself a feeling of mastery, of competence, of ability to face problems and solve them. And, to an extent, it works. But I have so little confidence that I actually have that mastery, that competence, that I don't want to expose it to criticism. I don't want my few fragile rags of self worth stripped away.

And so I work, and work, and work at something which is so arcane, so obscure, so damned pointless that no-one will ever use it.

Not because I'm even enjoying it, but just to burn the time.

This is mad.

I am mad.

I hate, hate, hate being mad.

Postscript: just writing this essay has made me tearful, headachey, sick, shaky. It's very hard to face up to the irrationalities and self-deceptions in one's own behaviour.

Friday, 25 August 2017

Riddles in the dark

I'm continuing my series of essays on the future of news; this essay may look like a serious digression, but trust me, it's not. The future of news, in my opinion, is about cooperation; it's about allowing multiple voices to work together on the same story; it's about allowing users to critically evaluate different versions of the story to evaluate which is more trustworthy. This essay introduces some of the technical underpinnings that may make that possible.

In January 2015, I ran a workshop in Birnam on Land Reform. While planning the workshop, I realised I would need a mechanism for people who had attended the workshop to work cooperatively on the document which was the output of the event. So, obviously, I needed a Wiki. I could have put the wiki on a commercial Wiki site like Wikia, but if I wanted to control who could edit it I'd have to pay, and in any case it would be plastered with advertising.

So I decided to install a Wiki engine on my own server. I needed a Wiki engine which was small and easy to manage; which didn't have too many software dependencies; which was easy to back up; and on which I could control who could edit pages. So I did a quick survey of what was available.

Wiki engines store documents, and versions of those documents. As many people work on those documents, the version history can get complex. Most Wiki engines store their documents in relational databases. Relational databases are large, complex bits of software, and when they need to be upgraded it can be a pain. Furthermore, relational databases are not designed to store documents; they're at there best with fixed size data records. A Wiki backed by a relational database works, but it isn't a good fit.

Software is composed of documents too - and the documents of which software is composed are often revised many, many times. In 2012 there were 37,000 documents, containing 15,004,006 lines of code (it's now over twenty million lines of code). It's worked on by over 1,000 developers, many of whom are volunteers. Managing all that is a considerable task; it needs a revision control system.

In the early days, when Linus Torvalds, the original author of Linux, was working with a small group of other volunteers on the early versions of the kernel, revision control was managed in the revision control system we all used back in the 1980s and 90s: the Concurrent Versions System, or CVS. CVS has a lot of faults but it was a good system for small teams to use. However, Linux soon outgrew CVS, and so the kernel developers switched to a proprietary revision control system, Bitkeeper, which the copyright holders of that system allowed them to use for free.

However, in 2005, the copyright holders withdrew their permission to use Bitkeeper, claiming that some of the kernel developers had tried to reverse engineer it. This caused a crisis, because there was then no other revision control system then available sophisticated enough to manage distributed development on the scale of the Linux kernel.

What makes Linus Torvalds one of the great software engineers of all time is what he did then. He sat down on 3rd April 2005 to write a new revision control system from scratch, to manage the largest software development project in the world. By the 29th, he'd finished, and Git was working.

Git is extremely reliable, extremely secure, extremely sophisticated, extremely efficient - and at the same time, it's small. And what it does is manage revisions of documents. A Wiki must manage revisions of documents too - and display them. A Wiki backed with git as a document store looked a very good fit, and I found one: a thing called Gollum.

Gollum did everything I wanted, except that it did not authenticate users so that I couldn't control who could edit documents. Gollum is written in a language called Ruby, which I don't like. So I thought about whether it was worth trying to write an authentication module in Ruby, or simply start again from scratch in a language I do like - Clojure.

I started working on my new wiki, Smeagol, on 10th November 2014, and on the following day it was working. On the 14th, it was live on the Internet, acting as the website for the Birnam workshop.

It was, at that stage, crude. Authentication worked, but the security wasn't very good. The edit screen was very basic. But it was good enough. I've done a bit more work on it in the intervening time; it's now very much more secure, it shows changes between different versions of a document better (but still not as well as I'd like), and it has one or two other technically interesting features. It's almost at a stage of being 'finished', and is, in fact, already used by quite a lot of other people around the world.

But Smeagol exploits only a small part of the power of Git. Smeagol tracks versions of documents, and allows them to be backed up easily; it allows multiple people to edit them. But it doesn't allow multiple concurrent versions of the same document; in Git terms, it maintains only one branch. And Smeagol currently allows you to compare a version of the document only with the most recent version; it doesn't allow you to compare two arbitrary versions. Furthermore, the format of the comparison, while, I think, adequately clear, is not very pretty. Finally, because it maintains only one branch, Smeagol has no mechanism to merge branches.

But: does Smeagol - that simple Wiki engine I whipped up in a few days to solve the problem of collaboratively editing a single document - hold some lessons for how we build a collaborative news system?

Yes, I think it does.

The National publishes of the order of 120 stories per issue, and of the order of 300 issues per year. That's 36,000 documents per year, a volume on the scale of the Linux kernel. There are a few tens of blogs, each with with at most a few tens of writers, contributing to Scottish political blogging; and at most a few hundred journalists.

So a collaborative news system for Scottish news is within the scale of technology we already know how to handle. How much further it could scale, I don't know. And how it would cope with federation I don't yet know - that might greatly increase the scaling problems. But I will deal with federation in another essay later.


Saturday, 2 January 2016

The Irresolution: on software, quality, and why one works

Meters, object oriented, with proper multiple inheritance.
Doesn't look very impressive? Well, this is Interlisp-D. In 1983.
I don't make new year resolutions; I never have. This year I almost did...

Some weeks ago, working on a project which has gone horribly badly wrong, I tripped on one of those nasty ugly non-orthogonalities which litter Microsoft software. In frustration, I wrote a very sarcastic comment in my code. In code review, a colleague whom I very much respect chided me: a bad workman blames his tools.

Yes, I thought but did not say, but a good craftsman chooses his tools. And I decided then that by the end of 2016 I would be working only in pure functional languages and only on competently-written platforms. That was going to be my first ever new years resolution.

I'm sixty, dammit. People are not going to pay me to cut code for very many more years. I started my career, thirty years ago, working on Interlisp-D, a development environment technically streets ahead of anything commercially available now. A thing of real beauty and a joy to work with.

The general idea of 'progress' is that things get better over time, not worse. I'm damned if I'm going to end my career writing on Microsoft's fit-of-pique clone of an ALGOL-derived language which Sun consciously and deliberately crippled because it was intended as a special purpose tool to develop programs for small-footprint embedded systems with very limited memory.

A meter I wrote this year, in SVG and Angular. No multiple
inheritance, so much less reusable code.
It's crap. Writing software is about solving problems, about finding elegant ways to solve problems. But it isn't and shouldn't be about finding elegant ways to solve the problems caused by the shoddy workmanship and poor technical design of platform you're working on. If the platform you're working on is shoddy, don't bloody work on it.

That was a good resolution. A wise resolution. A resolution it would have been sensible to adopt.

Just after Christmas it was tested: my attention was drawn to a job advert a friend of mine has posted: Clojure, my preferred language, on UNIX, my preferred platform, in Glasgow. I know the office, I've done coding dojos there. It's very slick, very comfortable, with standing desks I much admire; I know several of the team, and I like them. It should be a dream job.

And I'm not going to apply.

Why not?

Cowardice? Possibly.

An old man's reluctance to move? Well, certainly that. I find settling into new places and building new networks of relationships stressful and difficult, so that is a real consideration.

Engagement in the work I'm doing? Well, I do believe in what we're doing. I do believe that what we do, as a company, makes the world a better place. And I do suspect that the company is quite likely to make it very big indeed, and I'd be quite interested to watch that happen - even if this time I'm just an employee.

But what it really boils down to is loyalty. Loyalty to a key group of the people I'm working with; a desire not to let them down. Not that I imagine I'm irreplaceable; I'm nothing like as good as I used to be, and I'm much less good working with tools I dislike than I could be.

I know this is a bad decision. I know that some months down the track I'm going to find myself falling into another sinkhole of Microsoft shite and I'm going to regret this decision bitterly.

But this is my decision, and I've made it.

Afterword: on why tools and their quality matter

Yes, I know all computer systems of any significant size contain elements which seemed like a good idea at the time and which later turned out not to be. Interlisp is a LISP-2 - it has separate value and function pointers on every symbol. It also has subtly different binding mechanisms between interpreted and compiled code, which means that the scope of variables is different leading to semantic difference - which was an uncommon but horrible pitfall when your code worked when interpreted but broke when compiled. It has an awkward inorthogonality between older functions which have abbreviated names in ALL UPPER CASE and newer ones which have LongerNamesInCamelCase. Oh, and there was the curious CLISP feature which you could either see as magical or a well of despair, depending on how you used it (I didn't). 

Finally, in Medley, it had Common Lisp bodged onto the side. Common Lisp is a bit of a mess anyway, but the attempt to make one system interoperate functions written in two different syntaxes must have made for interesting debugging. But I had moved onto Prolog before Medley was released, so I never really had to deal with that.

Similarly, Clojure is partially crippled by the JVM's limited, fixed size stack (back to that notional 32 bit minimal-memory set-top box Java was designed for). And there have been some bizarre decisions in the language design:
  • the default arithmetic operators do not gracefully switch to bignums on arithmetic overflow (although there are alternative operators which sensibly do, if you know about them);
  • nil is not the empty list (both (nil? '()) and (empty? nil) return false) and is also not false ((false? nil) returns false); also, bizarrely, the car of the empty list is nil ((first '()) returns nil) but the cdr is the empty list ((rest '()) returns ());
  • the decision to remove a layer of brackets from cond and case  means that pretty-printing does not give a rational code layout (and also makes translation of code from other Lisps that bit harder);
  • the decision to notate arg-lists and let binding lists as vectors is also bizarre and again introduces needless inorthogonality (yes, I know that as compiled for the JVM they're implemented as vectors, but that's an implementation detail)...
And so on. There is no perfect computer language.

But anyone who has looked at a fern-moss, or a frond of bracken, or a waterfall, or a birch tree, or a cloud, or a prawn, or the development of a human embryo, has to acknowledge that if God does not write Lisp, God writes some language so similar to Lisp as to make no difference. The physical world is built in all its aspects and at all scales of simple functions recursively applied. It physically hurts me to work on systems whose underlying architecture is a bag of hacks kluged together.

A good workman chooses his tools.


Tuesday, 9 December 2014

Me and you and the Duke of Buccleuch

Drumlanrig Castle

I've written before about an exponential land tax. Rather often in fact... Here I want to give a clear account of how it would work, with a computer program (in Clojure) so that you can fiddle with it yourself. Thanks to the magic of Gorilla Repl, it's up on the web here for you to play with.

So, the core of the idea is that you pay a small amount on your first hectare of land, a little bit more on your next hectare, a little bit more on your next, and so on. There are two key numbers in this idea: the constant, which is the amount of money you pay on the first hectare, and the exponent, which is the power the number of hectares we've counted so far is raised to to calculate the little bit more. So what you pay in tax is Σ1..n(cn)e, where n is the number of hectares you own.

Expressed as a clojure function, that's:

(defn summed-exponential-series 
  "Sum an exponential series from 1 to limit.
   
   `constant`: the constant by which integers in the range are multiplied;
   `exponent`: the exponent to which they are raised;
   `limit`: the limit of the range to be summed."
  [constant exponent limit]
  (reduce + (map #(math/expt (* constant %) exponent) (range limit))))

We now need to create a table of sizes of interesting land-holdings as Clojure code (areas in hectares):

(def holding-sizes
  [["Average croft" 5]
   ["Average farm" 101]
   ["Glasgow Airport" 300]
   ["Edinburgh Airport" 400]
   ["Grangemouth Refinery" 700]
   ["Countess of Sutherland" 33000]
   ["Earl of Seafield" 40000]
   ["Captain Alwynn Farquharson" 51800]
   ["Duke of Westminster" 54000]
   ["Duke of Atholl" 58700]
   ["Duke of Buccleuch" 109000]])

and finally a little function to map the first function over the table:

(defn sample-taxes
  "Prints sample taxable amounts for a table of holdings."
  [constant exponent holdings]
  (map #(print (format "\n%s: £ %,.2f" 
                (first %) 
                (summed-exponential-series constant exponent (second %))))
       holdings))

We can now ask, given a constant and an exponent, how much tax each of these holdings would pay.

For example, if we start with a modest £1 of tax on the first hectare, and set an exponent of only 1.05, we get this pattern:

Average croft:£ 10.53
Average farm:£ 6,204.08
Glasgow Airport:£ 58,191.38
Edinburgh Airport:£ 105,040.07
Grangemouth Refinery:£ 331,177.47
Countess of Sutherland:£ 893,688,616.49
Earl of Seafield:£ 1,325,738,877.59
Captain Alwynn Farquharson:£ 2,252,234,219.56
Duke of Westminster:£ 2,452,703,794.50
Duke of Atholl:£ 2,910,359,665.77
Duke of Buccleuch:£ 10,350,620,262.71

So ordinary people would pay trivial amounts; farmers would pay a bit more than I really want them to; the airports and the refinery, amounts which are perfectly affordable to them... and the aristocracy would have either to abandon most of their lands or be bankrupted utterly (yes, that is ten billion pounds the Duke of Buccleuch would owe, each year).

The point of offering you this as code that you can fiddle with is that by changing the constant and the exponent you can change the pattern of charges. The curve isn't quite right here; I feel that the levy on farmers is relatively speaking too high, on the refinery perhaps a little low. Tweaking the constant down and the exponent up changes the picture.

Obviously, an exponential land tax would not generate very much revenue, since most people wouldn't pay very much at all and most of the few who would be assessed for large amounts would have to abandon their land immediately. But the point is not to generate revenue, the point is to redistribute land; and it would do that exceedingly effectively.

Saturday, 25 October 2014

Post Scarcity Hardware

A second-generation connection machine in use.
Each light represents one active processor node.
Eight years ago, I wrote an essay which I called Post Scarcity Software. It's a good essay; there's a little I'd change about it now - I'd talk more about the benefits of immutability - but on the whole it's the nearest thing to a technical manifesto I have. I've been thinking about it a lot the last few weeks. The axiom on which that essay stands is that modern computers - modern hardware - are tremendously more advanced than modern software systems, and would support much better software systems than we yet seem to have the ambition to create.

That's still true, of course. In fact it's more true now than it was then, because although the pace of hardware change is slowing, the pace of software change is still glacial. So nothing I'm thinking of in terms of post-scarcity computing actually needs new hardware.

Furthermore, I'm a software geek. I know very little about hardware; but I'm very much aware that as parallelism increases, the problems of topology in hardware design get more and more difficult. I've no idea how physically to design the machines I'm thinking of. But nevertheless I have been thinking more and more, recently, about the design of post-scarcity hardware to support post-scarcity software.

And I've been thinking, particularly, about one issue: process spawning on a new processor, on modern hardware, with modern operating systems, is ridiculously expensive.

A map of the problem

What got me thinking about this was watching the behaviour of the Clojure map function on my eight core desktop machine.

Mapping, in a language with immutable data, in inherently parallelisable. There is no possibility of side effects, so there is no particular reason for the computations to be run serially on the same processor. MicroWorld, being a cellular automaton, inherently involves repeatedly mapping a function across a two dimensional array. I was naively pleased that this could take advantage of  my modern hardware - I thought - in a way in which similarly simple programs written in Java couldn't...

...and then was startled to find it didn't. When running, the automaton would camp on a single core, leaving the other seven happily twiddling their thumbs and doing minor Unixy background stuff.

What?

It turns out that Clojure's default map function simply serialises iterations in a single process. Why? Well, one finds out when one investigates a bit. Clojure provides two different versions of parallel mapping functions, pmap and clojure.core.reducers/map. So what happens when you swap map for pmap? Why, performance improves, and all your available cores get used!

Except...

Performance doesn't actually improve very much. Consider this function, which is the core function of the MicroWorld engine:

(defn map-world
  "Apply this `function` to each cell in this `world` to produce a new world.
   the arguments to the function will be the world, the cell, and any
   `additional-args` supplied. Note that we parallel map over rows but
   just map over cells within a row. That's because it isn't worth starting
   a new thread for each cell, but there may be efficiency gains in 
   running rows in parallel."
  ([world function]
    (map-world world function nil))
  ([world function additional-args]
    (into []
           (pmap (fn [row]
                    (into [] (map
                             #(apply function
                                     (cons world (cons % additional-args)))
                             row)))
                  world))))

As you see, this maps across a two dimensional array, mapping over each of the rows of the array, and, within each row, mapping over each cell in the row. As you can see, in this current version, I parallel map over the rows but serial map over the cells within a row.

Here's why:

Hybrid parallel/non-parallel version

This is the current default version. It runs at about 650% processor loading - i.e. it maxes out six cores and does some work on a seventh. The eighth core is doing all the Unix housekeeping.

(time (def x1 (utils/map-world
        (utils/map-world w heightmap/tag-altitude (list hm))
        heightmap/tag-gradient)))
"Elapsed time: 24592.327364 msecs"
#'mw-explore.optimise/x1

Pure parallel version

Runs at about 690% processor loading - almost fully using seven cores. But, as you can see, fully one third slower.

(time (def x2 (utils/map-world-p-p
       (utils/map-world-p-p w heightmap/tag-altitude (list hm))
        heightmap/tag-gradient)))
"Elapsed time: 36762.382725 msecs"
#'mw-explore.optimise/x2

(For completeness, the clojure.core.reducers/map is even slower, so is not discussed in any further detail)


Non parallel version

Maxes out one single core, takes about 3.6 times as long as the hybrid version. But, in terms of processor cycles, that's a considerable win - because 6.5 cores for 24 seconds is 156 seconds, so there's a 73% overhead in running threads across multiple cores.

(time (def x2 (utils/map-world-n-n
       (utils/map-world-n-n w heightmap/tag-altitude (list hm))
        heightmap/tag-gradient)))
"Elapsed time: 88412.883849 msecs"
#'mw-explore.optimise/x2

Now, I need to say a little more about this. It's obvious that there's a considerable set-up/tear-down cost for threads. The reason I'm using pmap for the outer mapping but serial map for the inner mapping rather than the other way round is to do more work in each thread.

However, I'm still simple-mindedly parallelising the whole of one map operation and serialising the whole of the other. This particular array is 2048 cells square - so over four million cells in total. But, by parallelising the outer map operation, I'm actually asking the operating system for 2048 threads - far more than there are cores. I have tried to write a version of map using Runtime.getRuntime().availableProcessors() to find the number of processors I have available, and then partitioned the outer array into that number of partitions and ran the parallel map function over that partitioning:

(defn adaptive-map 
  "An implementation of `map` which takes note of the number of available cores."
  [fn list]
  (let [cores (.availableProcessors (. Runtime getRuntime ))
        parts (partition-all (/ (count list) cores) list)]
    (apply concat (pmap #(map fn %) parts))))

Sadly, as A A Milne wrote, 'It's a good sort of brake But it hasn't worked yet.'

But that's not what I came to talk about.

We are reaching the physical limits of the speed of switching a single processor. That's why our processors now have multiple cores. And they're soon going to have many more cores. Both Oracle (SPARC)  and ARM are demoing chips with 32 cores, each 64 bits wide, on a single die. Intel and MIPS are talking about 48 core, 64 bit wide, chips. A company called Adapteva is shipping a 64 core by 64 bit chip, although I don't know what instruction set family it belongs to. Very soon we will have more; and, even if we don't have more cores on a physical die, we will have motherboards with multiple dies, scaling up the number of processors even further.

The Challenge


The challenge for software designers - and, specifically, for runtime designers - is to write software which can use these chips reasonably efficiently. But the challenge, it seems to me, for hardware designers, is to design hardware which makes it easy to write software which can use it efficiently.

Looking for the future in the past, part one


Thinking about this, I have been thinking about the Connection Machine. I've never really used a Connection Machine, but there was once one in a lab which also contained a Xerox Dandelion I was working on, so I know a little bit about them. A Connection Machine was a massively parallel computer having a very large number - up to 65,536 - of very simple processors (each processor had a register width of one bit). Each processor node had a single LED lamp; when in use, actively computing something, this lamp would be illuminated. So you could see visually how efficient your program was at exploiting the computing resource available.

[Incidentally while reading up on the Connection Machine I came across this delightful essay on Richard Feynman's involvement in the project - it's of no relevance to my argument here, but nevertheless I commend it to you]

The machine was programmed in a pure-functional variant of Common Lisp. Unfortunately, I don't know the details of how this worked. As I understand it each processor had its own local memory but there was also a pool of other memory known as 'main RAM'; I'm guessing that each processor's memory was preloaded with a memory image of the complete program to run, so that every processor had local access to all functions; but I don't know this to be true. I don't know how access to main memory was managed, and in particular how contention on access to main memory was managed.

What I do know from reading is that each processor was connected to twenty other processors in a fixed topology known as a hypercube. What I remember from my own observation was that a computation would start with just one or a small number of nodes lit, and flash across the machine as deeply recursive functions exploded from node to node. What I surmise from what I saw is that passing a computation to an unoccupied adjacent node was extremely cheap.

A possibly related machine from the same period which may also be worth studying but about which I know less was the Meiko Computing Surface. The Computing Surface was based on the Transputer T4 processor, a 32 bit processor designed specifically for parallel processing. Each transputer node had its own local store, and very high speed serial links to its four nearest neighbours. As far as I know there was no shared store. The Computing Surface was designed to be programmed in a special purpose language, Occam. Although I know that Edinburgh University had at one time a Computing Surface with a significant number of nodes, I don't know how many 'a significant number' is. It may have been hundreds of nodes but I'm fairly sure it wasn't thousands. However, each node was of course significantly more powerful than the Connection Machine's one bit nodes.

A caveat


One of the lessons we learned in those high, far off, arrogant days was that special purpose hardware that could do marvellous things but was expensive lost out to much less capable but cheaper general purpose hardware. There's no point in designing fancy machines unless there's some prospect that they can be mass produced and widely used, because otherwise they will be too expensive to be practical; which presumes not only that they have the potential to be widely used, but also that you (or someone else related to the project) is able to communicate that potential to people with enough money to back the project.


Hardware for Post Scarcity software


Before going forward with this argument, lets go back. Let's go back to the idea of the Clojure map function. In fact, let's go back to the idea of a function.

If a processor is computing a function, and that function has an argument, then before the function can be computed the value of the argument must be computed; and, as the function cannot be computed until the value of the argument has been computed, there is no point in handing off the processing of the argument to another processor, because the first processor will then necessarily be idle until the value is returned. So it may just as well recurse up the stack itself.

However, if a function has two arguments and values of both must be computed, then if the first processor can hand off processing of one of them to another, similar, processor, potentially the two can be processed in the time in which the original processor could process just one. Provided, that is, that the cost of handing off processing to another processor is substantially less than the cost of evaluating the argument - which is to say, as a general thing, the closer one can get the cost of handing off to another processor to the cost of allocating a stack frame on the current processor, the better. And this is where current-generation hardware is losing out: that cost of handing off is just way too high.

Suppose, then, that our processor is a compute node in a Connection-Machine-like hypercube, able to communicate directly at high speed with twenty close neighbours (I'll come back to this point in detail later). Suppose also that each neighbour-connection has a 'busy' line, which the neighbour raises when it is itself busy. So our processor can see immediately without any need for doing a round-robin which of its neighbours are available to do new work.

Our processor receives a function call with seven arguments, each of which is a further function call. It hands six of these off to idle neighbours, pushes one onto its own local stack, computes it, and recurses back to the original stack frame, waits for the last of the other six to report back a value, and then carries on with its processing.

The fly in the ointment here is memory access. I assume all the processors have significant read-only cache (they don't need read-write cache, we're dealing with immutable data; and they only need a very small amount of scratchpad memory). If all six of the other processors find the data they need (for these purposes the executable function definition is also data) in local cache, all is good, and this will be very fast. But what if all have cache misses, and have to request the data from main memory?

This comes down to topology. I'm not at all clear how you even manage to have twenty separate data channels from a single node. To have a data channel from each node, separately, to main memory simply isn't possible - not if you're dealing with very large numbers of compute nodes. So the data bus has to be literally a bus, available to all nodes simultaneously. Which means, each node that wants some data from main memory must ask for it, and then sit watching the bus, waiting for it to be delivered. Which also means that as data is sent out on the bus, it needs to be tagged with what data it is.

Looking for the future in the past, part two


In talking about the Connection Machine which lurked in the basement of Logica's central London offices, I mentioned that it lurked in a lab where one of the Xerox 1108 Dandelions I was employed to work on was also located. The Dandelion was an interesting machine in itself. In typical computers - typical modern computers, but also typical computers of thirty years ago - the microcode has virtually the status of hardware. While it may technically be software, it is encoded immutably into the chip when the chip is made, and can never be changed.

The Dandelion and its related machines weren't like that. Physically, the Dandelion was identical to the Star workstations which Xerox then sold for very high end word processing. But it ran different microcode. You could load the microcode; you could even, if you were very daring, write your own microcode. In its Interlisp guise, it had all the core Lisp functions as single opcodes. It had object oriented message passing - with full multiple inheritance and dynamic selector-method resolution - as a single opcode. But it also had another very interesting instruction: BITBLT, or 'Bit Block Transfer'.

This opcode derived from yet another set, that developed for an earlier version of the same processor on which Smalltalk was first implemented. It copied an arbitrary sized block of bits from one location in memory to another location in memory, without having to do any tedious and time consuming messing about with incrementing counters (yes, of course counters were being incremented underneath, but they were in registers only accessible to the the microcode and which ran, I think, significantly faster than the 'main' registers). This highly optimised block transfer routine allowed a rich and responsive WIMP interface on a large bitmapped display on what weren't, underneath it all, actually terribly powerful machines.

BITBLT for the modern age


Why is BITBLT interesting to us? Well, if we can transfer the contents of only one memory location over the bus in a message, and every message also needs a start-of-message marker and an object reference, then clearly the bus is going to run quite slowly. But if we can say, OK, here's an object which comprises this number of words, coming sequentially after this header, then the amount of overhead to queuing messages on the bus is significantly reduced. But, we need not limit ourselves to outputting as single messages on the bus, data which was contiguous in main memory.

Most of the things which will be requested will be either vectors (yes, Java fans, an object is a vector) or lists. Vectors will normally point to other objects which will be needed at the same time as the vector itself is needed; list structures will almost always do so. Vectors will of course normally be contiguous in memory but the things they point to won't be contiguous with them; lists are from this point of view like structures of linked vectors such that each vector has only two cells.

So we can envisage a bus transfer language which is in itself like a very simple lisp, except decorated with object references. So we might send the list '(1000 (2000) 3000) over the bus as notionally

[ #00001 1000 [ #00002 2000 ] 3000 ]

where '[' represents start-of-object, '#00001' is an object reference, '1000' is a numeric value, and ']' is end-of-object. How exactly is this represented on the bus? I'll come back to that; it isn't the main problem just now.

Requesting and listening


Each processor can put requests onto the 'address bus'. Because the address bus is available to every processing node, every processing node can listen to it. And consequently every processing node does listen to it, noting every request that passes over the bus in a local request cache, and removing the note when it sees the response come back over the data bus.

When a processing node wants a piece of data, it first checks its local memory to see whether it already has a copy. If it does, fine, it can immediately process it. If not, it checks to see whether the piece of data has already been requested. If it has not, it requests it. Then it waits for it to come up the bus, copies it off into local store and processes it.

That all sounds rather elaborate, doesn't it? An extremely expensive way of accessing shared storage?

Well, actually, no. I think it's not. Let's go back to where we began: to map.

Mapping is a very fundamental computing operation; it's done all the time. Apply this same identical function to these closely related arguments, and return the results.

So, first processor gets the map, and passes a reference to the function and arguments, together with indices indicating which arguments to work on, to each of its unemployed neighbours. One of the neighbours then makes a request for the function and the list of arguments. Each other processor sees the request has been made, so just waits for the results. While waiting, each in this second tier of processors may sub-partition its work block and farm out work to unemployed third tier neighbours, and so on. As the results come back up the bus, each processor takes its local copy and gets on with its partition, finally passing the results back to the neighbour who originally invoked it.

The memory manager


All this implies that somewhere in the centre of this web, like a fat spider, there must be a single agent which is listening on the address bus for requests for memory objects, and fulfilling those requests by writing the objects out to the data bus. That agent is the memory manager; it could be software running on a dedicated processor, or it could be hardware. It really doesn't matter. It's operating a simple fundamental algorithm, maintaining a garbage collected heap of memory items and responding to requests. It shouldn't be running any 'userspace' code.

Obviously, there has to be some way for processor nodes to signal to the memory manager that they want to store new persistent objects; there needs to be some way of propagating back which objects are still referenced from code which is in play, and which objects are no longer referenced and may be garbage collected. I know I haven't worked out all the details yet. Furthermore, of course, I know that I know virtually nothing about hardware, and have neither the money nor the skills to build this thing, so like my enormous game engine which I really know I'll never finish, it's really more an intellectual exercise than a project.

But... I do think that somewhere in these ideas there are features which would enable us to build higher performance computers which we could actually program, with existing technology. I wouldn't be surprised to see systems fairly like what I'm describing here becoming commonplace within twenty years.

[Note to self: when I come to rework this essay it would be good to reference Steele and Sussman, Design of LISP-based Processors.]

Sunday, 28 September 2014

More on modelling rivers

I've been writing a lot about politics recently - and with reason. But now it's time to be getting back to writing about software, and, specifically, about river flows again.

Computed river map. Ignore the vegetation, it's run only a few generations and does not yet show natural patterns.
I wrote almost a year ago that I had had the first glimmer of success with modelling river flows. Well, some success was right, but not enough success. I didn't have a software framework in which I could model other things I wanted to model in my world, nor one with which I could play flexibly. I also - because I was working with maps of my fictional world, and not the real world - couldn't assess how well my algorithms were working, particularly as I had persistent diagonal artefacts.

So I decided to restart working in Clojure, using a cellular automaton, and with maps of Britain - maps I'm familiar with. I've written about that, too. From the point of view of modelling settlement, it's working well enough to be promising. So now I'm back to river flows, and once again I'm having 'the first glimmerings of success', and once again, frustrations. The picture above shows where I'm up to.

As you can see, I'm still using the 8Km granularity map. I can now actually compute water flows on the 1Km granularity map, but I haven't yet even tried to render them - it's a massive amount of data. But the 8Km map shows both that my algorithm partly works, and that it really does not work anything like well enough.

What works and what doesn't

What works? You can see the River Clyde from about Biggar down to the Falls of Clyde - but there, it disappears. Bits of the mouth of the Clyde are there, but not joined up. The lower Ayr is not only there, but identified as navigable. All of that is promising. I am getting some bits of some rivers showing up, and in the right place.

What doesn't work? Well, 8Km is a horribly wrong scale for doing this sort of thing. Even 1Km granularity is much too coarse, but attempting to map rivers on an 8Km scale... very few of our rivers have enough flow of water to show up. But worse, I haven't implemented 'laking' (as I did in the earlier, Java implementation). When a river arrives at a local low spot, it cannot flow up hill, so it just vanishes. At this coarse a granularity such local low spots are bound to exist. Actually, they will at any granularity - landscapes are fractal. So I need to rewrite my algorithm to deal with local lowspots - fill them up with water until it spills over and follows the rest of the valley down.
Visualisation of the problem. Numbers represent altitude, intensity of blue represents water flow.

This explains why my map of Britain shows so few rivers. The Spey is clearly visible from Aberlour down to Fochabers; the Tweed is a feature in Peebleshire; the Cree and the Esk are visible and recognised as potentially navigable. In England, the Tyne is entirely missing but the Tees is present and marked as navigable; a bit of the Ribble is recognisable as is a bit of the Mersey; tiny bits of the Thames show up around Oxford; and there's a river running north-south, west of London, that might be the Chess or the Misbourne. The Exe and the Tamar are also present. But several of the major rivers of Britain are not present at all, and many of those that are, are, like the Clyde, present only for parts of their course.

The Algorithm

So what is my algorithm? I had hoped, by this time, to publish all my source code so that you can see it. Unfortunately, when I went through the polite step of running this past my present employers, I hit a snag. Although this is - quite obviously - of no relevance to their business and of no interest to them, they claim that it is their secret, proprietary code. Which means that I and my present employers will part; but in the meantime (and possibly afterwards) I cannot publish this code.

I'm damned if I see why I should not publish a description of the algorithm, though.

Firstly, rainfall. I'm not (yet) trying to model the variability of rainfall. I could; last autumn's algorithm recognised that in middle latitudes the wind is primarily a coriolis wind, from the west. It carries damp air from the ocean to the west, and precipitates that rain as it cools - which it does as it rises over higher ground. So west facing slopes recieve more of the potential rainfall than east facing, highlands than low lands, western areas than eastern. We can verify those statements by looking at actual rainfall maps, and they're fairly easy to model. I have not done that yet; my present code just dumps one unit of rain on every cell of the map.

Then, from each cell, I compute the amount of rain which flows through that cell recursively by summing the flows through each of its neighbours which have a greater altitude. This is the simple-minded observation that water flows down hill. And, of course, it does. Obviously, again, the flow value at which I decide a river exists is arbitrary; for this map, I've shown a river where the flow through the cell is thirty units or above - which is to say, where the cell has collected the rainfall from at least thirty upstream cells.

There are two problems with this algorithm as it exists at present. When water in the real world hits a low point then - disregarding the amount lost to evaporation, which in Britain we can do, although in my fictional world I can't entirely - it builds up in a lake until it overflows the lip of the local hollow and continues. I had hoped that river valleys would show up sufficiently strongly that I could get away without laking, but sadly they don't. At the point the Tweed peters out on my map, the altitude of the last cell in which it is visible is 49 (arbitrary units - a monochrome heightmap gives me only 256 gradations of altitude, and sea level is 10). The altitude of the next cell down valley is 50. Given these are average elevations over an 8Km square, it's not in the least impossible that that's correct, but it stops the flow.

Worse still, the last recognised cell of the Ribble is at elevation 28. The next cell down valley - to which the river should flow - is also 28. The Clyde peters out at elevation 35, and the next cell down valley is also 35. It's inevitably the case that long rivers have sections in which they don't fall 5 metres in 8 kilometres, even disregarding the coarse granularity of my elevation data. But again, my present algorithm doesn't cope.

Parallelism and mutable data

There's a second problem, though. This one is not a modelling problem, it's a software framework problem. I'm working, now, in Clojure, and on the whole I find that extremely comfortable; it's a lovely language in which to write exploratory code. And I've been working with a framework which allows me to map a function across a two dimensional array, passing the function two arguments: the whole array, and the cell on which it's to operate. It returns a new copy of that cell, and the mapping operation constructs a new array from all the new cells. This has served me very well during my exploration of modelling environmental change and human settlement, because this process inherently allows great parallelism. On my eight core desktop machine, I can see 5 cores simultaneously maxed out processing a map.

But, the parallelism works because in Clojure data is immutable. In order to process the flow for each cell, I have to process, recursively, the flow in each cell upstream of it. But because the map is immutable, I can't cache those results on the map, so I can only return the one cell I was considering; all those partial results upstream are lost and must be recomputed when those cells are themselves considered. That's obviously wasteful, wasting all, and more, of the time I saved by paralellism. I haven't yet, however, worked out a practical way to pass those partial results round so that they don't need to be recomputed. That's an issue; it's part of the tradeoff of using immutable data, and also, possibly, part of the matter of learning better to use functional languages. But it isn't a blocker. While running this on a one kilometre resolution map is costly in time, it does run.

Laking

'Laking' can't easily be hacked onto my present algorithm. If you allow the algorithm to peak over hilltops as it works upstream, you have a halting problem. What is needed is a second pass which finds low spots where there is a flow of greater-than-river-flow value, and reworks from there.

One possible algorithm which I shall explore would then find the lowest adjoining cell and propagate the flow to that cell and so downstream. That isn't strictly laking, that's a  minimal-uphill algorithm; but it would, I believe, more or less accurately model the river flows.

A slight variation on the minimal-uphill algorithm would be to lower the lowest adjoining cell until it is lower than the cell in which the flow has stopped; that's a slightly more natural solution, with a possible risk of creating gorges where there are no gorges. Again, that's something to explore.

The final workaround is true laking. Let the water level rise - in all cells with the same altitude - until a way out is found. What I worry about laking on my relatively coarse models is that I will construct enormous lakes in unlikely places. It's also likely to turn the whole of East Anglia and Lincolnshire into essentially one lake - but if one interprets these large areas of water on flat land as fen rather than lake, that's more or less right..

Progress is being - very slowly - made.

Mind you, if it takes me two years to work out how to map rivers, this huge game world - which I really know I'll never finish in my lifetime - looks even further out of reach.

Thursday, 10 April 2014

On Clojure as a multi-user environment

Today, not having really enough to do at work, I was reading a Guy Steele and Richard Gabriel's (highly partisan) paper 'The Evolution of Lisp', and thinking about Post Scarcity Computing and about Clojure.

Lisp has failed to get traction too many times because people insisted on their own idea of what constituted a pure Lisp. Let's be clear about it, if I were designing my perfect Lisp it would be different from Clojure in a number of significant ways. Nevertheless, Clojure is more or less the best Lisp we have now, and furthermore it has traction. Furthermore, it has a number of most excellent features which, had I not been exposed to Clojure, I would not have thought of myself. So if you're going to build a post-scarcity computing environment now, Clojure is not a bad place to start.

But let's think about how Clojure impacts on the ideas I put forward in Post Scarcity Computing. Firstly and most importantly, in Clojure data is (with some special case exceptions which we'll come to) immutable. That has a number of interesting consequences, and one of them is that an older data cannot ever point to newer data.

How does that impact on a multi-user environment? Well, probably no-one's ever thought about that in the context of Clojure, because probably no-one's ever thought about having several people connected into the same Clojure session. But, suppose you connect to a base Clojure environment, and run some computation, and then I connect to the same base environment. I cannot see anything that's going on in your computation, because all the partial results of your computation are newer than the base environment.

You can try this yourself. There's probably simpler ways to do this, but here's mine: first, install emacs 24, and cider. Next, open three terminals, and run

lein repl :headless

in one of them. This starts a headless nrepl, and prints out the port number it's running on:

simon@fletcher:~$ lein repl :headless
nREPL server started on port 48705 on host 127.0.0.1

In the other two, start headless emacs sessions:

simon@fletcher:~$ emacs -nw

In each emacs session, type

escape-X cider

You'll be prompted for a host, with a default of 127.0.0.1 (localhost). Accept this. You're then prompted for a port; type in the port number that the nrepl server printed.

Now, you have two terminals both connected to the same Clojure session. In one, evaluate something:

user> (list 'a 'b)
(a b)

In the other, type *1 to look at the result of the last evaluation:

user> *1
nil

But in the first window, the one where you evaluated something, *1 returns the value you'd expect:

user> *1
(a b)

So, as I said above, two separate sessions whose computations and local environment are invisible to one another. How can they be made visible? It's as easy as defining a var:

user> (def shared (list 'a 'b))
#'user/shared

Now, in your other emacs terminal, the value of shared is what was set in the first:

user> shared
(a b)

So, by default, everything a user does is private. It can be made public by simply defining a variable, in the default (user) package. Again in one terminal:

user> (ns foo (:use clojure.core))
nil
foo> (def hidden (list 'p 'q))
#'foo/hidden

In the other:

user> (ns bar (:use clojure.core))
nil
bar> hidden
CompilerException java.lang.RuntimeException: Unable to resolve symbol: hidden \
in this context, compiling:(/tmp/form-init3330134955068707486.clj:1:691)        

Unless the other user can guess the package name you're working in, they can't see the variables you bind. Of course, in Clojure as it exists now, if you do know the name of the package another user is using, you can see their variable bindings, because Clojure as it exists now isn't designed as a multi-user environment. And security by obscurity isn't really security at all.

But nevertheless you can see that Clojure could easily be made an effective multi-user environment. You can see that it would be easy to write an 'executive' function, which provided its own read-eval-print loop in an environment in which it had a (mutable) Java HashMap bound to a local variable, and could intercept a particular input pattern to store 'user-private' variables into that hashmap.

It's not hard, in fact, to see how a multi-user system could be built on a persistent Clojure session. I'll return to this soon, in further blog posts.


Monday, 16 December 2013

In praise of LuminusWeb

Well, I've just finished my first in-anger, for-a-paying-customer, website in Clojure. Essentially it's just a very simple CRUD system; it landed on my desk last week in a rush because the agency which had been supposed to be building it had drawn some pretty pictures and then thrown their hands up in the air and said 'this is too hard'.

It wasn't a requirement it be written in Clojure; in fact, until I tacked a credits line on the bottom of the pages saying 'Powered by Clojure' I don't think the customer knew that it was. I estimated four days on a fixed price basis; I think this was fair. In fact it took six, but I worked over the weekend so the project hasn't been delayed by my overrun.

Some of the overrun was unforeseen - the agency who abandoned the job had built the forms in JotForm, and they proved to be so horrible that I had to rewrite them from scratch - the HTML was bizarrely bad, and none of the field names were meaningful. Also, the pretty pictures drawn by the agency were all fixed size - they didn't flow or scale. I admit I'm a snob, but if a website is going to be identified as my work I want it to be right. So now, it is right (well, mostly; the navigation does something ugly and not very usable if you shrink a desktop browser window too small, but I haven't yet found a workaround for that which fits with the design). Now, it uses media queries to distinguish smartphones, tablets and desktops, and serves the appropriate CSS for each. Yes, that cost me time, too, but in my opinion it's worth it.

And, of course, there was a bit of feature creep - there always is. The customer asked for several features which hadn't been in the original specification, and I've delivered them - but they all add time.

But the overrun I did foresee - learning time - was much less than I would have expected, and that was down to LuminusWeb. LuminusWeb is not really so much an integrated toolkit in its own right as a collection of tools from a range of developers. It doesn't use all the tools which, if I was selecting for myself, I would choose; for example it uses Selmer for generating dynamic content rather than the, in my opinion, much more elegant Enlive. But what LuminusWeb does provide - which makes all the difference if you're building something against a deadline - include

  • a great project template which sets up most of your scaffolding
  • a very clear tutorial, and
  • excellent documentation.

LuminusWeb's scaffolding and examples, with a little help from a couple of books


allowed me to build what I needed very quickly, and with remarkably little pain.

LuminusWeb is also very up-to-date; it uses current or near versions of all the tools it depends on.

There is one thing I'm disappointed with. I had hoped to integrate Chris Emerick's Friend authentication package to do OpenID authentication, but I struggled with the documentation (despite good example programs from both Chris himself and from Assen Kolov), and simply couldn't get this going in time. I did work out how I would have done proper database-layer authentication, but in the end I simply did application layer authentication because it was quick and easy.

Altogether, it's been a very pleasant experience and I'm kicking myself for not having done it sooner.

Sunday, 10 November 2013

Implementing Milkwood in Java and Clojure

I was recently given, as a coding exercise by a potential employer, this problem.

It's an interesting problem, because the set of N-grams (the problem specification suggests N=3, so trigrams, but I'm sufficiently arrogant that I thought it would be more interesting to generalise it) forms, in effect, a two dimensional problem space. We have to extend the growing tip of the generated text, the meristem, as it were; but to do so we have to search sideways among the options available at each point. Finally, if we fail to find a way forward, we need to back up and try again. The problem seemed to me to indicate a depth-first search. What we're searching is not an 'optimal' solution; there is no 'best' solutions. All possible solutions are equally good, so once one solution is found, that's fine.


Data design

So the first issue is (as it often is in algorithmics) data design. Obviously the simpleminded solution would be to have an array of tuples, so the text:

I came, I saw, I conquered.

would be encoded as

I came I
came I saw
I saw I
saw I conquered

The first thing to note is these tuples are rules, with the first N-1 tokens acting as the left hand side of the rule, and the last token acting as the right hand side:

I came => I
came I => saw
I saw => I
saw I => conquered

To be interpreted as 'if the last N-1 tokens I emitted match the left hand side of a rule, the right hand side of that rule is a candidate for what to emit next.'
The next thing to note is that if we're seeking to reconstruct natural language text with at least a persuasive verisimilitude of sense, punctuation marks are tokens in their own right:

I came => COMMA
came COMMA => I
COMMA I => saw
I saw => COMMA
saw COMMA => I
COMMA I => conquered
I conquered => PERIOD

Now we notice something interesting. It's perfectly possible and legitimate to have two rules with the same left hand side, in this case {COMMA I}. So we could recast the two {COMMA I} rules as a single rule:

COMMA I => [saw | conquered]

This means that, in our table of rules, each left-hand-side tuple can be distinct, which makes searching easier. However, a system which searches a table of N-ary tuples for matches isn't especially easy or algorithmically efficient to implement. If we had single tokens, we could easily use maps, which can be efficient. One can see at a glance that two tokens occur repeatedly in the first position of the left hand side of the rules, 'I', and 'COMMA'.

'I' has three possible successors:

I [came | saw | conquered] 

However the right hand side is not the same for 'saw' as it is for conquered, so this composite rule becomes:

I => [came => [COMMA]| saw => [COMMA]| conquered => [PERIOD]]

This enables us to consider our rules as a recursive map of maps:

[
    I => [came => [COMMA]| saw => [COMMA]| conquered => [PERIOD]] |
    came => [COMMA => [I]] |
    COMMA => [I => [saw | conquered]] |
    saw => [COMMA => [I]]
]
And thus, essentially, as a tree that, given a path, we can walk. Matching becomes trivial and efficient.
Thus far we're almost language independent. I say almost, because in Prolog (which would be a very good implementation language for this problem) we'd simply assert all the N-grams as predicates and let the theorem solver sort them out. However, I've not (yet) tackled this problem in Prolog.

Implementation: Java

My Java implementation is milkwood-java.

I started in Java, because that's what I was asked to do. Java (or C#, which is to a very close approximation the same language) is pretty much the state of the art as far as imperative, procedural languages go. Yes, I know it's object oriented, and I know Java methods are in principal functions not procedures. But it is still an imperative, procedural language. I say so, so it must be true. What I hope makes this essay interesting is that I then went on to reimplement in Clojure, so I can (and shall) compare and contrast the experience. I'm not (yet) an experienced Clojure hacker; I'm an old Lisp hacker, but I'm rusty even in Lisp, and Clojure isn't really very Lisp-like, so my Clojure version is probably sub-optimal.

But let's talk about Java. I made a tactical error early in my Java implementation which makes it less than optimal, too. We have an input file to analyse, and we don't know how big it is. So my first instinct wasn't to slurp it all into memory and then tokenise it there; my first instinct was to tokenise it from the stream, in passing. That should be much more conservative of store. And so I looked in the Java libraries, and there was a library class called StreamTokenizer. Obviously, that's what I should use, yes? Well, as I learned to my cost, no, actually. The class java.io.StreamTokenizer is actually part of the implementation of the Java compiler; it's not a general purpose tokeniser and adapting it to tokenise English wasn't wonderfully successful. That wasted a bit of time, and at the time of writing the Java implementation still depends on StreamTokenizer and consequently doesn't tokenise quite as I would like. If I backported the regex based tokeniser I used in the Clojure version to the Java version (which I easily could) it would be better.
So the first gotcha of Java was that the libraries now contain a lot of accreted crud.

The second point to note about Java is how extraordinarily prolix and bureaucratic it is. My Java implementation runs to almost a thousand lines, of which over 500 lines are actual code (317 comment lines, 107 blank lines, 36 lines of import directives). Now, there are two classes in my solution, Window and WordSequence, which could possibly be refactored into one, saving a little code. But fundamentally it's so large because Java is so prolix.

By contrast, the Clojure reimplementation, which actually does more, is a third the size - 320 lines, of which 47 are blank and 29 are inline comments. I don't yet have a tool which can analyse Clojure documentation comments, but at a guess there's at least fifty lines of those, so the Clojure solution is no more than two fifths of the size of the Java.

The Java implementation comprises eight classes:
  • Composer essentially the two mutually recursive functions which perform depth first search over the rule set, to compose output
  • Digester scans a stream of text and composes from it a tree of rules
  • Milkwood contains the main() method; parses command line arguments
  • RuleTreeNode a node in the tree of rules
  • Tokeniser a wrapper around StreamTokenizer, to try to get it to tokenise English; not very successful
  • Window a fixed length stack of tokens, used as a glance-back window in both scanning and composing
  • WordSequence a sequence of tokens implemented as a queue
  • Writer a wrapper around BufferedWriter which performs on-the-fly orthographic tricks to create a verisimilitude of natural English
One might argue that that's excessive decomposition for such a small problem, but actually small classes greatly increase the comprehensibility of the code.

There are things I'm not proud of in the Java implementation and I may at some stage go back and polish it more, but it isn't a bad Java implementation and is fairly representative of the use of Java in practice.

Clojure implementation

My Clojure implementation is milkwood-clj.

Some things to say about the Clojure implementation before I start. First, I implemented it in my own time, not under time pressure. Second, although I'm quite new to Clojure, I'm an old Lisp hacker, and even when I'm writing Java there are elements of Lisp-style in what I write. Thirdly, although I'm trying to write as idiomatic Clojure as I'm able, because that's what I'm trying to learn, I am a Lisp hacker at heart and consequently use cond far more than most Clojure people do - despite the horrible bastardised mess Clojure has made of cond. Finally, it was written after the Java implementation so I was able to avoid some of the mistakes I'd made earlier.

I used LightTable as my working environment. I really like the ideas behind LightTable and suspect that in time it will become my IDE of choice, but I haven't got it working for me yet. Particularly I haven't got its 'documentation at cursor' function working, which, given my current (lack of) familiarity with the Clojure, is a bit of a nuisance.

I tripped badly over one thing. Clojure, to my great surprise, does not support mutually recursive functions, and the algorithm I'd designed depends crucially on mutually recursive functions. However after a bit of flailing around, I remembered it does support dispatch in one function on different arities of arguments, and I was able to rewrite my two functions as different arity branches of the same function, which then compiled without difficulty.

The other trip was that map, in Clojure, is lazy. So when I tried to write my output using

(defn write-output
    "Write this output, doing little orthographic tricks to make it look superficially
     like real English text.

     output: a sequence of tokens to write."
    [output]
    (map write-token output))

nothing at all was printed, and I couldn't understand why not. The solution is that you have to wrap that map in a call to dorun to force it to evaluate.

Aside from that, writing in Clojure was a total joy. Being able to quickly test ideas in a repl ('Read Eval Print Loop') is a real benefit. But a clean functional language is so simple to write in, and data structures are so easy to build and walk.

Another thing Clojure makes much easier is unit tests. I got bogged down in the mutual recursion part of the Java problem and unit tests would have helped me - but I didn't write them because the bureaucratic superstructure is just so heavy. Writing unit tests should be a matter of a moment, and in Clojure it is.
I broke the Clojure implementation into four files/namespace:
  • analyse.clj read in the input and compile it into a rule tree; more or les Tokeniser and Digester in milkwood-java;
  • core.clj essentially replaces Milkwood in milkwood-java; parses command line arguments and kicks off the process;
  • synthesise.clj compose and emit the output; broadly equivalent to Composer and Writer in milkwood-java;
  • utils.clj small utility functions. Among other things, contains the equivalent of Window in milkwood-java.
Additionally there are two test files, one each for analyse and synthesise, containing in total seven tests with eight assertions. Obviously this is not full test coverage; I wrote tests to test specific functions which I was uncertain about.

Conclusion

Obviously, all Java's bureaucracy does buy you something. It's a very strongly typed language; you can't (or at least it's very hard to) just pass things around without committing to exactly what they will be at compile time. That means that many problems will be caught at compile time. By contrast, many of the functions in my Clojure implementation depend on being passed suitable values and will break at run time if the values passed do not conform.

Also, of course, the JVM is optimised for Java. I've blogged quite a bit about optimising the JVM for functional languages; but, in the meantime, my Java implementation executes about seven times as fast as my Clojure implementation (but I'm timing from the shell and I haven't yet instrumented how long the start up time is for Java vs Clojure). Also, of course, I'm not an experienced Clojure hacker and some of the things I'm doing are very inefficient; Alioth's Clojure/Java figures suggest much less of a performance deficit. But if peformance is what critically matters to you, it seems to me that probably the performance of Java is better, and you at least need to do some further investigation.

On the other hand, at bottom Java is fundamentally an Algol, which is to say it's fundamentally a bunch of hacks constructed around things people wanted to tell computers to do. It's a very developed Algol which has learned a great deal from the programming language experience over fifty years, but essentially it's just engineering. There's no profound underlying idea.

Clojure, on the other hand, is to a large extent pure Lambda calculus. It is much, much more elegant. It handles data much more elegantly. It is for me much more enjoyable to write.

Creative Commons Licence
The fool on the hill by Simon Brooke is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License