Threading arrow частные определения в clojure.test
Рассмотрим следующие функции в пространстве имен MVE (минимальный жизнеспособный пример) из свежего lein new app arrow-mve
, Функция extract-one
является публичным, и функция extract-two
это личное. Я включил main-
Функция только для полноты и для удаленной возможности, что это повлекло за собой мою проблему:
(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!"))
В моем параллельном тестовом пространстве имен я могу проверить эти функции следующим образом. Я могу проверить публичную функцию extract-one
либо прямым вызовом, либо с помощью макроса с многопоточностью ->
, Также обратите внимание, что у меня нет проблем со ссылкой на приватную функцию, extract-two
по полной Var
в прямом звонке. Эти тесты проходят:
(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}))))
Но я получаю ошибку компиляции при попытке вызвать приватную функцию extract-two
со стрелкой макрос:
(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)
Все становится более странным, когда я делаю тест немного более сложным.
(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)
Опять же, тест проходит в форме прямого вызова:
(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}))))
Я подозреваю, что проблема заключается в ограничении макро-цепочки через deftest
, is
читатель макроса #'
за Var
, и макрос стрелки, и интересно, было ли это из-за замысла или потенциальной ошибки. Конечно, в моем реальном приложении (не в этом MVE) у меня есть длинные и глубокие цепочки вызовов, которые делают использование макросов со стрелками весьма желательным.
1 ответ
Вот ответ (разные нс):
Основное пространство имен:
(ns clj.core
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(defn extract-one [m]
(-> m :a))
(defn- extract-two [m]
(-> m :a))
Тестирование пространства имен:
(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
Ответ в том, что ссылка на var должна быть в скобках. Все макросы потока имеют тест вида (псевдокод):
(if (not (list? form))
'(form)
form)
Так что форма как
(-> 1
inc)
превращается в
(-> 1
(inc))
до того, как остальная часть потока происходит. if
Тест, похоже, не подходит для вас, так как переменная не является символом. Включение переменной var в список как вызов функции решает проблему.
Я предпочитаю всегда заключать вызовы функций в потоки в круглых скобках и не использовать "голые" функции, даже если это обычно допустимо:
(-> 1
(inc) ; could have typed "inc" w/o parens
(* 2)) ; must use parens since more than 1 arg
;=> 4