Fork me on GitHub
#clojure-spec
<
2017-05-09
>
mpenet08:05:27

Would it make sense to have a ^:fdef (or ^:spec) meta on fn to do the :pre/:post automatically from a fdef ? Before you mention instrument, it's not the same (instrument triggers gen).

mpenet08:05:37

that would avoid some boilerplate :

(s/fdef ::foo
        :args (s/cat :a ::a
                     :b ::b
                     :c ::c))
(defn foo
  [a b c]
  {:pre [(s/assert (:args (s/get-spec ::foo))
                   [a b c])]
   :post [(s/assert (:ret (s/get-spec ::foo))
                   %)]}
  42)
vs
(s/fdef ::foo
        :args (s/cat :a ::a
                     :b ::b
                     :c ::c))
(defn foo ^:fdef ::foo
  [a b c]
  42)

mpenet08:05:09

the spec and where it's applied is still decoupled, the latter would basically expand into the former (almost, omited the potential :fn part of the spec)

Alex Miller (Clojure team)08:05:48

no, not interested in that. first, this re-couples vars and function specs which are intentionally not coupled (and can be created in either order). second, we don’t generally want to enable pre/post checking of specs for performance reasons.

mpenet08:05:58

(it doesn't have to use :pre/:post, would open doors to auto conforming via metadata hint etc)

Alex Miller (Clojure team)08:05:59

I don’t think Rich has any interest in that

mpenet08:05:18

alright, not surprised, I guess this might end up being done as part of a lib

mpenet08:05:10

pre/post is toggleable (s/assert as well), but again this would/should be done without these 2 anyway

mpenet09:05:17

@alexmiller about ordering I am not sure I follow, if that expands to s/assert calls it wouldn't matter/cause any issue

Alex Miller (Clojure team)09:05:01

pre/post effects what ends up in the compiled code (even if toggled off) and thus has a runtime cost

Alex Miller (Clojure team)09:05:25

s/assert can actually be omitted from compilation entirely using s/*compile-asserts* etc

mpenet09:05:00

ok, so lets say it's implemented without pre/post 🙂, just with s/assert

Alex Miller (Clojure team)09:05:23

re ordering, right now you create the specs before the functions or after the functions

Alex Miller (Clojure team)09:05:34

by having defn rely on the spec, you’ve changed that

mpenet09:05:43

(s/fdef ::foo-like ...)

(defn foo [x y]
  (s/assert ::foo-like [x y])
  ...
  )

mpenet09:05:01

it would be the same as the above , you can just def ::foo-like after as well

mpenet09:05:07

or I am missing something

Alex Miller (Clojure team)09:05:19

I don’t know, maybe that would work

Alex Miller (Clojure team)09:05:52

aside: the ::foo-like there would be `foo

mpenet09:05:20

I don't follow

Alex Miller (Clojure team)09:05:31

function specs are named by symbol, not keyword

mpenet09:05:43

missed that one

Alex Miller (Clojure team)09:05:46

generally, we don’t think you should be doing this though

mpenet09:05:54

that would be handy to be able to reuse specs defined for instrument on live (staging/test) systems without paying the gen cost when that matters

Alex Miller (Clojure team)09:05:33

there is no “gen cost”?

mpenet09:05:55

hmm if I recall instrument does trigger gen in some cases

Alex Miller (Clojure team)09:05:09

the only case like this is with fspec

Alex Miller (Clojure team)09:05:11

if that’s a problem for you, then don’t use fspec

mpenet09:05:17

well ... first class functions are quite common in clj code, but yes that's an option

lambder11:05:27

hello all I got the following problem:

clojure

(s/def ::s string?)

(s/def ::g
  (s/cat :string ::s
         :g (s/* ::g)))

(s/conform ::g '["abc" "abc"]) ; --> ok;

(s/def ::g2
  (s/cat :string (s/? ::s)
         :g (s/* ::g2)))

(s/conform ::g2 '["abc" "abc"]) ; --> java.lang.StackOverflowError
any ideas?

dergutemoritz11:05:13

@lambder That recursion will never terminate because (s/? ::s) may match nothing so once the two "abc" strings are matched, it will recur forever on the remaining sequence []

lambder11:05:01

@dergutemoritz how do I much the following grammar then?

S -> T R | R
T -> :tag
R -> :foo | :foo R

dergutemoritz11:05:25

@lambder That | in there is probably s/or

dergutemoritz11:05:51

Err sorry, it's s/alt in regex

dergutemoritz11:05:27

Everything else is s/cat

dergutemoritz11:05:46

Just an educated guess, of course, not sure what grammar notation you're using exactly there

dergutemoritz11:05:23

OK then yeah, that should work#

dergutemoritz11:05:53

Hm well it's not really BNF though, that doesn't use -> at least

dergutemoritz11:05:11

Don't have an SO account

dergutemoritz11:05:40

YW, hope it works out!

vandr0iy12:05:28

suppose I got a spec, (s/def ::foo (s/keys :req-un [::a ::b] :opt-un [::c ::d])) and a data structure, (def bar {:a 1 :c 3 :d 4 :e 5}). How do I remove from the bar all the key-value pairs not explicitly mentioned in the spec and fill in with nils the data that's missing (so, :b nil and remove :e)?

lambder12:05:07

is it possible to preserve attached meta to structures which are spec conformed?

lambder14:05:23

I’ve done this:

(in-ns 'clojure.spec)
(defn conform [spec x]
  (let [m (clojure.core/or (meta x) {})]
    (let [obj (conform* (specize spec) x)]
      (cond
        (instance? clojure.lang.MapEntry obj) (with-meta (into [] obj) m)
        (instance? clojure.lang.IObj obj) (with-meta obj m)
        :else obj))))

lambder14:05:04

it’s a bit hacky

lambder15:05:26

Is it possible to ‘tap’ into a protocol method. I’d like to have ‘around’ wrapper for all such method calls. e.g there is a protocol clojure.spec.Spec which has conform* method defined. Some of the conform* implementations call conform* recursively. I’d like to wrap my logic around each such call.

lambder15:05:56

I want to inject some code allowing to preserve meta of the parsed data-structures

lambder15:05:47

my monkey patch is :

(in-ns 'clojure.spec)
(defn conform [spec x]
  (let [m (clojure.core/or (meta x) {})]
    (let [obj (conform* (specize spec) x)]
      (cond
        (instance? clojure.lang.MapEntry obj) (with-meta (into [] obj) m)
        (instance? clojure.lang.IObj obj) (with-meta obj m)
        :else obj))))

lambder15:05:29

but the call to conform is sometimes omitted as conform* is called instead

Alex Miller (Clojure team)16:05:16

in short, no and anything touching these impl details stands a good chance of being broken later

lambder16:05:35

@alexmiller yes, i think so too. Any idea why conform* implementations aren’t calling conform? performance?

dergutemoritz16:05:26

@lambder Err I just realized that the grammar you pasted is not equivalent to what you were trying to express in spec - according to that, :tag is only allowed as the first element of the whole sequence and then only :foos are allowed (because R recurs on itself, not on S). Is that what you intended?

lambder16:05:12

not really. the grammar was simplification of what i needed

lambder16:05:00

but thanks to http://stackoverflow.com/a/43869208/135892 now I know how to express this kind of grammers in spec

dergutemoritz16:05:56

@lambder AFAIUI that's more complicated than necessary. Doesn't something like (s/def ::g (s/+ (s/cat :tag (s/? ::tag) :val (s/alt :number ::n :string ::s)))) capture what you want to do much neater + give you a more convenient result structure to work with?

dergutemoritz16:05:52

Might be misinterpreting your use case, of course. Anyhow, gotta run. Cheers

danielcompton21:05:38

Is there a way to define newtype's in spec? e.g. specialise a Long to an AccountId or a ProductId. I'm pretty sure there's not, but thought I'd ask in case.

ghadi22:05:29

@danielcompton seems like the spec name itself is that information

danielcompton22:05:31

Let's say I have a function that gets a product (get-product product-id). If the product-id and account-id are both Longs, nothing stops me from passing it (get-product account-id)

caio22:05:47

I solve this by creating :account/minimal as (s/keys :req [:account/id] :opt [ all the other stuff]) when really necessary

danielcompton22:05:41

but if you ever need to extract and pass around :account/id by itself, then you can't guarantee that that's what the functions are taking?

caio23:05:23

I don't think so. specs are about contracts, not strict types

caio23:05:37

if you want to be this strict, define :account/id as (s/tuple [#(= % :id/account) int?]) or something like that (maybe multi-spec?) and adapt inside the fns that use the id