Fork me on GitHub
#clojure-spec
<
2017-08-11
>
tianshu05:08:19

Can I add spec to a function before I define the function? like @spec in elixir. And is it possible to have a variable T to represent some type like generic types?

seancorfield05:08:46

@doglooksgood Not sure what you mean about "generic types" in the context of Clojure?

seancorfield05:08:31

as for declaring a spec for a function before its defn, yes, that definitely possible...

tianshu06:08:12

my mistake, I was thought that I have to write spec for function after that is defined. for my first question, maybe I shouldn't call it a generic type. I want to know something that can help me writing a spec for functions like map.

seancorfield06:08:28

map is a pretty hard function to write a spec for -- especially since the one argument version returns a transducer 🙂

seancorfield06:08:33

You don't need to spec everything. Just spec at the boundaries of your subsystems and/or APIs.

tianshu06:08:10

it seems impossible for writing spec for a function that receive T and returns T?

Alex Miller (Clojure team)16:08:30

correct - specs are not parameterized

didibus17:08:15

I think it doesn't make sense to have parameterized specs. Because they're predicate based, the parameters would be non intuitive. For each one you'd have to pass in a pred, but you wouldn't know how that pred would combine with the spec. And each spec could do it differently. That said, I'm not sure, it's a good idea. I'm trying to think if there's useful real use cases for it, nothing jumps at me right away, can you suggest one?

Alex Miller (Clojure team)18:08:04

well, we’re not going to do it, so I’m not going to invent a reason why :)

seancorfield06:08:34

I think spec is much more valuable for spec'ing data structures than functions, to be honest, but that might be the domain I'm working in (where I'm using spec mostly to describe what API parameters should be, so s/conform and s/invalid? are being called).

seancorfield06:08:56

So you want the type of the return to match the type of the first argument?

tianshu06:08:52

so I should use :fn in fdef?

seancorfield06:08:58

You can spec that with :fn to check that yes.

seancorfield06:08:58

Do you really want identical types tho'? What sort of function are you talking about?

seancorfield06:08:54

For example, map takes a function and a collection and returns a lazy sequence so the types don't match there -- but I guess you could check non-empty collection/sequence members being the same type? But you can't do much for empty collection/sequence. Would you want to check the whole input/output? What about lazy sequences not being entirely consumed, like (take 5 (map inc (range)))?

seancorfield06:08:15

You need to be careful that the spec doesn't realize the whole sequence.

misha06:08:57

@doglooksgood :fn will not be checked by instrumentation. you will need you run tests explicitly.

tianshu06:08:55

I got a library for this

tianshu06:08:52

But I think it will be nice to have some spec inference in editor. but without type variable, it seems to be difficult.

didibus17:08:00

@doglooksgood I'd give a look at https://github.com/arohner/spectrum It gives everything you spec a type based on the spec, and then uses that to validate types at compile time. Maybe you could use it to infer things in an editor.

arohner19:08:57

I’m slowly working on inference of untyped fns

arohner19:08:44

also, spectrum isn’t really usable for production use yet

souenzzo19:08:46

Functions with the signature like +: (fn [& args])... How to declare theirs specs? (s/fdef my-fn :args (s/coll-of integer?)) ?

bfabry20:08:17

@souenzzo (s/cat (s/* integer?))

bfabry20:08:42

@souenzzo actually minus the cat, just (s/* integer?)

bfabry20:08:44

boot.user=> (defn +'' [& args] (apply + args))
#'boot.user/+''
boot.user=> (s/fdef +'' :args (s/* integer?))
boot.user/+''
boot.user=> (st/instrument)
[boot.user/+'']
boot.user=> (+'' 1 2 3)
6
boot.user=> (+'' 1 2 3 'a)
clojure.lang.ExceptionInfo: Call to #'boot.user/+'' did not conform to spec:
                            In: [3] val: a fails at: [:args] predicate: integer?