Consider the following functions in an MVE (minimal viable example) namespace from a fresh lein new app arrow-mve
. The function extract-one
is public, and the function extract-two
is private. I've included the main-
function just for completeness and for the remote possibility that it's entailed in my problem:
(ns arrow-mve.core
(:gen-class))
(defn extract-one [m]
(-> m :a))
(defn- extract-two [m]
(-> m :a))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
In my parallel test namespace, I can test these functions as follows. I can test the public function extract-one
either by a direct call or using the arrow-threading macro ->
. Also notice that I have no problem referring to the private function, extract-two
, by its full Var
in a direct call. These tests pass:
(ns arrow-mve.core-test
(:require [clojure.test :refer :all]
[arrow-mve.core :refer :all]))
(deftest test-one-a
(is (= 1 (extract-one {:a 1, :b 2}))))
(deftest test-one-b
(is (= 1 (-> {:a 1, :b 2}
extract-one))))
(deftest test-two-a
(is (= 1 (#'arrow-mve.core/extract-two
{:a 1, :b 2}))))
But I get a compile error when I attempt to call the private function extract-two
with the arrow macro:
(deftest test-two-b
(is (= 1 (-> {:a 1, :b 2}
#'arrow-mve.core/extract-two))))
$ lein test
Exception in thread "main" java.lang.RuntimeException: Unable to resolve var: arrow.mve.core/extract-two in this context, compiling: (arrow_mve/core_test.clj:10:12) at clojure.lang.Compiler.analyzeSeq(Compiler.java:6875) at clojure.lang.Compiler.analyze(Compiler.java:6669) at clojure.lang.Compiler.analyze(Compiler.java:6625)
Things get more strange when I make the test a little more complex.
(deftest test-two-b
(is (= {:x 3.14, :y 2.72}
(-> {:a {:x 3.14, :y 2.72}, :b 2}
#'arrow-mve.core/extract-two))))
$ lein test
Exception in thread "main" java.lang.ClassCastException: clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.Symbol, compiling:(arrow_mve/core_test.clj:18:10) at clojure.lang.Compiler.analyzeSeq(Compiler.java:6875) at clojure.lang.Compiler.analyze(Compiler.java:6669) at clojure.lang.Compiler.analyzeSeq(Compiler.java:6856)
Again, the test passes in the direct-call form:
(deftest test-two-b
(is (= {:x 3.14, :y 2.72}
(#'arrow-mve.core/extract-two
{:a {:x 3.14, :y 2.72}, :b 2}))))
I suspect that the problem is a limitation of macro-chaining through deftest
, is
, the reader macro #'
for Var
, and the arrow macro, and wondered if it was by design or a potential bug. Of course, in my real application (not this MVE), I have long and deep call chains that make using the arrow macros highly desirable.
Here is the answer (different ns):
Main namespace:
(ns clj.core
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(defn extract-one [m]
(-> m :a))
(defn- extract-two [m]
(-> m :a))
Testing namespace:
(ns tst.clj.core
(:use clj.core
clojure.test )
(:require [tupelo.core :as t]))
(t/refer-tupelo)
(deftest test-one-a
(is (= 1 (extract-one {:a 1, :b 2}))))
(deftest test-one-b
(is (= 1 (-> {:a 1, :b 2}
extract-one))))
(deftest test-two-a1
(is (= 1 (#'clj.core/extract-two {:a 1, :b 2}))))
;(deftest test-two-b
; (is (= 1 (-> {:a 1, :b 2}
; clj.core/extract-two)))) ; fails: not public
;(deftest test-two-b1
; (is (= 1 (-> {:a 1, :b 2}
; #'clj.core/extract-two))))
; fails: can't cast PersistentArrayMap to Symbol
(deftest test-two-b
(is (= 1 (-> {:a 1, :b 2}
(#'clj.core/extract-two))))) ; works
The answer is that the var reference needs to be inside parentheses. The thread macros all have a test of the form (pseudocode):
(if (not (list? form))
'(form)
form)
So a form like
(-> 1
inc)
is transformed into
(-> 1
(inc))
before the rest of the threading occurs. The if
test seems to be failing for you since the var is not a symbol. Enclosing the var in a list as a function call fixes the problem.
I prefer to always enclose the function calls in threading forms in parentheses, and not use any "naked" functions even though it is normally allowable:
(-> 1
(inc) ; could have typed "inc" w/o parens
(* 2)) ; must use parens since more than 1 arg
;=> 4