Making decisions and repeating actions. Phel gives you several tools for control flow — each suited for different situations.
Exercise 1: Define a function absolute that returns the absolute value of a number using if.
(absolute -5) # => 5
(absolute 3) # => 3
(defn absolute [n]
(if (< n 0)
(- n)
n))
if takes a condition, a "then" branch, and an "else" branch. It always returns a value.
Learn more: Control Flow
Exercise 2: Define a function small? that returns true for numbers under 100.
(small? 99) # => true
(small? 100) # => false
(defn small? [n] (< n 100))
By convention, predicate functions (that return true/false) end with ?.
Learn more: Control Flow
Exercise 3: Use when to print a warning only if a number is negative. What does when return when the condition is false?
(defn warn-if-negative [n] ...)
(warn-if-negative -5) # prints "Warning: negative number!"
(warn-if-negative 5) # => nil
(defn warn-if-negative [n]
(when (< n 0)
(println "Warning: negative number!")))
(warn-if-negative -5) # prints "Warning: negative number!"
(warn-if-negative 5) # => nil
when is like if without an else branch. It returns nil when the condition is false. Use it when you only care about one case.
Learn more: Control Flow
Exercise 4: Define a function grade that converts a score to a letter grade:
(grade 95) # => "A"
(grade 82) # => "B"
(grade 71) # => "C"
(grade 55) # => "F"
Use the cond structure (scores: A >= 90, B >= 80, C >= 70, F otherwise).
(defn grade [score]
(cond
(>= score 90) "A"
(>= score 80) "B"
(>= score 70) "C"
"F"))
cond tests conditions in order and returns the value for the first truthy one. The final standalone value acts as a default (like "else").
Learn more: Control Flow
Exercise 5: Define a function day-type that classifies days of the week:
(day-type :monday) # => "weekday"
(day-type :saturday) # => "weekend"
(day-type :friday) # => "almost there!"
Use the case structure.
(defn day-type [day]
(case day
:saturday "weekend"
:sunday "weekend"
:friday "almost there!"
"weekday"))
case matches a value against exact cases. The last value without a match acts as the default. It's cleaner than cond when you're comparing against specific values.
Learn more: Control Flow
Exercise 6: The exercises in the Conditionals & Structures section of the old practice used a message function with if, cond, and case. Now that you know all three — when would you choose each one? Write a describe-temp function that uses the best fit:
(describe-temp 35) # => "hot"
(describe-temp 20) # => "nice"
(describe-temp 5) # => "cold"
(describe-temp -10) # => "freezing"
(defn describe-temp [degrees]
(cond
(>= degrees 30) "hot"
(>= degrees 15) "nice"
(>= degrees 0) "cold"
"freezing"))
cond is the right choice here because we're testing ranges, not exact values. Use case for exact matches, if for simple true/false, and cond for multiple conditions.
Learn more: Control Flow
Exercise 7: Use loop and recur to build a vector of numbers from 1 to 10.
(loop [v [] i 1]
(if (> i 10)
v
(recur (push v i) (inc i))))
# => [1 2 3 4 5 6 7 8 9 10]
loop defines initial bindings and recur jumps back to the loop with new values. This is Phel's way of doing iteration without mutable variables.
Learn more: Control Flow
Exercise 8: Use loop and recur to compute the sum of numbers from 1 to 100.
(loop [i 1 total 0]
(if (> i 100)
total
(recur (inc i) (+ total i))))
# => 5050
The accumulator pattern: carry your result in a loop variable and return it when done. (Fun fact: the answer is the same one young Gauss famously computed in seconds!)
Learn more: Control Flow