August 15, 2018

Just Juxt #1: Fibonacci Sequence (4clojure #26)

Welcome to just juxt! Where it's all juxt, all the time!

We might as well introduce the juxt function with its own source:

Clojure 1.9.0
user=> (source juxt)
(defn juxt 
  "Takes a set of functions and returns a fn that is the juxtaposition
  of those fns.  The returned fn takes a variable number of args, and
  returns a vector containing the result of applying each fn to the
  args (left-to-right).
  ((juxt a b c) x) => [(a x) (b x) (c x)]"
  {:added "1.1"
   :static true}
  ([f] 
     (fn
       ([] [(f)])
       ([x] [(f x)])
       ([x y] [(f x y)])
       ([x y z] [(f x y z)])
       ([x y z & args] [(apply f x y z args)])))
  ([f g] 
     (fn
       ([] [(f) (g)])
       ([x] [(f x) (g x)])
       ([x y] [(f x y) (g x y)])
       ([x y z] [(f x y z) (g x y z)])
       ([x y z & args] [(apply f x y z args) (apply g x y z args)])))
  ([f g h] 
     (fn
       ([] [(f) (g) (h)])
       ([x] [(f x) (g x) (h x)])
       ([x y] [(f x y) (g x y) (h x y)])
       ([x y z] [(f x y z) (g x y z) (h x y z)])
       ([x y z & args] [(apply f x y z args) (apply g x y z args) (apply h x y z args)])))
  ([f g h & fs]
     (let [fs (list* f g h fs)]
       (fn
         ([] (reduce1 #(conj %1 (%2)) [] fs))
         ([x] (reduce1 #(conj %1 (%2 x)) [] fs))
         ([x y] (reduce1 #(conj %1 (%2 x y)) [] fs))
         ([x y z] (reduce1 #(conj %1 (%2 x y z)) [] fs))
         ([x y z & args] (reduce1 #(conj %1 (apply %2 x y z args)) [] fs))))))
nil
user=> 

To build our dataset we will use Mike Fikes' coal-mine, a collection of submissions from the top 1000 users on 4clojure. A grep yielded over 300 occurrences of juxt. The very first of which is found in a solution to problem #26:

The good ol' Fibonacci Sequence!

Write a function which returns the first x fibonacci numbers.

(ns live.test
  (:require [cljs.test :refer-macros [deftest is testing run-tests]]))
  
(defn fib [x]
  (->> [1 1]
       (iterate
         (juxt last
               (partial apply +)))
       (take x)
       (map first)))
       
(deftest test-26
  (is (= (fib 3) '(1 1 2)))
  (is (= (fib 6) '(1 1 2 3 5 8)))
  (is (= (fib 8) '(1 1 2 3 5 8 13 21))))

(run-tests)

And there we have it, our first canonical use of juxt!

Now that we see it passes the tests, let's get our hands dirty and get a better feel for what juxt is doing. We'll simply hack our function into pieces to get a closer look. (BTW the code on this page is interactive so go ahead and smack it around ;)

(->> [1 1]
       (iterate
         (juxt last
               (partial apply +)))
       (take 12)
       (map first))

Here it is macroexpanded:

(map first (take 12 (iterate (juxt last (partial apply +)) [1 1])))

Remove (map first) to see what it does:

(take 12 (iterate (juxt last (partial apply +)) [1 1]))

We could also write it using an anonymous function instead of using partial (yay even moar shorter!):

(take 12 (iterate (juxt last #(apply + %)) [1 1]))

As we can see, juxt is acting as our trusty vector-pair generator. The two functions it takes, last and #(apply + %) are used to populate them. Each iteration produces:

  1. The last (second) digit; and
  2. The sum of the 2 digits.

That's a fib.

But that ain't no fib, folks. See y'all tomorrow for another issue of (just juxt!)!

Tags: Cryogen