More Capable Functions
Create a multi-arity function:
(defn interest
([balance rate] (* balance (/ rate 100)))
([balance rate days] (* balance (/ rate 100) (/ days 360))))
(interest 17543.55 1.25) ; 219.294375
(interest 17543.55 1.25 180) ; 109.6471875It is common for lower-arity functions to call higher-arity functions by filling in arguments with default values to share an implementation:
(defn interest
([balance rate] (interest balance rate 360))
([balance rate days] (* balance (/ rate 100) (/ days 360))))
(interest 9456.93 0.75) ; 70.926975
(interest 9456.93 0.75 180) ; 35.4634875Write a variadic function by accepting a variable number of arguments:
(defn pizza-price [basic & extras]
(+ basic (count extras)))
(pizza-price 17) ; 17
(pizza-price 17 "Salami") ; 18
(pizza-price 17 "Salami" "Extra Cheese" "Olives") ; 20Write a multimethod that dispatches the function call to its proper implementation depending on the arguments provided:
(defn dispatch [shape]
(cond
(and (contains? shape :height) (contains? shape :width)) :rectangle
(contains? shape :side) :square
(contains? shape :radius) :circle))
(defmulti area dispatch)
(defmethod area :rectangle [r]
(* (:height r) (:width r)))
(defmethod area :square [s]
(* (:side s) (:side s)))
(defmethod area :circle [c]
(* (Math/pow (:radius c) 2) Math/PI))
(defmethod area :default [x]
(ex-info "unknown shape" {:shape x}))
(area {:width 3 :height 4}) ; 12
(area {:side 3}) ; 9
(area {:radius 5}) ; 78.53981633974483
(area {:h 7 :a 10 :c 4}) ; ExceptionAn argument not accounted for in the dispatch function is resolved to
:default.
Write a recursive function:
(defn sum [xs]
(if (empty? xs)
0
(+ (first xs) (sum (rest xs)))))
(sum []) ; 0
(sum [1 2 3]) ; 6
(sum [1 2 3 4 5]) ; 15Write a tail-recursive function:
(defn sum
([xs] (sum xs 0))
([xs acc]
(if (empty? xs)
acc
(sum (rest xs) (+ (first xs) acc)))))Enable tail-call optimization by replacing the recursive function call with
recur:
(defn sum
([xs] (sum xs 0))
([xs acc]
(if (empty? xs)
acc
(recur (rest xs) (+ (first xs) acc))))) ; NOTE: recur instead of sumRefactor tail recursion as a loop:
(defn sum [xs]
(loop [xs xs
acc 0]
(if (empty? xs)
acc
(recur (rest xs) (+ (first xs) acc)))))Document values and function using a docstring:
(def VAT "value added tax rate in percent" 8.1)
(defn hypot
"Calculates the hypothenuse given the rectangular triangle's legs a and b."
[a b]
(Math/sqrt (+ (Math/pow a 2) (Math/pow b 2))))
(hypot 3 4) ; 5.0Retrieve a docstring programmatically:
(doc VAT) ; value added tax rate in percent
(doc hypot)
;; Calculates the hypothenuse given the rectangular triangle's legs a and b.Provide validations of arguments and the return value using pre and post conditions, respectively:
(defn hypot [a b]
{:pre [(> a 0) (> b 0)]
:post [(> % a) (> % b)]}
(Math/sqrt (+ (Math/pow a 2) (Math/pow b 2))))
(hypot 3 4) ; 5.0
(hypot 3 0) ; Assert failed: (> b 0)The return value is available as % in the post condition.
Exercises
Acceleration and Speed
Write a multi-arity function end-speed to compute the speed given an
acceleration a in m/s², a time indication t in s, and—optionally—a
starting speed v0 in m/s. Document the function with the formula in a
docstring.
Hint: Use the formula v=v0+at. The lower-arity function shall call the
higher-arity function.
Test: (end-speed 2 10) shall return 20, and (end-speed 2 10 7.5) shall
return 27.5.
Property Summation
Given the following maps of personal financial data:
(def jack {:income 4500 :balance 12942 :debt 120000})
(def jill {:income 3200 :balance 19172 :debt 250000})
(def jane {:income 4900 :balance 10342 :debt 100000})Write a recursive function (sum-by [prop & args]) that sums up the given
property prop in the provided data. Document the function using a docstring.
Hint: Return 0 if the property isn’t found in the data, or of no arguments are
provided. Use apply for the recursive call.
Test: (sum-by :income jack jill) shall return 7700, and (sum-by :debt jack jill jane) shall return 470000.
Bonus Calculation
Given the following payroll data:
(def dilbert {:salary 120000 :position "Engineer"})
(def wally {:salary 90000 :position "Engineer"})
(def topper {:salary 150000 :position "Sales" :revenue 2750000})
(def ashok {:salary 36000 :position "Intern"})
(def boss {:salary 500000 :position "Manager"})Write a multimethod bonus that calculates each employee’s bonus based on the
following rules:
- Engineers earning more than
100000get a bonus of 10% of their salary. Engineers earning up to100000get a bonus of 15% of their salary. - Sales people get a bonus of 10% of their salary and 1% of their revenue.
- Interns get a fixed bonus of
2000. - All other employees get a bonus of
0.
Hint: The dispatch function shall differentiate between low- and high-earning
engineers. Do not explicitly return :default from the dispatch method for all
other employees.
Test: (bonus dilbert) shall return 12000.0, (bonus wally) shall return
13500.0, (bonus topper) shall return 42500.0, and (bonus ashok) shall
return 2000.0
Fibonacci Numbers
Write a tail-recursive function (fib [n]) that computes the nth Fibonacci
number.
Hint: The nth Fibonacci number is defined as the sum of its two predecessors.
The first two Fibonacci numbers are 1 and 1. Use loop and recur to
implement the function.
Test: (fib 0) and (fib 1) shall return 1, (fib 10) shall return 89,
and (fib 45) shall return 1836311903—and finish within within milliseconds
(use (time (fib 45)) to check).
Blob Eats Blob
Given the following data:
(def red {:weight 80 :strength 50})
(def blue {:weight 90 :strength 40})
(def green {:weight 70 :strength 35})
(def black {:weight 0 :strength 0})Write a function (merge-blobs [a b]) which simulates a fight between the two
blobs a and b. The blob with the higher strength eats up the blog with the
lower strength, thereby gaining the defeated blob’s weight and 10% of its
strength. Test for the following conditions:
- Preconditions: Both blobs have a positive weight and strength.
- Postcondition: The returned weight is higher than the weight of any given blob.
Hint: Use :pre and :post to enforce the conditions.
Test: (merge-blobs red blue) shall return {:weight 170 :strength 54}, and
(merge-blobs green black) shall throw an assertion error.