Fork me on GitHub
#clojure-spec
<
2020-11-09
>
Jim Newton14:11:50

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 Newton14:11:45

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.

Alex Miller (Clojure team)15:11:31

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.

✔️ 3
Jim Newton15:11:19

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

Alex Miller (Clojure team)15:11:30

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)

Alex Miller (Clojure team)15:11:05

spec? is the predicate for that

Jim Newton15:11:25

yes but spec? returns nil.

Alex Miller (Clojure team)15:11:53

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 Newton15:11:00

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.

Alex Miller (Clojure team)15:11:17

yeah, your predicate is asking a symbolic spec form question

Jim Newton15:11:25

and I just typed spec? at the REPL.

Jim Newton15:11:41

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.

borkdude15:11:07

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

borkdude15:11:33

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

Jim Newton15:11:12

what is s/ ?

Jim Newton15:11:31

you mean use spec to redefine a function?

Alex Miller (Clojure team)15:11:46

are you asking about function specs or functions?

Alex Miller (Clojure team)15:11:05

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

Jim Newton15:11:06

funtions. Sorry if I mistyped before?

Jim Newton15:11:16

sorry, its probably my fault.

Jim Newton15:11:47

(def name nil) works of course.

Alex Miller (Clojure team)15:11:00

^^ that doesn't unmap, just redefines

Jim Newton15:11:07

ahhh ns-unmap. thats the cleaner way.

borkdude15:11:41

@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

borkdude15:11:16

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 Newton15:11:23

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.

Alex Miller (Clojure team)15:11:45

the double-edged blade of memoization :)

Jim Newton15:11:13

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

Jim Newton15:11:21

to make sure it's not a problem of memoization

borkdude15:11:23

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

Jim Newton15:11:09

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

borkdude15:11:29

makes sense

borkdude15:11:52

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

Jim Newton15:11:11

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

borkdude15:11:25

(doc with-redefs)

Jim Newton15:11:08

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

borkdude15:11:38

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

Alex Miller (Clojure team)15:11:46

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

Jim Newton15:11:28

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

borkdude15:11:43

yeah, when running tests concurrently this may bite you

Jim Newton15:11:15

Aren't dynamic variables thread-local in clojure?

borkdude15:11:44

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

borkdude15:11:43

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

borkdude15:11:29

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

Alex Miller (Clojure team)16:11:16

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

Alex Miller (Clojure team)16:11:58

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

Jim Newton16:11:15

👍:skin-tone-2:

Jim Newton15:11:02

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.

borkdude15:11:53

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

😀 3
Jim Newton15:11:39

Excellent. that does the trick. much more program friendly

Alex Miller (Clojure team)16:11:34

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

Jim Newton16:11:49

makes sense.

amorokh20:11:46

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)?

borkdude20:11:35

@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

amorokh20:11:24

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