A sample of Clojure destructuring from a blog post revisited

Reading blogs can be so thought-provoking.

I did experience it today after having read Paul Sanwald’s clojure basics: argument destructuring. It’s so short and easy to follow that I read it a couple of times. I seem to not like it much, as it left me wondering how to improve the code. What stroke me was the function symbol in map. I didn’t like it (and I bet many newcomers to Clojure would concur).

#(let [[p px] %1] (* p px))

I spent a few “mental” cycles thinking about a better way. Here’s what I came up with. There’s no anonymous function definition inside map, and the code follows:

(def multiply (partial apply *))
(reduce + (map multiply distribution))

Do you think it’s really better? Would you think of a better way? Let me know.

For novices, a bit of explanation on what these two lines do. In line 1, partial defines a function that will call (apply *) with the arguments given.

user=> (doc partial)
([f arg1] [f arg1 arg2] [f arg1 arg2 arg3] [f arg1 arg2 arg3 & more])
  Takes a function f and fewer than the normal arguments to f, and
  returns a fn that takes a variable number of additional args. When
  called, the returned function calls f with args + additional args.

user=> (partial apply *)
#<core$partial$fn__4070 clojure.core$partial$fn__4070@3a3c7b60>

The symbol # (hash) says the result of calling partial with arguments is a function.

With def special form the result is in turn assigned to the multiply symbol. From now on, multiply points to the result of the partial‘s call and resolves to a function.

The arguments to multiply (and hence to the new partial function) will come from the distribution map/vector – distribution – when used in map (the 2nd line).

user=> (doc map)
([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
  Returns a lazy sequence consisting of the result of applying f to the
  set of first items of each coll, followed by applying f to the set
  of second items in each coll, until any one of the colls is
  exhausted.  Any remaining items in other colls are ignored. Function
  f should accept number-of-colls arguments.

So, let’s pretend we work with the distribution (I use ratios to denote fractions – a neat feature of Clojure I have not found in the other languages I worked with or learnt):

user=> (def d [[1 1/2] [2 1/3][3 1/4]])

map takes each element off a sequence and applies the function given returning another sequence of results. In our case, the function is (partial apply *) which applies multiplication to every pair.

user=> (map multiply d)
(1/2 2/3 3/4)

Once map finishes, reduce kicks in.

user=> (doc reduce)
([f coll] [f val coll])
  f should be a function of 2 arguments. If val is not supplied,
  returns the result of applying f to the first 2 items in coll, then
  applying f to that result and the 3rd item, etc. If coll contains no
  items, f must accept no arguments as well, and reduce returns the
  result of calling f with no arguments.  If coll has only 1 item, it
  is returned and f is not called.  If val is supplied, returns the
  result of applying f to val and the first item in coll, then
  applying f to that result and the 2nd item, etc. If coll contains no
  items, returns val and f is not called.

It reduces the seq(uence of the multiply results) with + to sum it up and gives the final result.

user=> (reduce + (map multiply d))

Easy, isn’t it?

When d(istribution) is of the Map type, make use of (mapcat seq d).

user=> (def d [{1 1/2} {2 1/3} {3 1/4}])

user=> (reduce + (map multiply (mapcat seq d)))

I don’t however think a map would be the correct type for distribution. Let me know how much off base I am. I’m glad to learn.

Be Sociable, Share!
This entry was posted in Languages.

Leave a Reply

%d bloggers like this: