Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by

Does it seem useful to add a new kwarg to fspec to allow a function spec to provide one or more constraints on or invariants of the function across distinct invocations in order to provide for constraints such as commutativity. I am working on this, but thought good to check if anyone else thinks is useful.

The new kwarg, possibly :alg given that this allows expression of the algebraic properties of the function, would take a map, with key being a naming keyword and value being a predicate (typically needing a let).

Looking somewhat like this for a few binary operators:

(s/fdef add

:args (s/cat :x int? :y int?)
:ret int?
:alg { :comm

        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)]
               (= ((f x y) (f y x))

        :assoc 
        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)
                z  (-> % args :x)]
                (= (f (f x y) z) (f x (f y z)))

        :ident   
        (let [f (:f %)
                x (-> % args :x)]
               (= (f x 0) x))})

(s/fdef mul
:args (s/cat :x int? :y int?)
:ret int?
:alg { :comm

        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)]
               (= ((f x y) (f y x))

        :assoc 
        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)
                z  (-> % args :x)]
                (= (f (f x y) z) (f x (f y z)))

        :ident   
        (let [f (:f %)
                x (-> % args :x)]
               (= (f x 0) x))

        :super
        (let [f (:f %)
                x (-> % args :x)]
               (= (f x 0) 0))})

(s/fdef max
:args (s/cat :x int? :y int?)
:ret int?
:alg { :comm

        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)]
               (= ((f x y) (f y x))

        :assoc 
        (let [f (:f %)
                x  (-> % args :x)
                y  (-> % args :x)
                z  (-> % args :x)]
                (= (f (f x y) z) (f x (f y z)))

        :idem
        (let [f (:f %)
                x (-> % args :x)]
                (= (f x x) x)})

:ident, :super and :idem above could obvs be done with :fn, but seems handy to put together once one gets :comm and :assoc in place.

This may give rise to question of how to similarly handle constraints on or invariants of sets of related functions, although probably less commonly useful. However that may suggest that actually this would be better done as separate from the fspec in a separate spec - ?

1 Answer

0 votes
by

What problem are you trying to solve?

by
To be honest no real problem to solve - I am an addict of property based / generative testing and so I have these already as tests for the functions in question.

However I like spec and it seems nice to me to maybe be able to have this specification style info available in an fspec, and thus to drive the same testing from there.

I'm somewhat new to using Clojure and trying to push myself to pick some problem up that I can use to learn more deeply, and that will challenge me a bit; so I was mainly just wondering if it seemed useful, or if it was just me.
...