(chapter "Custom Functions")

(section "Simple Functions")
(example "Define function"
         (defn square [x] (* x x)))(example "Use function"
         (square 8))
(example "Multi-arg functions"
         (defn mul-add [a b c] (+ (* a b) c))
         (mul-add 3 10 5))
(example "Nullary function"
         (defn nullary [] 10)
         (nullary))
(example "Anonymous functions"
         ((fn [x] (+ x x)) 10)
         (#(+ % %) 10)
         (#(+ %1 %2) 10 20))
(example "Functions as values"
         (defn twice [f] (fn [a] (f (f a))))
         ((twice square) 3))

(section "Recursive Functions")
(example "Recursive Fibonacci"
         (defn rec-fib [n]
           (cond
             (== 0 n) 1
             (== 1 n) 1
             :else (+ (rec-fib (- n 1))
                      (rec-fib (- n 2)))))
         (rec-fib 40))
(example "Memoized Fibonacci"
         (def mem-fib
           (memoize
             (fn [n]
               (cond
                 (== 0 n) 1
                 (== 1 n) 1
                 :else (+ (mem-fib (- n 1)) (mem-fib (- n 2)))))))
         (mem-fib 90))
(example "Tail-recursive Fibonacci"
         (defn iter-fib [n]
           (letfn [(iter-fib' [n a b]
                     (if (== 0 n)
                       a
                       (iter-fib' (- n 1) b (+' a b))))]
             (iter-fib' n 1 1)))
         (iter-fib 90)
         (iter-fib 10000)
         (defn iter-fib-recur [n]
           (letfn [(iter-fib' [n a b]
                     (if (== 0 n)
                       a
                       (recur (- n 1) b (+' a b))))]
             (iter-fib' n 1 1)))
         (iter-fib-recur 10000))
(example "Explicit loop Fibonacci"
         (defn loop-fib [n]
           (loop [n n a 1 b 1]
             (if (== 0 n)
               a
               (recur (- n 1) b (+ a b)))))
         (loop-fib 90))

(section "Pre and Post conditions")
(example "Fast power"
         (defn power
           "Raises a to the b-th power"
           [a b]
           {:pre [(<= 0 b)]
            :post [(or (zero? b) (zero? a) (zero? (rem % a)))]}
           (cond
             (zero? b) 1
             (even? b) (power (* a a) (quot b 2))
             (odd? b) (* a (power a (dec b))))))
(example "Pre and postconditions ok"
         (power 2 5)
         (power 2 0)
         (power 0 2))
(example "Precondition violated"
         (power 2 -5))
(example "Invalid postcondition"
         (defn ipower
           [a b]
           {:pre [(<= 0 b)]
            :post [(zero? (rem % a)) (<= 0 %)]}
           (power a b)))
(example "First part of invalid postcondition violated"
         (ipower 2 0)
         (power 2 0))
(example "Second part of invalid postcondition violated"
         (ipower -2 3)
         (power -2 3))
