Category: Scheme

Lambda bodies in Scheme (by )

So, if you look at a recent Scheme standard such as R7RS, you'll see that the body of a lambda expression is defined as <definition>* <expression>* <tail expression>; zero or more internal definitions, zero or more expressions evaluated purely for their side-effects and the results discarded, and a tail expression whose evaluation result is the "return value" of the resulting procedure.

I used to find myself using the internal definitions as a kind of let*, writing procedures like so:

(lambda (foo) (define a ...some expression involving foo...) (define b ...some expression involving a and/or foo...) ...some final expression involving all three...)

But the nested defines looked wrong to me, and if I was to follow the specification exactly, I couldn't intersperse side-effecting expressions such as logging or assertions amongst them. And handling exceptional cases with if involved having to create nested blocks with begin.

For many cases, and-let* was my salvation; it works like let*, creating a series of definitions that are inside the lexical scope of all previous definitions, but also aborting the chain if any definition expression returns #f. It also lets you have expressions in the chain that are just there as guard conditions; if they return #f then the chain is aborted there and #f returned, but otherwise the result isn't bound to anything. I would sometimes embed debug logging and asserts as side-effects within expressions that returned #t to avoid aborting the chain, but that was ugly:

(and-let* ((a ...some expression...) (_1 (begin (printf "DEBUG\n") #t)) (_2 (begin (assert (odd? a)) #t))) ...)

And sometimes #f values are meaningful to me and shouldn't abort the whole thing. So I often end up writing code like this:

(let* ((a ...) (b ...)) (printf "DEBUG\n") (assert ...) (if ... (let* ((c ...) (d ...)) ...) ...))

And the indentation slowly creeps across the page...

However, I think I have a much neater solution!

Read more »

Ugarit 2.0 released (by )

Unless I messed up the release process, Ugarit version 2.0 is now available from the Chicken Scheme egg repository.

What does this mean to you, dear reader? Not a huge amount; you can go and read the release notes at the bottom of the Ugarit documentation page for the fine detail. But, to summarise:

  • The beginnings of archival mode! As well as storing chains of snapshots of a filesystem, as Ugarit has always done (generally to be used as a versioned backup system), Ugarit vaults can now also store "archives", which are groups of files or directory trees identified by arbitary metadata, such as "This is the song 'Ooofarno' by 'Bobby and the Beaters', which is track 11 out of 12 from the 'Fishes In The Sea' album", or "This is a photo of Aunt Mavis taken at 13:58 on the 3rd of August, 2020, at Uncle Bob's 100th birthday party", or "This is a PDF of a paper by Donald Knuth on ternary numbers, called 'Simplified Arithmetic in Base Three'". You can then find things by searching on this metadata, which is much, much, nicer than creating trees of directories to organise all your stuff into. The user interface for getting things in and out of archives is still a bit minimal - but I have plans to fix that.

  • The way tags are stored has changed. Ugarit 2.0 will read vaults created by prior versions happily, but when it writes to a vault, it'll write new-format tags (which have type information as well as a pointer to something), which old versions of Ugarit won't be very pleased to find.

  • We now store a "metadata block" in every vault, pointed to by a hidden tag (we didn't used to be able to hide tags, so old versions of Ugarit will show you a funny tag, and complain if you try and do anything with it, as it's a new-format tag). This stores a vault format version number, so we can better handle incompatible changes to the vault format going forward; and as it's hashed and encrypted like any other block, it means you'll get an instant error if you try and connect to a vault using the wrong hashing and encryption settings, rather than bizarre errors further down the line.

  • We've made it possible to store large logs in the vault. When we do a snapshot or an archive import or something, we keep a log of warnings and recoverable errors that cropped up while doing so. This is stored as a file attached directly to the snapshot or import object, so it can be arbitrarily large.

  • Added log.sexpr and properties.sexpr files in the explore-mode interface, inside every snapshot or archive import object, which let you access the log and the metadata. These are files you can extract, or you can look at them with the new cat command.

  • Added a cat command in the explore-mode user interface to dump a file to the screen for viewing.

  • Added a client-side cache of snapshots and imports, which significantly speeds up the exploration of backup histories and archive metadata. Optionally, you can make the cache persist between sessions, otherwise it's made afresh for every explore session.

  • As well as the existing ability to fork a tag into two tags that share the same history (applicable both to snapshot tags and archive tags), added the ability to merge two tags into one, melding the two histories into one. This includes some exciting logic to combine those histories for display in explore mode!

  • Added a new sqlite backend, which provides a storage inside a single file, managed by sqlite. I wrote it to make testing easier, but it's a useful storage backend in its own right!

  • Tidied up the Ugarit internals, splitting the core up into a load of separate modules. This makes development easier for me, and means nothing to users.

So what's next? I want to improve the usability of archive mode - right now, it's quite easy to import a bunch of files, but you have to hand-edit a text file to provide metadata beyond what it can automatically extract (currently just basic file information, plus whatever it can extract from ID3 tags and Ogg metadata); and then you can explore the history of the archive (as a series of imports) through the explore interface, or use a command-line tool to search for files, and then extract them or stream them to standard output.

What I want is:

  • A shiny (web-based?) UI for searching the archive, seeing thumbnails of images, and the ability to download files with a single click or to perform bulk editing operations on metadata with ease and panache.

  • A music player, that lets me pick music from an archive to queue, or to be given an arbitrary search criteria to find music to random-play, playing direct from the archive.

  • A way to pick photos and assemble them into galleries, which are then publicly visible through a Web interface. Sarah wants to be able to put sequences of photos together, as well as individual photos that don't form parts of sequences, into multiple albums, for her blog publishing stuff (which is often quite image-heavy). The current image publishing framework I threw together for her years ago is a bit limiting now, and quite clunky to use.

  • A mountable filesystem that lets me access archives, either in a generic manner (with auto-generated directories for every property, and every value of that property, containing all files with that value of that property) or with customised directory layouts (such as presenting my music collection as /music/ARTIST/ALBUM/NUMBER:TITLE.EXTENSION, with all the capitalised bits generated from the metadata). I'd like to do this by adding this to the explore mode virtual filesystem, and then having that mountable.

Ugarit performance (by )

Ugarit was once renowned for its poor import performance, and rightly so. However, it's a lot faster these days - not, sadly, due to amazing optimisation work on my part, but because Thomas Hintz made write-u8vector! faster. It's not released yet, but will be in Chicken 4.10.

There's still work to be done, though. In my experiments with archival mode, I imported 9GiB into an archive in:

real    24m14.822s
user    17m51.485s
sys     1m59.920s

Writing an uncompressed tarball of the same 9GiB took:

real    8m49.931s
user    0m1.076s
sys     1m1.315s

That's a factor of 3. Ugarit spends four minutes waiting for I/O while tar spent eight minutes, which is puzzling, but Ugarit spent seventeen minutes of CPU time while tar spent one second; this will be down to the fact that Ugarit still copies each byte of the file several times between reading it in and writing it out, and I know how to fix that!

There will be some unavoidable architectural cost in the fact that Ugarit will always use at least two processes - a frontend and a backend, with the data sent over a pipe between them - but I think there's a lot I can reduce first. Onwards and downwards!

Ugarit archive mode manifest maker (by )

When I last wrote about Ugarit progress, I had developed archive mode to the point where one could import a list of files with metadata from a "manifest file", and then search for files based on the metadata from the manifest and stream out chosen files. I gave an example of using this to play MP3s matching a search pattern:

[alaric@ahusai ugarit]$ for i in `ugarit search test.conf music '(= ($ artist) "UNKLE")' keys`;
do ugarit archive-stream test.conf music $i | mpg123 -;
done

Well, that was all based on hand-written manifest files, which are no fun to produce (our music collection is large). As such, I've been working on a "manifest maker" that takes a list of files and directories and makes a manifest file from them, recursing down through directories to list all the files. And for each, it automatically extracts metadata into the manifest file, which can then be hand-edited if required, and then used to import from.

The idea is that the manifest maker will have support for a number of file types it knows how to extract additional metadata from, and the first one I've implemented is ID3 tag extraction from MP3s. I've implemented the ID3 V2.2 and ID3 V2.3 specs, as those were the two that I found present in the subset of my MP3 collection I'm testing against!

For example, here's the output it produced for one of my MP3s:

(object "./test-data/THE HOLLIES - He Ain't Heavy, He's My Brother.mp3"
  (filename = "THE HOLLIES - He Ain't Heavy, He's My Brother.mp3")
  (mime-type = "audio/mpeg")

   ;; Unknown ID3 tag "COMM"="engiTunNORM\x00 00000402 00000000 00001B59 00000000 00004E65 00000000 000040EC 00000000 00015FD5 00000000"
  (keyword = "Pop")
  (name = "He Ain't Heavy, He's My Brother")
  (creator = "THE HOLLIES")
  (creation-date = "2002")
  #;(featuring = "")
  (collection-name = "Legends CD2")
  #;(collection-volume = "")
  #;(collection-volumes = "")
  (volume-index = 16)
  (volume-size = 18)

  (mtime = 1428948696.0)
  (ctime = 1428948696.0)
  (size = 4063360))

It prints out unknown ID3 tags as comments, in case a human can glean some useful information from them to put into the metadata, and it suggests the names of metadata tags I might be able to provide by hand that it hasn't found (in this case, a tag for other people featured in the music, and two for indicating that this album is part of a set. As it happens, it is, as the "CD2" in the name suggests, but it wasn't indicated in the ID3 so I'll have to hand-edit it; likewise, the date from the MP3 of 2002 is clearly for the production of the album, not that classic track... ID3 metadata is often a bit shabby!). Also included are file mtime, ctime, and size in bytes.

I hope to add Ogg Vorbis metadata next; I'd like to add EXIF support to parse information out of the JPEGs in our vast family photo library, but it looks much harder, and I'm not sure how useful it will actually be!

Folding history (by )

Ugarit is a content-addressed store; the vault is a series of blocks, identified by a hash, that cannot change once they are written.

But logically, they appear as a set of "tags", each of which either points to an archive (a set of files with associated metadata, which can be added to, or the metadata of existing files changed) or snapshots (a chain of snapshots of a filesystem at a point in time).

So in a store where objects cannot be modified, how do we create the illusion of mutable state in these "tags"? Read more »

WordPress Themes

Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales
Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales