[英]How to test in Clojure if any given value is not-empty collection?
I need a predicate which returns logically true if the given value is a not-empty collection and logically false if it's anything else (number, string etc.). 我需要一个谓词,如果给定的值是非空集合,则逻辑返回true,如果是其他任何内容(数字,字符串等),则逻辑上为false。 And more specifically, that the predicate won't throw the IllegalArgumentException
if applied to single number, or string. 更具体地说,如果应用于单个数字或字符串,则谓词不会抛出IllegalArgumentException
。
I came up with the following function, but I'm wondering if there is some more idiomatic approach? 我提出了以下功能,但我想知道是否有一些更惯用的方法?
(defn not-empty-coll? [x]
(and (coll? x) (seq x)))
This will satisfy following tests: 这将满足以下测试:
(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)
EDIT: A potential use case: 编辑:一个潜在的用例:
Let's say we need to process some raw external data which are not (yet) under our control. 假设我们需要处理一些尚未受我们控制的原始外部数据。 Input could be for example a collection which contains either primitive values, or nested collections. 输入可以是例如包含原始值或嵌套集合的集合。 Other example could be a collection holding some inconsistent (maybe broken?) tree structure. 其他示例可能是一个包含一些不一致(可能已损坏?)树结构的集合。 So, we can consider mentioned predicate as first line data cleaning. 因此,我们可以将提到的谓词视为第一行数据清理。
Otherwise, I agree with comments that is better to explicitly separate and process collection and non-collection data. 否则,我同意更好地明确分离和处理收集和非收集数据的注释。
As suggested in the comments, I would consider calling not-empty?
正如评论中所建议的那样,我会考虑打电话给not-empty?
with a non-collection argument to be an invalid usage, which should generate an IllegalArgumentException
. 使用非集合参数作为无效用法,应该生成IllegalArgumentException
。
There is already a function not-empty?
已有功能not-empty?
available for use in the Tupelo library . 可在Tupelo图书馆使用 。 Here are the unit tests: 以下是单元测试:
(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)))
The preferred approach would be for a function to only receive collection parameters in a given argument, not a mixture scalar & collection arguments. 首选方法是函数只接收给定参数中的集合参数,而不是混合标量和集合参数。 Then, one only needs not-empty
given the pre-knowledge that the value in question is not a scalar. 然后,只有在知道所讨论的值不是标量的情况下,才需要not-empty
。 I often use Plumatic Schema to enforce this assumption and catch any errors in the calling code: 我经常使用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 ] ))
The 2 uses of notation :- [s/Any]
checks that the arg & return value are both declared to be a sequential collection (list or vector). 表示法的两种用法:- [s/Any]
检查arg&return值是否都被声明为顺序集合(列表或向量)。 Each element is unrestricted by the s/Any
part. 每个元素不受s/Any
部分的限制。
If you can't enforce the above strategy for some reason, I would just modify your first approach as follows: 如果由于某种原因无法执行上述策略,我只需修改您的第一种方法,如下所示:
(defn not-empty-coll? [x]
(and (coll? x) (t/not-empty? x)))
I'm hoping you know at least a little about the param x
so the question becomes: Is xa scalar or a non-empty vector. 我希望你至少知道关于param x
的一点,所以问题就变成了:是xa标量还是非空向量。 Then you could say something like: 然后你可以这样说:
(defn not-empty-coll? [x]
(and (sequential? x) (t/not-empty? x)))
How about using Clojure protocols and type extensions to solve this? 如何使用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)
Maybe it would be cleaner to extend just String
and Number
instead of Object
- depends on what do you know about the incoming data. 可能更简洁的是扩展String
和Number
而不是Object
- 取决于你对输入数据的了解。 Also, it would be probably better to filter out nil
s beforehand instead of creating a case for it as you see above. 此外,如上所述,最好过滤掉nil
而不是为它创建一个案例。
Another - conceptually similar - solution could use multimethods. 另一个 - 概念上类似 - 解决方案可以使用多方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.