This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-09-19
Channels
- # announcements (1)
- # beginners (115)
- # calva (7)
- # cider (8)
- # clj-kondo (3)
- # cljdoc (12)
- # clojure (50)
- # clojure-europe (4)
- # clojure-italy (5)
- # clojure-nl (6)
- # clojure-spec (70)
- # clojure-uk (88)
- # clojurescript (54)
- # core-async (16)
- # cursive (5)
- # datomic (31)
- # editors (4)
- # emacs (4)
- # fulcro (29)
- # graphql (17)
- # luminus (1)
- # lumo (2)
- # off-topic (37)
- # pathom (16)
- # random (2)
- # re-frame (5)
- # reitit (3)
- # rum (2)
- # shadow-cljs (192)
- # sql (11)
Has anyone experimented with making specs for stateful things, with the intention of using mocks for those values?
@dominicm Do you mean something like
(stest/instrument `invoke-service {:stub #{`invoke-service}})
found at https://clojure.org/guides/spec#_combining_check_and_instrumentoh, wait. That's not quite what I was looking for. But still very interesting in what I'm thinking of :thinking_face:
that's really neat. I didn't know you could do that. That's actually really handy for what I'm working on.
I'm actually thinking of the case where you have a function like invoke-service
, and you want a dumb wrapper which can figure out what to call it with (from it's registry of "stuff") based on the spec.
I'm a bit frustrated with the pattern of passing around a grab-bag of state which inevitably grows into an unrepl-able mess.
ah - sorry, I am still catching up with the discussion though have found the intercepting properties useful at test-time
I'm a little unsure on that. Parallelizing tests is valuable for speed (until Rich invents his tool for running only the required subset of the tests). I don't know how much I love that idea really.
mm yeah fair enough.
So is your use-case this:
use fdef
to document what deps your function needs
have some kind of wrapper that then knows how to extract and inject only those deps into a calling function?
Yeah. I'm coming round to the idea that I might be barking up the wrong tree though 🙂 I'm thinking a little more time in the hammock should help me puzzle out the use cases.
I'm trying to exploit maximum leverage without creating unnecessary verbosity. It's a hard balance 🙂
I guess I can in theory see how you could fetch the fspec then use a generator, to satisfy the s/keys :req
portion but feels brittle, if only as I'm not convinced it's an appropriate usage of the tools
I'm hoping to publish my results somewhere, I think I'm onto something, I've reduced the initial problem space into something more palatable. But I still have to handle the stateful dependencies part.
I'm not sure I understand the usecase for the dumb wrapper
. What is registry of "stuff"
?
I suppose something to the effect of, I have a "system" like: {:db db-conn :some-stateful-service statey}
and I want to direct things directly to a function defined like:
(defn add-user
[db add-user-data] …)
So the "registry" is that first map.Yeah. Exactly. But passing around systems is an anti-pattern. And I also can't figure out what's in my pedestal context map half the time, because there's lots of interceptors messing with it, and it's unclear why routeA gets :mongodb and routeB doesn't. (So I'm trying to come at this with a new angle of positional parameters)
So, is the goal here to assert a property about the dumb wrapper
but you're trying to determine the best way to inject actually stateful mocks?
I guess I could write a spec parser for s/cat? And that would let me figure out the arguments in order.
Well, actually I was thinking that it would be handy to just start by being able to call add-user
by using it's fdef to figure out that it needs a database, and getting one from the system. (and somehow connecting ::db/db
as a spec to that)
I might be wrong, but it sounds like you want an integration between your DI tool (are you using component?) and the s/select
facilities in spec2. If you're using component though, shouldn't that fdef to figure out....
step be happening at system/lifecycle start time?
But I'd be using this in production, not just for testing. So I would know that this function only takes 1 non-stateful argument (the "event", or maybe "req" for http). So I just need to get the rest of the arguments.
I'm not using component. The reason being that a system probably ends up with ~50 or so handlers, and the boilerplate involved is quickly tedious. Although maybe I should just use a macro for that 😛
The way I see it, I have actual stateful things (e.g. db conn) and things that want to use those things.
This is starting to feel more and more like spring style annotation DI vs data oriented component DI. And further away from a problem related to spec.
A little, yeah. And that's somewhat intentional. I see one of two patterns in this space: 1. create a map with everything in, call it "deps" and hope the function you're passing it to understands it (and there's a bunch of bad patterns which fall out of this). 2. roll entire namespaces into being partial
applied with their state available, and passed in as arguments.
Tbh, I expect there's not a good spec function for figuring this out 🙂 As I have a lot of contextual awareness that spec doesn't have. (e.g. I know it's always a list, and I will always know everything except 1 argument).
I think the 3rd pattern is component/system/mount oriented, where functions are passed positional arguments (not a deps/req/context map) which were determined by the DI tool. I don't think you can reduce that kind of complexity, only choose where to solve it, 1 large place (component) or in every dumb-wrapper
by declaring the deps at the callsite. I definitely don't think spec is going to be helpful here.
I just started solving some basic programming puzzles with Clojure, with the constraint that I want to spec the solutions. I hit a snag right on the first one. Here's the fn I want to spec:
(defn two-sum
"Returns indices of two elems in ints that add up to sum"
{::url " "}
[ints sum]
(loop [l 0
r (dec (count ints))]
(let [s (+ (nth ints l) (nth ints r))]
(cond (= l r) nil
(= s sum) [l r]
(< s sum) (recur (inc l) r)
:else (recur l (dec r))))))
And here's what I came up with:
(s/fdef two-sum
:args (s/cat :ints (s/coll-of int? :min-count 2 :kind vector?) :sum int?)
:ret (s/and (s/tuple (s/and int? #(>= % 0)) pos-int?) (fn [[l r]] (< l r)))
:fn (s/and
#(< (-> % :ret second) (-> % :args :ints count))
#(= (-> % :args :sum)
(+ (nth (-> % :args :ints) (-> % :ret first))
(nth (-> % :args :ints) (-> % :ret second))))))
The puzzle is to find two (different) indices s.t. the elements in ints
in those indices add up to sum
.
The catch is: there is always exactly one solution. How do I spec my args to get gen right?
Unless I use the fn itself in it's spec (or trusted equivalent), I can't seem to get gen. to work.
@jaihindhreddy That sounds like an external constraint on the data? An arbitrary vector of 2+ ints and an arbitrary sum aren't going to satisfy that condition so the behavior of two-sum
on such data will be...? Undefined? nil
?
Your :ret
spec doesn't account for the function returning nil
-- which it clearly can -- so your spec isn't right as it stands.
user=> (two-sum (into [] (range 10 20)) 9)
nil
So your :ret
spec needs to be s/nilable
or use s/or
and then your :fn
spec needs to accept that (:ret %)
can be nil
(or should satisfy that complex predicate).It is an external constraint on the data. I thought of using s/nilable
there but actually the fn is not supposed to be called with such args and its UB, and I'm trying to get the generation to work in such a way that there exists exactly one answer.
I'll probably look into using fmap
to generate the sum
from the ints
. Thanks for your help!
And it's also an ascending sequence of ints, yes? (based on the <
logic in there)
I think what you're attempting is a bit self-defeating: the generator and the :fn
spec together are pretty much going to be a re-implementation of the function logic at this point...
Kinda reckoned that myself.
The fn returns a pair of indexes into the ints
arg, so I'm checking that the second one in the return value is less than the count of ints
.
noob question: why spec forms sometimes have qualified, and sometimes unqualified symbols?
minimal example:
(s/def ::vec vector?)
(:pred (first (::s/problems (s/explain-data ::vec {:a 1}))))
; => clojure.core/vector?
(:pred (first (::s/problems (s/explain-data (s/coll-of vector?) [{:a 1}]))))
; => vector?
Should always be qualified
There are some pending patches for this stuff
I'm fascinated by spec, sprinkling it a bit here and there to document/enforce contracts where it makes sense... do you think it's the future of strong gradually static type systems?
like being able to say something along the lines of
let n: int? = (get-some-int)
let x: (and int? even?) = n ;; checks for `even?` during casting
I also think the traditional notion of static type systems look very different if you can work in a dynamic system.
I don't consider it "like" a type system (and I think it's a bit misleading to think of it in those terms).
We've been using spec in production code very heavily since it first appeared. We don't use it much on functions (which is the only "like types" part of it -- and even that isn't much like a "type system").
We mostly use data specs and validation/conformance. We use it to generate (random, conforming) data for example-based tests. We use it for generative testing of some things. We use it via instrument
a little bit during dev/test.