Clojure Tic Tac Toe solver
Some Minor Points

(+ x 0) and (+x) can be replaced by just x, in (get-in board[(+ x 0) (+ x 0)])) and (get-in board [(+ x) (- 2 x)])) respectively.

You can omit the nil in else position in if : (if row ... nil) \$\rightarrow\$ (if row ...)

Idiomatic way of checking if something is one of a number of things is using a set of those values as a predicate: (or (= dist [:o]) (= dist [:x])) \$\rightarrow\$ (#{[:o] [:x]} dist)

Rename row to something not misleading. Since you named the collection checks you could name the iteration variable check.

Your Questions


  Could I have created the diagonal vectors in one expression?


Yes. If you factor out the differing portions, namely [x x] and [x (- 2 x)], you get:

(let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
                  (for [x (range 3)]
                    (get-in board (f x))))


Since diagonals is already a sequence, you replace
checks (concat board [vl] [vr] columns)] with checks (concat board diagonals columns)


  Is there a way that I could avoid recursion with this problem?


Recursive processing of collections/sequences can be straightforwardly transformed to for expressions. Your :

(letfn [(check-board [[row &amp; res]]
                (if row
                  (let [dist (distinct row)]
                      (if (or (= dist [:o]) (= dist [:x]))
                        (first dist)
                        (check-board res)))
                  nil))]
        (check-board checks))))


becomes this:

(first 
  (for [row checks 
        :let [dist (distinct row)] 
        :when (or (= dist [:o]) (= dist [:x]))]
    (first dist)))


Note for is lazy, therefore (first (for ... is equivalent to your short-circuit return.

These Changes Applied Altogether

(fn [board]
  (let [diagonals (for [f [#(list % %) #(list % (- 2 %))]]
                    (for [x (range 3)]
                      (get-in board (f x))))
        columns (apply map vector board)
        checks (concat board diagonals columns)]
    (first 
      (for [check checks 
            :let [dist (distinct check)] 
            :when (#{[:o] [:x]} dist)]
        (first dist)))))


Final Point

Also check out some, every?, keep, etc. which process collections with a predicate; and which this exercise probably wants you to learn. For example some is probably what you mean by short-cut evaluation reduce.