How do I specify higher order function arguments in Clojure?

Let's say I have a function that takes a function and returns a function that takes whatever arguments it is passed to the passed function and puts the result in a vector (this is an example example, but will hopefully illustrate my point).

(defn box [f]
  (fn [& args]
    [(apply f args)]))

      

I think the spec for the box function looks like this:

(spec/fdef box
  :args (spec/cat :function (spec/fspec :args (spec/* any?)
                                        :ret any?))
  :ret (spec/fspec :args (spec/* any?)
                   :ret (spec/coll-of any? :kind vector? :count 1)))

      

If I then use the box function

(spec-test/instrument)

      

and the call field with clojure.core / + I get an exception

(box +)
ExceptionInfo Call to #'user/box did not conform to spec:
In: [0] val: ([]) fails at: [:args :function] predicate: (apply fn),  Cannot cast clojure.lang.PersistentVector to java.lang.Number
:clojure.spec.alpha/args  (#function[clojure.core/+])
:clojure.spec.alpha/failure  :instrument
:clojure.spec.test.alpha/caller  {:file "form-init4108179545917399145.clj", :line 1, :var-scope user/eval28136}
  clojure.core/ex-info (core.clj:4725)

      

If I understood the error correctly, then he took some? predicate and generate PersistentVector for test that clojure.core / + obviously cannot use. This means that I can make it work by changing the spec of the box argument function to be

(spec/fspec :args (spec/* number?)
            :ret number?)

      

but what if i want to use field for clojure.core / + and clojure.string / lower-case?

NB To get a spec for working in the REPL I need

:dependencies [[org.clojure/clojure "1.9.0-alpha16"]]
:profiles {:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}}
:monkeypatch-clojure-test false

      

in project.clj and following imports

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

      

+3


source to share


2 answers


I don't think you can express this type of function with clojure.spec. You will need a variable type to be able to write something like (here using Haskell type signature)

box :: (a -> b) -> (a -> [b])

      



That is, it is important that you can "grab" the specification of the input function f and include it in your output specification. But there is no such thing in clojure.spec as far as I know. You can also see that clojure.spec list of specs for inline functions does not define a spec for, for example clojure.core/map

, which would have the same problem.

+3


source


As @ amalloy's answer says that the type (spec) of your higher order function return value depends on the argument you gave it. If you provide a function that can work with numbers, then the function returned by the HOF can also work with numbers; if it works on strings, then strings, etc. So you need to somehow inherit / reflect the function of the argument (spec of the) to provide the correct output specification for HOF which I can't think of.

In any case, I would prefer to create separate functions (aliases) for different use cases:

(def any-box box)

(def number-box box)

      

Then you can specify them yourself:



(spec/fdef any-box ;... like your original spec for box

(spec/fdef number-box
  :args (spec/cat :function (spec/fspec :args (spec/* number?)
                                        :ret number?))
  :ret (spec/fspec :args (spec/* number?)
                   :ret (spec/coll-of number? :kind vector? :count 1)))

      

The spectra are working with the instrument as expected:

(spec-test/instrument)

(number-box +)
(any-box list)

      

Of course, writing a BOM for each use case can be quite tricky if you have a lot of them.

0


source







All Articles