This is the TXR Lisp interactive listener of TXR 233.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> (defun merge-hashes (hlist)
(let ((hout (hash)))
(each ((h hlist))
(dohash (k v h) (push v [hout k])))
hout))
merge-hashes
2> (merge-hashes '(#H(() (a 1) (b 2) (c 3)) #H(() (b 5) (c 7) (d 8))))
#H(() (c (7 3)) (b (5 2)) (a (1)) (d (8)))
Now here is that function fully code golfed with unnecessary spaces removed and all symbols one character long: (defun m(x)(let((o(hash)))(each((h x))(dohash(k v h)(push v[o k])))o))
That's down to 70 characters: only about double the K3 size.If defun and the other built-ins were one character long, it would be down to 50 chars:
(d m(x)(l((o(H)))(e((h x))(D(k v h)(p v[o k])))o))
Now we have a fair comparison where we have leveled the field, eliminating the difference due to whitespace elimination and token condensation.Though still significantly by raw character count (35 versus 50), there is less clutter in it. Also, it defines the name m whereas the {.+...} syntax needs a few more characters to define a function, I think.
But wait; that's far from the shortest code necessary. What I wrote is a decently efficient way of doing it which iterates the input hashes imperatively and builds up the output. It can be done in other ways, like this:
1> (defun merge-hashes (hlist)
[group-reduce (hash) car (op cons (cdr @2) @1) [mappend hash-alist hlist]])
merge-hashes
2> (merge-hashes '(#H(() (a 1) (b 2) (c 3)) #H(() (b 5) (c 7) (d 8))))
#H(() (d (8)) (c (7 3)) (b (5 2)) (a (1)))
We can obtain a flat "assoc list" of all the key value pairs from all the dictionaries by mappend-ing them through hash-alist. hash-alist retrieves an assoc list from a single dictionary, and we map over that, appending these together.Then we can group-reduce the assoc list; group-reduce populates a hash by classifying elements from an input sequence into keys, which denote individual reduce accumulators. So all the a elements are subject to their own reduce job, so are the b elements and so forth. The reduce function is (op cons (cdr @2) @1); an anonymous function that takes the cdr (value element) of each pair (that pair coming as argument 2), and conses it onto the accumulator (coming in as argument 1), returning the new accumulator. Since nil is the empty list, and fetching a nonexistent hash key yields nil, the accumulation can boostrap implicitly from an empty hash.
Now if we were to remove all unnecessary whitespace and give a one-character name to everything, we now get:
(d m(h)[R(H)c(o n(r @2)@1)[M L h]])
That shows there is a potential to get this down to 35 characters, with suitable function/operator and variable names.Moreover, these 35 characters are yet easier to parse because all the parentheses/brackets are there: they comprise 14 out of the 35 characters! And we still have 5 spaces. So 19 characters out of the 35 are punctuation having to do with shaping the syntax; 16 are semantic.
If you remember that R is group-reduce, you know that its arguments are (H) c (o ...) and [M ...]. There is no guesswork about what goes with what.
The above K expression is actually verbose because it doesn't use very many characters for shaping the syntax tree. Most of the characters you see do something.
Languages like K leverage their terseness not just from compression of the input notation, but from the semantics of the operations. But they do not have a monopoly in semantics. Good semantics that enables terse programming can be found in languages that don't use terse notations at the token level.
The K3 example we have here is not leveraging good semantics; if that's the best that can be done, it suggests that k doesn't have a well-rounded library of operations for concisely manipulating dictionaries. (Could it be that the insistence on one-character names creates a pressure against that?)
It's better to have thousands of functions with descriptive names, and then be able to choose the best ones for expressing a given problem tersely, than to reach for one-letter naming as the primary means for achieving terseness, and then try to pull a one-size-fits-all library within that naming convention.