holtzermann17 / FloWrTester

Test ProcessNode combinations using the FloWr web API
Creative Commons Zero v1.0 Universal
1 stars 1 forks source link

Getting function from spec #1

Open holtzermann17 opened 7 years ago

holtzermann17 commented 7 years ago

Note, the tip described here shows how to use Github issues to render code nicely for pasting into a Google Group discussion. So I'm re-doing my initial about this here, and will maintain future posts as issues.


As a running example related to the ideas in http://clojure.org/guides/spec#_spec_ing_functions, please consider the following function:

(defn mapper [x y]
  {:tacos (vec (range x))
   :burritos (vec (range y))})

Example input and output:

(mapper 2 4) ;=> {:tacos [0 1], :burritos [0 1 2 3]}

OK, I'll go ahead and write a sensible-seeming spec for this function now, as follows:

(s/fdef mapper
        :args (s/cat :t (s/and integer? #(> % 0))
                     :b (s/and integer? #(> % 0)))
        :ret map?
        :fn (s/and #(= (-> % :ret :tacos)
                       (vec (range (-> % :args :t))))
                   #(= (-> % :ret :burritos)
                       (vec (range (-> % :args :b))))))

My first question: Is there a way to only write the spec, and have the function generated automatically? The behavior I have in mind is (after all) specified in the :fn part of the fdef.

And, another somewhat related question: if I redefine the function to do nothing, and then exercise it, I get output that doesn't seem to conform to the :fn condition given above, but no error -- why is that?

(defn mapper [x y])
(s/exercise-fn `mapper)
;=> ([(1 3) nil] [(1 1) nil] [(4 2) nil] [(2 3) nil] [(6 3) nil] 
       [(15 2633) nil] [(3 1) nil] [(3 4) nil] [(1 1) nil] [(28 4) nil])

Thanks for any help!

holtzermann17 commented 7 years ago

Hm, the same thread points out that you can use Markdown Here to write Github-style Markdown in any form, so storing messages as issues just to get markup isn't needed -- but it's probably useful to keep track of the issues in one centralised place anyway.


This is a late reply to my own question but maybe it will be helpful to a future searcher.

http://blog.cognitect.com/blog/2016/10/5/interactive-development-with-clojurespec has an example that showed me more clearly how to use :fn.

Specifically here's how I rewrote the spec:

(s/fdef mapper
        :args (s/cat :t :fake.flowrs-tests/positive-integer?
                     :b :fake.flowrs-tests/positive-integer?)
        :ret map?
        :fn (fn [{args :args ret :ret}]
              (= (:tacos ret)
                 (vec (range (:t args))))
              (= (:burritos ret)
                 (vec (range (:b args))))))

(s/exercise-fnmapper)has the same behavior as before -- it exercises the _implementation_ of the function. But(stest/check mapper) will return "Specification-based check failed" etc. when the implementation of mapper doesn't match.

As for the other part of the question: turning the logical test of :fn into a function that can be run to generate the result, that would require quite a bit of code rewriting. A partial step is just to extract the :fn part of the code, and that can be done as follows:

(:fn (apply hash-map (rest (s/form (get (s/registry) 'fake.flowrs/mapper)))))
;=>
(clojure.core/fn
 [{args :args, ret :ret}]
 (clojure.core/=
  (:tacos ret)
  (clojure.core/vec (clojure.core/range (:t args))))
 (clojure.core/=
  (:burritos ret)
  (clojure.core/vec (clojure.core/range (:b args)))))

The function so-obtained can be evaluated, but it won't "do" the job of the mapper function without further code transformation. I'll leave it at that for now.