I'm trying to contribute to an open-source application called stensil. It is written in Clojure.
stensil
is a templating engine that works on top of .docx. The templating engine declares a bunch of custom functions to be used in a template. A full list of functions is quite short and can be seen here.
What I need is to add another custom function that checks if a list contains an element. Following existing examples, I did this:
(defmethod call-fn "contains" [_ item items] (contains? items item))
To test this, I created a .docx file that contains the following snippet:
{%= contains(5, data) %}
and a .json file that contains this:
{
"data": [9, 4, 1, 6, 3]
}
Then I run the application like this:
java -jar stencil-core-0.3.8-standalone.jar template.docx sample.json
Unfortunately, when used, the function I defined doesn't work, throwing the following exception:
java.lang.IllegalArgumentException: contains? not supported on type: java.util.Collections$UnmodifiableRandomAccessList
It appears that items
is of type java.util.Collections$UnmodifiableRandomAccessList
. Indeed, if I add a (println items)
to the function I get this:
#object[java.util.Collections$UnmodifiableRandomAccessList 0x778ca8ef [9, 4, 1, 6, 3]]
I tried doing it differently by using some
function. This answer suggests it should work most of the time. Unfortunately both variants below return nil
:
(defmethod call-fn "contains" [_ item items] (some #{item} items))
(defmethod call-fn "contains" [_ item items] (some #(= item %) items))
I've never written a line of Clojure in my life before, and a bit lost now. What am I doing wrong? Would it make sense to convert the list from UnmodifiableRandomAccessList to something that some
or contains?
can work with? If yes, how do I perform such conversion on items
variable?
Upd. Tried using .contains
as suggested by cfrick in an answer, like this:
(defmethod call-fn "contains" [_ item items] (.contains items item))
This doesn't raise a runtime error, yet the output is always false
now. println
debugging reveals this:
(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]]
Why?
The answers above are all correct, because contains?
can not be used on lists for this purpose and also we are trying to compare different types.
The problem in this specific example is that the number in the first argument is a java.lang.Long
, whereas the list in the second argument contains java.math.BigDecimal
instances. This is because the default JSON parser stencil uses in standalone mode deserializes numbers as BigDecimal.
A quick and dirty solution is to use strings everywhere.
You can also define a variation of the function that works specifically with numbers:
(defmethod call-fn "ncontains" [_ item items]
(boolean (some #{(double item)} (map double items))))
Or coerce all numbers to the same type:
(defmethod call-fn "contains" [_ item items]
(letfn [(norm [x] (if (number? x) (double x) x))]
(boolean (some #{(norm item)} (map norm items)))))