Fork me on GitHub
#clojure-spec
<
2019-06-17
>
Alexander Stavonin01:06:50

Is it possible to check that a spec argument is a function with expected signature?

Alexander Stavonin01:06:46

I can easily implement such check with protocols, but in one particular case function is preferable for me.

taylor02:06:17

spec alpha1 “checks” function values by invoking them; so yes, if that meets your needs you could spec function args

taylor02:06:35

maybe you could do something with meta and :arglists?

Alexander Stavonin03:06:01

I have a function which accept other function as argument. I’d like to be sure, the argument is: a) function, b) function with expected signature (lets say with 2 arguments). I suppose, fn? could be helpful here, but what should I do with function arguments test?

seancorfield04:06:21

@UKHQCLTQV clojure.spec isn't a type system.

Alexander Stavonin04:06:17

I know, but sometimes type-system like guarantees are very convenient. This is especially important in cross components calls. Can you suggest any other way to which will provide such guarantees?

seancorfield04:06:30

Maybe you want to look at Typed Clojure instead, in order to analyze code and ensure that you're passing the right sort of function as an argument?

Alexander Stavonin04:06:55

@U0JLGECPK, are you talking about https://github.com/clojure/core.typed? I just looked thru readme and got feeling Typed Clojure means – whole project should be statically typed. This is not a goal for me, as I’m more or less happy with dynamic typing, and the only needs is – cross components interface data and types validity guarantees.

Alexander Stavonin04:06:13

Ok, I should look deeper here:

This work adds static type checking (and some of its benefits) to Clojure, a dynamically typed language, while still preserving idioms that characterise the language. It allows static and dynamically typed code to be mixed so the programmer can use whichever is more appropriate.

seancorfield04:06:58

Yeah, there are trade offs. And that's an old article. core.typed has had a lot of work since then.

seancorfield04:06:17

We also tried it back then and gave up for all the same reasons CircleCI gave up.

seancorfield04:06:39

But the question you're asking is best answered by core.typed rather than spec.alpha

djtango10:06:42

that said - Racket was able figure out a solution to higher-order functions with their run-time contracts system, that also doesn't involve doing gen-testing on the input function

djtango10:06:54

but function equivalence is undecidable so tradeoffs are always going to have to be made in this space...

drone12:06:59

Typed Clojure seems to be pretty inactive. While spec isn’t a type system, I could see function arity being part of a contract. E.g., for a function passed to reduce. But I also think the spec designers would argue that function arity errors already exist and solve most of the problem.

seancorfield16:06:28

Ambrose just successfully defended his thesis work on Typed Clojure. It's still an active project.

drone16:06:25

There was a flurry of activity last winter, and then other than a few commits to the core.typed.analyzer.jvm project in April (likely in preparation for defense) there has been no activity for seven months

seancorfield16:06:19

If you've watched any of his talks you know that he has a lot of design work ongoing about dealing with the typed / untyped code boundary. Just because there have been no recent commits doesn't mean there's no recent work -- this is an extremely hard topic area that requires a lot of "hammock" time.

drone16:06:57

sure, but it’s also just as likely he is moving on from the project after completing his PhD program

seancorfield19:06:15

Not based on what he's talked about at conferences.

drone22:06:12

that’s great if he’s still working on it. but based on observable output, including: repo commits, the project’s twitter account, his patreon, and activity in #core-typed; not much is going on in the project and that’s what I meant by “inactive”. this seems like a weird thing to argue about…

👍 4
Jakub Holý (HolyJak)12:06:46

Is there a shorter, nicer way to do this (without nesting if in let)?

(defn conform! [x spec]
  (let [conformed [(s/conform spec x)]]
    (if (#{::s/invalid} conformed)
      (throw (ex-info
               (s/explain-str spec x)
               (s/explain-data spec x)))
      conformed)))

Charles Fourdrignier12:06:21

This looks like a lot to Stuart Halloway conform!, so I guess you can't do better. https://github.com/Datomic/mbrainz-importer/blob/master/src/cognitect/xform/spec.clj

❤️ 4
Olical16:06:03

I came up with something similar but as a macro

(defmacro on-invalid [conformed fallback]
  `(let [conformed# ~conformed]
     (if (s/invalid? conformed#)
       ~fallback
       conformed#)))

(defn validate [x spec message]
  (when-not (s/valid? spec x)
    (throw (ex-info (str message "\n\n" (expound/expound-str spec x)) {})))
  x)

ghadi13:06:45

@holyjak don't do that because you'll be putting a ton of data in the exception message (via explain-str)

Jakub Holý (HolyJak)13:06:08

hm, good point, thanks!

Jakub Holý (HolyJak)13:06:04

maybe I should just use (-> explain-data ::s/problems first (dissoc :val :value)) instead?

misha23:06:52

implementing this as macro has an advantage of being able to include calling form into error data/message, which saves a lot of time for exceptions purposed for developer and repl. or just have an error-msg arg in a second arity, with default message like "does not conform to spec <spec form>" because you'll expand exception data in repl anyway, because "first problem" is not always "the only problem"