clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
Jim Newton 2020-11-09T14:53:50.298600Z

how can I recognize an object such as the one returned from (s/or :1 int? :2 number?) . I see that s/regex? , s/get-spec, and s/spec? all return nil. I notice however that the class of the object has (type (s/or :1 int? :2 number?)) in its ancestors?

(ancestors (type (s/or :1 int? :2 number?)))
  ==> #{clojure.spec.alpha.Spec clojure.lang.IObj clojure.lang.IMeta
  java.lang.Object clojure.spec.alpha.Specize}
So I can use the following ugly idiom:
(instance? clojure.spec.alpha.Spec (s/or :1 int? :2 number?))
isn't there a better way?

Jim Newton 2020-11-09T14:56:45.299900Z

Next question, how can I extract int? and number? from the object? I see that (keys (s/or :1 int? :2 number?)) throws an Exception.

(keys (s/or :1 int? :2 number?))
Execution error (IllegalArgumentException) at clojure-rte.genus-spec-test/eval17912 (form-init5322407590801644377.clj:30310).
Don't know how to create ISeq from: clojure.spec.alpha$or_spec_impl$reify__2118
I see that (s/describe (s/or :1 int? :2 number?)) returns the list (or :1 int? :2 number?) . However, to recognize the object as one which has a describe method.

alexmiller 2020-11-09T15:07:31.302300Z

it's important to be clear in talking about the two worlds of spec - spec forms and spec objects. (s/or :1 int? :2 number?) is a spec form, which when evaluated, returns a spec object (something that satisfies the spec protocol). Trying to nail down concrete types for the latter part is going to be a bad time - the important thing is the protocol which is inherently polymorphic.

✔️ 1
Jim Newton 2020-11-09T15:08:19.303800Z

so how do I know whether I have an object which satisfies the spec protocol?

alexmiller 2020-11-09T15:09:30.305100Z

spec 1 does not have great answers for extracting information from either spec objects (which are opaque) or spec forms. One path to this is to write specs for spec forms and then use s/conform to "parse" the forms. A significant stab at this exists in https://clojure.atlassian.net/browse/CLJ-2112 but I think the future direction is really making a more data-centric representation which is one thing we are doing in spec 2 (but that's still very much a work in progress)

alexmiller 2020-11-09T15:11:05.306200Z

spec? is the predicate for that

Jim Newton 2020-11-09T15:11:25.306600Z

yes but spec? returns nil.

alexmiller 2020-11-09T15:11:35.306800Z

for what?

alexmiller 2020-11-09T15:12:53.308400Z

user=> (s/spec? (s/or :1 int? :2 number?))
#object[clojure.spec.alpha$or_spec_impl$reify__2118 0x51ec2df1 "clojure.spec.alpha$or_spec_impl$reify__2118@51ec2df1"]

Jim Newton 2020-11-09T15:13:00.308600Z

aaaaaaaaahhhhhhhhh!!!!!! yikes. s/spec? returns non-nil. That's EXCELLENT I have a local function named gs/spec? which just asks whether it is a sequence whose first element is spec. spec? was my first guess. just was shadowed by a local function. Cool. thanks.

alexmiller 2020-11-09T15:13:17.309Z

yeah, your predicate is asking a symbolic spec form question

Jim Newton 2020-11-09T15:13:25.309300Z

and I just typed spec? at the REPL.

Jim Newton 2020-11-09T15:13:42.309600Z

REPL issue.

Jim Newton 2020-11-09T15:17:41.310500Z

is there a way to undef a function. Sometimes I rename a function while debugging, and the old function (of course) is still defined in vm, and if my code accidentally calls it because I didn't find and change all references, that introduces bugs which are hard to find.

borkdude 2020-11-09T15:26:07.310900Z

@jimka.issy You can use (s/fdef foo/bar nil)

borkdude 2020-11-09T15:26:33.311300Z

(I assume, I know (s/def ::foo nil) at least works)

alexmiller 2020-11-09T15:29:08.311900Z

yes, s/def to nil

Jim Newton 2020-11-09T15:29:12.312Z

what is s/ ?

alexmiller 2020-11-09T15:29:19.312300Z

clojure.spec.alpha

Jim Newton 2020-11-09T15:29:31.312800Z

you mean use spec to redefine a function?

alexmiller 2020-11-09T15:29:46.313300Z

are you asking about function specs or functions?

alexmiller 2020-11-09T15:30:05.314Z

I think @borkdude and I assumed you meant specs since we're in the spec channel here

Jim Newton 2020-11-09T15:30:06.314100Z

funtions. Sorry if I mistyped before?

Jim Newton 2020-11-09T15:30:16.314300Z

sorry, its probably my fault.

alexmiller 2020-11-09T15:30:47.314900Z

you can use ns-unmap

Jim Newton 2020-11-09T15:30:47.315Z

(def name nil) works of course.

alexmiller 2020-11-09T15:31:00.315500Z

^^ that doesn't unmap, just redefines

Jim Newton 2020-11-09T15:31:07.315700Z

ahhh ns-unmap. thats the cleaner way.

