如果任何给定的值不是空集合,如何在Clojure中测试?

时间:2017-04-14 12:55:31

标签: clojure

我需要一个谓词,如果给定的值是非空集合,则逻辑返回true,如果是其他任何内容(数字,字符串等),则逻辑上为false。 更具体地说,如果应用于单个数字或字符串,则谓词不会抛出IllegalArgumentException

我提出了以下功能,但我想知道是否有更多惯用法?

(defn not-empty-coll? [x]
  (and (coll? x) (seq x)))

这将满足以下测试:

(is (not (not-empty-coll? nil)))    ;; -> false
(is (not (not-empty-coll? 1)))      ;; -> false
(is (not (not-empty-coll? "foo")))  ;; -> false
(is (not (not-empty-coll? [])))     ;; -> nil      (false)
(is (not (not-empty-coll? '())))    ;; -> nil      (false)
(is (not (not-empty-coll? {})))     ;; -> nil      (false)
(is (not-empty-coll? [1]))          ;; -> (1)      (true)
(is (not-empty-coll? '(1)))         ;; -> (1)      (true)
(is (not-empty-coll? {:a 1}))       ;; -> ([:a 1]) (true)

编辑:一个潜在的用例:

假设我们需要处理一些尚未受我们控制的原始外部数据。输入可以是例如包含原始值或嵌套集合的集合。其他示例可能是一个包含一些不一致(可能已损坏?)树结构的集合。因此,我们可以将提到的谓词视为第一行数据清理。

否则,我同意最好明确区分和处理收集和非收集数据的评论。

2 个答案:

答案 0 :(得分:0)

正如评论中所建议的那样,我会考虑使用非集合参数调用not-empty?作为无效用法,这应该生成IllegalArgumentException

已有一个功能not-empty?可供使用in the Tupelo library。以下是单元测试:

(deftest t-not-empty
  (is (every?      not-empty? ["one" [1] '(1) {:1 1} #{1}     ] ))
  (is (has-none?   not-empty? [ ""   [ ] '( ) {}     #{ }  nil] ))

  (is= (map not-empty? ["1" [1] '(1) {:1 1} #{1} ] )
         [true true true true true]  )
  (is= (map not-empty? ["" [] '() {} #{} nil] )
         [false false false false false false ] )

  (is= (keep-if not-empty?  ["1" [1] '(1) {:1 1} #{1} ] )
                            ["1" [1] '(1) {:1 1} #{1} ] )
  (is= (drop-if not-empty?  [""  []  '()  {}     #{}  nil] )
                            [""  []  '()  {}     #{}  nil] )

  (throws? IllegalArgumentException (not-empty? 5))
  (throws? IllegalArgumentException (not-empty? 3.14)))

更新

首选方法是使函数只接收给定参数中的集合参数,而不是混合标量和参数。集合参数。然后,只有在知道所讨论的值不是标量的情况下,才需要not-emptyI often use Plumatic Schema强制执行此假设并捕获调用代码中的任何错误:

(ns xyz
  (:require [schema.core :as s] )) ; plumatic schema
(s/defn foo :- [s/Any]
  "Will do bar to the supplied collection"
  [coll :- [s/Any]]
  (if (not-empty coll)
    (mapv bar foo)
    [ :some :default :value ] ))

符号:- [s/Any]的两种用法检查arg&返回值都声明为顺序集合(列表或向量)。每个元素都不受s/Any部分的限制。

如果您出于某种原因无法执行上述策略,我只会修改您的第一种方法,如下所示:

(defn not-empty-coll? [x]
  (and (coll? x) (t/not-empty? x)))

我希望你至少知道一下param x,所以问题就变成了:x是标量还是非空向量。然后你可以这样说:

(defn not-empty-coll? [x]
  (and (sequential? x) (t/not-empty? x)))

答案 1 :(得分:0)

如何使用Clojure协议和类型扩展来解决这个问题?

(defprotocol EmptyCollPred
  (not-empty-coll? [this]))

(extend-protocol EmptyCollPred
  Object
    (not-empty-coll? [this] false)
  nil
    (not-empty-coll? [this] false)
  clojure.lang.Seqable
    (not-empty-coll? [this] (not (empty? (seq this)))))

(is (not (not-empty-coll? nil)))    ;; -> false
(is (not (not-empty-coll? 1)))      ;; -> false
(is (not (not-empty-coll? "foo")))  ;; -> false
(is (not (not-empty-coll? [])))     ;; -> nil      (false)
(is (not (not-empty-coll? '())))    ;; -> nil      (false)
(is (not (not-empty-coll? {})))     ;; -> nil      (false)
(is (not-empty-coll? [1]))          ;; -> (1)      (true)
(is (not-empty-coll? '(1)))         ;; -> (1)      (true)
(is (not-empty-coll? {:a 1}))       ;; -> ([:a 1]) (true)

仅仅扩展StringNumber而不是Object可能会更加清晰 - 取决于您对传入数据的了解。另外,如上所示,最好过滤掉nil s而不是为它创建一个案例。

另一个 - 概念上相似 - 解决方案可以使用多方法。