Почему мой or-spec действителен только для одной из заданных спецификаций?

Рассмотрим следующую спецификацию для текста или номера порта канального уровня:

(require '[clojure.spec.alpha :as spec])

(spec/def ::text (spec/and string? not-empty))
(spec/valid? ::text "a")                ; => true
(spec/valid? ::text "")                 ; => false
(spec/def ::port (spec/and pos-int? (partial > 65535)))
(spec/valid? ::port 4)                  ; => true
(spec/valid? ::port 0)                  ; => false
(spec/def ::text-or-port (spec/or ::text ::port))
(spec/valid? ::text-or-port 5)          ; => true
(spec/valid? ::text-or-port "hi")       ; => false

По какой-то причине он принимает только номера портов, а не текст, с чего бы это?


person Rovanion    schedule 23.05.2017    source источник


Ответы (1)


Ключ к пониманию этой проблемы можно найти в документацию и используя spec/conform.

(spec/conform ::text-or-port 5)
; => [:user/text 5]

Проблема в том, что clojure.spec.alpha/or имеет API, который отличается от clojure.core/or, который с учетом двух аргументов возвращает первый правдивый:

(#(or (string? %) (integer? %)) 5)      ; => true
(#(or (string? %) (integer? %)) "")     ; => true
(#(or (string? %) (integer? %)) :a)     ; => false

Скорее, он принимает пары меток и спецификаций/предикатов. И поскольку даже ключевые слова с именами принимаются в качестве меток, спецификация ::text-or-port, указанная в OP, соответствовала только той, которая соответствовала требованиям для ::port и давала ей метку ::text. Ниже приведена правильная спецификация для того, что мы хотим сопоставить:

(spec/def ::text-or-port (spec/or :text ::text
                                  :port ::port))
(spec/valid? ::text-or-port "hi")       ; => true
(spec/valid? ::text-or-port 10)         ; => true
person Rovanion    schedule 23.05.2017