Clojure Koans 14 Recursion Notebook

Today's Clojure Koans notebook focuses on "recursion".

As always, be mindful that below I give my answers to the exercises in the Clojure Koans - this means spoilers!

The original Clojure Koans can be found here: https://github.com/functional-koans/clojure-koans/

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        ;; complient is used for autocompletion
        ;; add your libs here (and restart the runtime to pick up changes)
        compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation
{:hello (clojure-version)}
0.1s
Clojure

Initial Thoughts

This section of Clojure Koans initially confused me. Where were the meditations? Where were the lines of text introducing each Koan exercise? After re-reading the entire file I understood that this Clojure Koan section is different than the ones preceding. For this one, the functions at the top of the file are to be completed by the learner, one at a time. The meditations below serve instead as a set of increasingly specific tests, rather than fill-in-the-blank exercises themselves.

(defn is-even? [n]
  (if (= n 0)
    true ;; note: this is the blank to fill in for "Recursion ends with a base case"
    (not (is-even? (dec n))))) ;; note: this is the blank to fill in for "And starts by moving toward that base case"
;; skill 14001: Create a recursive function which does *not* utilize explicit tail calls with recur
Clojure
(defn is-even-bigint? [n]
  (loop [n   n
         acc true]
    (if (= n 0)
      acc ;; question 14001: Is the correct answer here `false` or `acc` ?
      (recur (dec n) (not acc)))))
;; hint1: Having too many stack frames requires explicit tail calls with recur
;; skill 14002: Create a recursive function which *does* utilize explicit tail calls with recur
;; question 14001: What is "tail-call optimization" explained simply?
Clojure
(defn recursive-reverse [coll]
  (loop [n 0 acc '()]
    (if (= (count acc) (count coll))
      acc
      (recur (inc n) (conj acc (nth coll n))))))
;; note: This also passes the single test: `(reduce conj '() coll)`
;; question 14002: What are other correct, well-written answers?
;; exercise 14001: Write a recursive sequence reverse function that utilizes tail-call optimization
Clojure
(defn factorial [n]
  ;; (cond
  ;;   (= n 1) 1 ;; step 1
  ;;   (= n 2) 2 ;; step 2
  ;;   (= n 3) (* 3 (factorial 2)) ;; step 3
  ;;   (> n 3) (* n (factorial (dec n)))) ;; step 4
  (loop [n n acc 1]
    (if (= n 1)
      acc
      (recur (dec n) (* acc n)))))
;; exercise 14002: Write a factorial function that utilizes tail-call optimization
Clojure
;; "Recursion ends with a base case"
(= true (is-even? 0))
;; hint: Read this file from top to bottom, looking for references to `is-even?`
Clojure
;; "And starts by moving toward that base case"
(= false (is-even? 1))
;; question 14003: How can "moving toward that base case" be made more clear & explicit in the "how" it "moves" (recursive call trace stack/tree)?
Clojure
;; "Having too many stack frames requires explicit tail calls with recur"
(= false (is-even-bigint? 100003N))
;; question 14004: Why does this Koan have two accepted? (`false` and `acc`)
Clojure
;; "Reversing directions is easy when you have not gone far"
(= '(1) (recursive-reverse [1]))
;; question 14005: What is the expected take-away (learning) here?
Clojure
;; "Yet it becomes more difficult the more steps you take"
(= '(6 5 4 3 2) (recursive-reverse [2 3 4 5 6]))
;; question 14006: Is `(reduce conj '() coll)` recursive?
Clojure
;; "Simple things may appear simple."
(= 1 (factorial 1))
Clojure
;; "They may require other simple steps."
(= 2 (factorial 2))
Clojure
;; "Sometimes a slightly bigger step is necessary"
(= 6 (factorial 3))
Clojure
;; "And eventually you must think harder"
(= 24 (factorial 4))
Clojure
;; "You can even deal with very large numbers"
(< 1000000000000000000000000N (factorial 1000N))
Clojure
;; "But what happens when the machine limits you?"
(< 1000000000000000000000000N (factorial 100003N))
Clojure

Closing Thoughts

Conceptually speaking, I thought recursion was fairly simple, and the syntax for recursion in other languages (JavaScript, C++, Python, etc.) to be not particularly complex. I struggled a bit more with this Clojure Koan than those prior... I figure it was due to the unfamiliarity to the syntax, especially pertaining to the flow-control structures of `loop` and `recur`. More practice is definitely warranted.

Runtimes (1)