August 16, 2018

Just Juxt #2: Palindrome Detector (4clojure #27)

4clojure 27 - Palindrome Detector


Write a function which returns true if the given sequence is a palindrome.

Hint: "racecar" does not equal '(\r \a \c \e \c \a \r).

Unit tests

We'll start by setting up a placeholder function and our test suite in a KLIPSE snippet. This code is live and editable, so go ahead and fill it in to make the tests pass. Or not... Not trying to tell you what to do or anything...

(ns live.test
  (:require [cljs.test :refer-macros [deftest is testing run-tests]]))

(defn palindrome? [s]

(deftest test-27
  (is (false? (palindrome? '(1 2 3 4 5))))
  (is (true? (palindrome? "racecar")))
  (is (true? (palindrome? [:foo :bar :foo])))
  (is (true? (palindrome? '(1 1 3 3 1 1))))
  (is (false? (palindrome? '(:a :b :c)))))


First off, here's the standard, boring way to do it:

(#(= (seq %) (reverse %)) '(1 2 3 4 5))
(#(= (seq %) (reverse %)) '(1 2 3 2 1))

But friends don't let friends pass up a juxt-ortunity...

Are you ready for this?

Remember, juxt is our universal vector constructor. So, what do we want in our vector?

Let's back up a tiny bit - were it not for that pesky string ("racecar"), we would only have to compare a sequence with its reverse:

(defn palindrome? [s]
  (= s (reverse s)))

You see, the string gets turned into a sequence of characters when reverse is called on it:

(reverse "racecar")

So to perform a proper comparison we need to call seq on the forward version too.

That's why this is a perfect juxt-ortunity!

Any time we need to produce a vector of items each processed by separate functions, we could be thinking juxt. When this use-case is understood, the function will become a potentially formidable item in our utility-belt. It seems evident to us, as juxt-ers, that encouraging its establishment as an idiomatic Clojure design pattern will lead to code that more clearly demonstrates our intentions. That's what language expressivity is all about, and I believe it's one of Clojure's biggest strengths. And indeed, modeling the problem as a vector of items processed by different functions seems entirely fitting in this case:

(seq "racecar")
(reverse "racecar")

Here we go, time to juxt it up:

((juxt seq reverse) "racecar")

Isn't it beautiful?

From here we can easily compare them with (apply =):

(defn palindrome? [s]
  (apply = ((juxt seq reverse) s)))

Now that's what I call love at first juxt. See you all tomorrow for more!

Tags: KLIPSE 4clojure Cryogen juxt