Hmm, an interesting feature of a bucket is that there's a definite root node you can mutate, whereas in order to mutate something onto the front of an alist, you need to handle the alist by way of another box: (nil (a . 1) (b . 2) (c . 3)). Since buckets don't need to be wrapped in boxes, they provide less of a separation of concepts, which could be good or bad. (Edit: Oh, I'm duplicating zck's comment a little here.)
On the same note, two buckets can't share a tail. However, they can share key tails and value tails individually.
Another advantage of buckets is that they require no dots to write, as you've shown.
Well, there are definitely fewer conses in ((a b) 1 2) than in ((a 1) (b 2)), but ((a . 1) (b . 2)) has them both beat. (Maybe I'm missing something.)
As for being palatable to read and write, I actually disagree, even with the decrease in punctuation.
When writing a literal bucket, to add or remove a key-value pair, I'd have to edit in two places. Besides that, I'd encounter horizontal layout annoyances: I'd have to word-wrap the keys and the values in the same way in order to see the bindings clearly. And every time I added or removed a binding, I'd have to rewrap.
Reading is a bit more of a wash. When reading a bucket in debug output, it's harder to look up specific bindings but a lot easier to see what keys exist. I think I do those two things in about equal proportions (when I'm working on Penknife, at least).
> Well, there are definitely fewer conses in ((a b) 1 2) than in ((a 1) (b 2)), but ((a . 1) (b . 2)) has them both beat. (Maybe I'm missing something.)
I resorted to cons counting to confirm it for my own simple mind and... you're correct! ^_^