Clojuration
During my vacation, I was playing with a couple of things, Clojure being one of them: it’s a Lisp dialect on top of the Java Virtual Machine, with great Java interoperability, so one pretty much gets a whole truck of neat Java libraries, and can use a language that makes much more sense than Java. Well, at least if one’s been an Emacs user for more than a decade, Lisp just makes sense.
But let me just showcase the beauty and power of Clojure with a simple function:
(defn trim-at-boundary [string max-len]
(if (< (.length string) max-len)
string
(str (s/take
(reduce #(if (< (+ (.length %1) (.length %2) 1) (- max-len 3))
(str %1 " " %2) %1)
(s/split string #"s"))
(- max-len 3))
" ...")))
The function above takes a string and a maximum length, and truncates the string at a word boundary closest to the maximum length, or if that is not possible, at maximum length minus a few, and appends three dots, if the string was truncated. If the string is shorter than the maximum length, it is returned as-is.
Beautiful.
And how does it work? Easy!
First, it checks if the string’s within limits, and if so, returns it. Otherwise it goes into the “else branch”, which appends “…” to the first (max-len – 3) chars of some stuff.
That “stuff” does the magic: I use the (reduce f coll) function, which takes a function (that takes two parameters) and a collection (in this case, the string split at whitespace), and applies the function to the collection so that at first, it is called with the first two elements of it, then it is called with the result of that, and the next element, and so on and so forth.
The function I use is an in-line function, that checks if the length of the two parameters combined (plus one, for the separator) is smaller than the maximum length (minus 3, for the dots). If yes, then the two parameters are joined together with a single space inbetween. Otherwise the first is returned.
This means that from the first time the function encounters a case where appending the next word would go over the limit, it will return always return the first string, which is still below the limit. Unless, of course, the first word was over the maximum length, in which case the outer (s/take) will take care of truncating it, word boundary or not.
Fun, isn’t it?
Out of curiosity, I tried to implement something similar in Haskell, and this is what I ended up with:
strLenCat l a b | length a + length b + 1 < l = a ++ " " ++ b | otherwise = a trimAtBoundary s l | length s < l = s | otherwise = (take (l - 3) $ foldl1 (strLenCat (l - 3)) (words s)) ++ " ..."
Shorter, and perhaps easier to understand, but I needed to use a helper function, which perhaps, could be inlined one way or the other, but my Haskell knowledge doesn’t really go farther than this; and without guards, it would look awkward. Nevertheless, currying is a fun thing (do note the strLenCat call inside the foldl1!).
Lets see python!
def strLenCat (len, a, b):
if len (a) + len (b) + 1 < len:
return a + " " + b
else:
return a
def trimAtBoundary (s, len):
if len (s) < len:
return s
return reduce (lambda a,b: strLenCat (len - 3, a, b),
s.split (" "))[:len - 3] + " ...."
Looks strangely like Haskell, to be honest, except I’m using ifs instead of guards. On the flip side, I learned that python has reduce() aswell!
