UnmodifiableRandomAccessList 和 "contains?" 函数的问题

Issue with UnmodifiableRandomAccessList and "contains?" function

我正在尝试为一个名为 stensil 的开源应用程序做贡献。它是用 Clojure 编写的。

stensil 是一个基于 .docx 的模板引擎。模板引擎声明了一组要在模板中使用的自定义函数。完整的功能列表很短,可以看到 here.

我需要添加另一个自定义函数来检查列表是否包含元素。在 existing examples 之后,我这样做了:

(defmethod call-fn "contains" [_ item items] (contains? items item))

为了对此进行测试,我创建了一个包含以下代码片段的 .docx 文件:

{%= contains(5, data) %}

和一个包含以下内容的 .json 文件:

{
  "data": [9, 4, 1, 6, 3]
}

然后我运行这样的应用程序:

java -jar stencil-core-0.3.8-standalone.jar template.docx sample.json

不幸的是,使用时,我定义的函数不起作用,抛出以下异常:

java.lang.IllegalArgumentException: contains? not supported on type: java.util.Collections$UnmodifiableRandomAccessList

items 似乎是 java.util.Collections$UnmodifiableRandomAccessList 类型。事实上,如果我在函数中添加一个 (println items),我会得到这个:

#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

我尝试通过使用 some function. This answer 以不同的方式进行操作,建议它应该 大多数时间 有效。不幸的是,以下两种变体 return nil:

(defmethod call-fn "contains" [_ item items] (some #{item} items))
(defmethod call-fn "contains" [_ item items] (some #(= item %) items))

我这辈子从来没有写过一行 Clojure,现在有点迷茫。我究竟做错了什么?将列表从 UnmodifiableRandomAccessList 转换为 somecontains? 可以使用的内容是否有意义?如果是,我如何对 items 变量执行此类转换?

更新。按照 cfrick 在答案中的建议尝试使用 .contains,如下所示:

(defmethod call-fn "contains" [_ item items] (.contains items item))

这不会引发 运行 时间错误,但现在输出总是 falseprintln 调试显示:

(defmethod call-fn "contains" [_ item items]
  (println item)
  (println items)
  (.contains items item))
1
#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]

为什么?

contains? 无论如何都不会做你想做的事——它是为了支持查找的东西(例如,你可以检查,如果一个列表有一个 index - 如果不是里面有一个值)。

来自文档:

Returns true if key is present in the given collection, otherwise returns false. Note that for numerically indexed collections like vectors and Java arrays, this tests if the numeric key is within the range of indexes. 'contains?' operates constant or logarithmic time; it will not perform a linear search for a value. See also 'some'.

您可以退回到 Java 此处提供的内容 Collection.contains:

Returns true if this collection contains the specified element. More formally, returns true if and only if this collection contains at least one element e such that (o==null ? e==null : o.equals(e)).

所以你可以这样做:

(.contains items item)

进一步探索:

user=> (.contains (java.util.Collections/unmodifiableList [42]) 42)
true
user=> (.contains (java.util.Collections/unmodifiableList [42]) 64)
false
user=> (.contains [42] 42)
true
user=> (.contains [42] 64)
false

上面的答案都是正确的,因为 contains? 不能用于此目的的列表,而且我们正在尝试比较不同的类型。

这个具体示例中的问题是第一个参数中的数字是 java.lang.Long,而第二个参数中的列表包含 java.math.BigDecimal 个实例。这是因为默认 JSON 解析器模板在独立模式下使用 deserializes numbers as BigDecimal.

一个快速而肮脏的解决方案是在任何地方都使用字符串。

您还可以定义专门用于数字的函数变体:

(defmethod call-fn "ncontains" [_ item items]
  (boolean (some #{(double item)} (map double items))))

或将所有数字强制转换为同一类型:

(defmethod call-fn "contains" [_ item items]
  (letfn [(norm [x] (if (number? x) (double x) x))]
    (boolean (some #{(norm item)} (map norm items)))))