简体   繁体   中英

Threading arrow private defns in clojure.test

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM