Thus spoke the rodent on Monday, 10 July 2017:

Composing a function of binary arity

Way back when, also known as 28th June 2017, I went to a London Clojurians coding dojo meetup. Wherein one of the ideas floated for small dojo projects was to code some cyphers.

Very well. My group started with a simple ROT. At which point I got an urge to split the character traversal algorithm from the actual operation performed on characters, hereafter referred to as f, because we're in Clojure-land and the Clojure way is to be wonderfully terse when naming function arguments. (Whereas I can be wonderfully terse about the benefits of having readable code. But since I don't have a drink at hand, let's leave the rant for some other day and continue.)

Anyway! Your quest, should you choose to accept it, is this: convert each character into an integer, pass it to an arbitrary function convert the result back into a character.

Great! This works:

(defn convert [f s]
  (apply str
         (map (comp char f int) s)))

Let's level up, shall we?

The new task is to convert each character into an integer, pass it to an arbitrary function along with its index in list, and convert the result back into a character.

f would now be a function of two arguments, aka binary arity, so we would use map-indexed. The problem is that char and int should only operate on the value, not on the index.

So this doesn't work:

(defn convert [f s]
  (apply str
         (map-indexed (comp char f int) s)))

I can do it with threading. Except as it happens, I'm trying to get more practice with comping. But I will write out the threading version anyway (this is what I went with during the dojo) because, as it turns out...

(defn convert [f s]
  (->>
    s
    (map int)
    (map-indexed f)
    (map char)
    (apply str)))

...it's easy to convert the above into exactly what I was looking for.

In short, you do not comp the functions directly, but you use transducers based off appropriate maps, in the same order as in the threading macro above. Which thus converts the binary arity function f into a unary arity transducing function (map-indexed f) accepting a collection. Yay!

(defn convert [f s]
  (transduce
    (comp
      (map int)
      (map-indexed f)
      (map char))
    str
    s))

Props to Nathell for kicking the idea around with me until I got to the final form :)

For the curious: why am I practicing comping? For performance reasons. Compared to a threading macro, comp'd transducers do not create a ton of temporary collections for holding intermediary results.

Tags: clojure