Clojure guards

Once I wanted to have something like a pretty “match” operator from Scala (or Rust, or C# 7), but in Clojure. In this blog I explore some solutions for that available in Clojure world.

Use case

Generally, you would use pattern matching as a neat syntax for multiple if..else or switch..case statements. Consider this example of transforming values between two enums:

if (entityType == DBEntityType::ISSUE) {
    return UIEntityType::ISSUE;
} else if (entityType == DBEntityType::SUBTASK) {
    return UIEntityType::SUBTASK;
} else {
    return UIEntityType::UNKNOWN;
}

This could be written neatly with a switch..case statement:

switch (entityType) {
    case DBEntityType::ISSUE:
        return UIEntityType::ISSUE;
    case DBEntityType::SUBTASK:
        return UIEntityType::SUBTASK;
    default:
        return UIEntityType::UNKNOWN;
}

In C# starting with version 7 you could write this using switch expression:

return entityType switch {
    DBEntityType.ISSUE => UIEntityType.ISSUE,
    DBEntityType.SUBTASK => UIEntityType.SUBTASK,
    _ => UIEntityType.UNKNOWN
};

But what if you have multiple conditions to check?

if (entityType == DBEntityType::ISSUE && permissions.canEdit(user, DBEntityType::ISSUE)) {
    return UIEntityType::ISSUE;
} else if (entityType == DBEntityType::SUBTASK && permissions.canEdit(user, DBEntityType::SUBTASK)) {
    return UIEntityType::SUBTASK;
} else {
    return UIEntityType::UNKNOWN;
}

This can not be converted to switch..case nicely. But with switch expressions (or rather, pattern matching, in general) you can do something like this:

return entityType switch {
    DBEntityType::ISSUE when permissions.canEdit(user, DBEntityType::ISSUE) => UIEntityType::ISSUE,
    DBEntityType::SUBTASK when permissions.canEdit(user, DBEntityType::SUBTASK) => UIEntityType::SUBTASK,
    _ => UIEntityType::UNKNOWN,
};

Using cond

;; Note how this method does not check the `n > 3` case
(defn testFn [n]
    (cond
        (= n 1) "You entered 1!"
        (= n 2) "You entered two!"
        (= n 3) "You entered three!"
        :else "You entered a big number!"
    ))

(defn complexFn [entityType user permissions]
    (cond
        (and (= entityType :db-issue) (can-edit? permissions user :db-issue) :ui-issue)
        (and (= entityType :db-subtask) (can-edit? permissions user :db-subtask) :ui-subtask)
        :else :ui-unknown))

Using condp

;; Note how this method does not check the `n > 3` case
(defn testFn [n]
    (condp = n
        1 "You entered 1!"
        2 "You entered two!"
        3 "You entered three!"
        "You entered a big number!"))

(defn complexFn [entityType user permissions]
    (condp = [ entityType true ]
        [ :db-issue (can-edit? permissions user :db-issue) ] :ui-issue
        [ :db-subtask (can-edit? permissions user :db-subtask) ] :ui-subtask
        :ui-unknown))

Using guard macro

(defn generateGuardBody
    "Generates the function body required to support the guard macro"
    [args]
    (let [[thisBranch remainingBranches] (split-at 2 args)
            testCond (first thisBranch)
            testResult (second thisBranch)
            elseFunc (if (empty? remainingBranches)
                false
                (if (=  1 (count remainingBranches))
                    (first remainingBranches)
                    (generateGuardBody remainingBranches)))]
        `(if ~testCond
            ~testResult
            ~elseFunc)))

(defmacro defguardfn
    "Creates a haskell-style guarded function"
    [fnName args & body]
    `(defn ~fnName ~args
        ~(generateGuardBody body)))

(defmacro guard
    "Allows inline guard syntax without surrounding defn"
    [& body]
    `~(generateGuardBody body))


(defguardfn testFn [n]
    (= 1 n) "You entered 1!"
    (= 2 n) "You entered two!"
    (= 3 n) "You entered three!"
    (> n 3) "You entered a big number!")

(println (testFn 5))

(defguardfn fib [n]
    (< n 2) n
    (+ (fib (- n 1)) (fib (- n 2))))


(defn testFn-2 [x]
    (guard
        (= x 1) "One!"
        (= x 2) "Two!"
        true  "Something Else!"))

(println (map fib (range 0 10)))
(println (testFn-2 3)) ; "Something else!"

Using multimethods

;; Implementing factorial using multimethods Note that factorial-like function
;; is best implemented using `recur` which enables tail-call optimization to avoid
;; a stack overflow error. This is a only a demonstration of clojure's multimethod

;; identity form returns the same value passed
(defmulti factorial identity)

(defmethod factorial 0 [_]  1)
(defmethod factorial :default [num]
    (* num (factorial (dec num))))

(factorial 0) ; => 1
(factorial 1) ; => 1
(factorial 3) ; => 6
(factorial 7) ; => 5040

Using core.match

(defn div3? [x] (zero? (rem x 3)))
(let [y [2 3 4 5]]
  (match [y]
    [[_ (a :guard even?) _ _]] :a0
    [[_ (b :guard [odd? div3?]) _ _]] :a1))

(let [x [1 2 3]]
  (match [x]
    [[1 (:or 3 4) 3]] :a0
    [[1 (:or 2 3) 3]] :a1))
;=> :a1

(let [x {:a 3}]
  (match [x]
    [{:a (:or 1 2)}] :a0
    [{:a (:or 3 4)}] :a1))
;=> :a1

(let [v [[1 2]]]
  (match [v]
    [[[3 1]]] :a0
    [[([1 a] :as b)]] [:a1 a b]))
;=> [:a1 2 [1 2]]