borkdude 2020-11-09T15:31:41.316100Z

@jimka.issy

user=> (def x 1)
#'user/x
user=> (ns-unmap *ns* 'x)
nil
user=> x
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: x in this context
user=> user/x
Syntax error compiling at (REPL:0:0).
No such var: user/x

borkdude 2020-11-09T15:32:16.317100Z

I recently discovered ns-unmap also works for imported classes. I need to fix a bug in sci which doesn't support that yet :/ ;)

Jim Newton 2020-11-09T15:32:23.317300Z

I have several functions which are memoized. it speeds up my program 1000 fold. But when debugging, it can be a source of errors, because I forget that the function might not really be called.

alexmiller 2020-11-09T15:32:45.317700Z

the double-edged blade of memoization :)

Jim Newton 2020-11-09T15:33:13.318400Z

indeed. So sometimes I really want to set-the-d*mn-function to undefined

Jim Newton 2020-11-09T15:33:21.318800Z

to make sure it's not a problem of memoization

borkdude 2020-11-09T15:33:23.319Z

@jimka.issy A solution to that problem might be to call the original function foo* and the memoized one foo

Jim Newton 2020-11-09T15:34:09.319400Z

Here's the macro I'm using.

(defmacro defn-memoized
  [[public-name internal-name] docstring & body]
  (assert (string? docstring))
  `(let []
     (declare ~public-name) ;; so that the internal function can call the public function if necessary
     (defn ~internal-name ~@body)
     (def ~(with-meta  public-name {:dynamic true}) ~docstring (memoize ~internal-name))
     ))
This allows my to locally rebind the name such as
(binding [my-function (memoize -internal-name)]
   ...)
which I often do in test suits

borkdude 2020-11-09T15:35:29.320Z

makes sense

borkdude 2020-11-09T15:35:52.320500Z

for tests you can also use with-redefs which doesn't require your vars to be dynamic

Jim Newton 2020-11-09T15:36:11.320800Z

never heard of with-redefs. what's that.

borkdude 2020-11-09T15:36:25.320900Z

(doc with-redefs)

Jim Newton 2020-11-09T15:38:08.321200Z

indeed. so that just saves needing to declare them as dynamic? or does it do something else?

borkdude 2020-11-09T15:38:38.321500Z

dynamic bindings aren't visible to all threads, with-redefs changes the root binding of vars temporarily

alexmiller 2020-11-09T15:39:46.321800Z

(note that there are issues using with-redefs with concurrency, including future etc)

Jim Newton 2020-11-09T15:40:28.322Z

@alexmiller are these the same issues with any dynamic variable?

borkdude 2020-11-09T15:40:43.322200Z

yeah, when running tests concurrently this may bite you

Jim Newton 2020-11-09T15:41:15.322400Z

Aren't dynamic variables thread-local in clojure?

borkdude 2020-11-09T15:41:44.322600Z

yes, but with-redefs doesn't use thread-local, it's just a global mutation that gets restored afterwards

Jim Newton 2020-11-09T15:42:14.322900Z

ah ha.

borkdude 2020-11-09T15:42:43.323200Z

Personally I don't have any test suites that run into problems with this, but then again, I hardly use with-redefs at all

borkdude 2020-11-09T15:43:29.323400Z

There could be a performance penalty to marking all your vars dynamic, but not if they haven't been dynamically re-bound yet since there's a pretty efficient check for that happy path

Jim Newton 2020-11-09T15:52:02.324400Z

why doe s/describe return lists using and and or rather than clojure.spec.alpha/and and clojure.spec.alpha/or ? that makes programmatic usage more difficult.

borkdude 2020-11-09T15:53:53.324900Z

s/describe isn't intended for programmatic usage. use s/form rather than that

😀 1
Jim Newton 2020-11-09T15:54:39.325300Z

Excellent. that does the trick. much more program friendly

alexmiller 2020-11-09T16:00:16.325500Z

we regularly see people run into failing test suites when using with-redefs

alexmiller 2020-11-09T16:00:58.325700Z

because they don't understand what it does, which is why I mention this

borkdude 2020-11-09T16:01:04.325900Z

:thumbsup:

Jim Newton 2020-11-09T16:01:15.326100Z

👍:skin-tone-2:

alexmiller 2020-11-09T16:01:34.326700Z

s/describe is primarily useful for printing shorter specs for humans (used in doc for example)

Jim Newton 2020-11-09T16:01:49.326900Z

makes sense.

2020-11-09T20:48:46.328700Z

I just gotta ask, is there a way to create a shorthand alias for a namespace to use in fully-qualified keys without actually creating that namespace (using the ns form)?

borkdude 2020-11-09T20:51:35.329500Z

@amorokh I think this is a pretty common pattern:

user=> (require '[clojure.spec.alpha :as s])
nil
user=> (alias 'foo (create-ns 'foo))
nil
user=> (s/def ::foo/bar int?)
:foo/bar
Rumor has it that core team is working on making this easier

2020-11-09T20:53:24.330300Z

@borkdude thanks, not sure I want to do that but at least I know about that possibility