I have a question about spec'ing higher order functions
I have a function until that's defined as follows:
(defn until
"Iterates until predicate is met"
[p f x]
(some #(when (p %)
%)
(iterate f x)))
My function spec is as follows:
(s/fdef until
:args (s/cat :p (s/fspec :args (s/cat :x any?)
:ret boolean?)
:f (s/fspec :args (s/cat :x any?)
:ret any?)
:x any?)
:ret any?)
When I call (until #(= 5 %) inc 0) is says the function call does not meet the spec, giving an error messages that says: (nil) - failed: Cannot invoke "Object.getClass()" because "x" is null in: [1] at: [:f]. Anyone know why?@didibus I actually ran a property test on until that seems to be why it was passing nil in.
@hiredman That all makes a lot of sense. I was conceptualizing any? like a type system, but it's actually very hard to find a function that accepts anything as an argument; inc doesn't.
It seems like it's hard to spec a highly generic function, right? Am I missing something?
It depends, if you want to use instrument, then I think fn? might be preferable to fspec, but there are other things you might use specs for where it wouldn't matter as much
https://clojurians.slack.com/archives/C1B1BB2Q3/p1587432463216700
Don't :args (s/cat :x any?) means it needs an argument x, but you are passing inc with no args to it?
I'm not sure. I thought a unary function would satisfy the spec. How would you make the call? Like (until #(= 5 %) #(inc %) 0)?
That seemed to work... interesting
Actually, I don't think it did... one sec
Yeah, that didn't work
Because the way spec checks that a function is valid for a function spec is by using the function spec to generate arguments and call the function
The "smallest" value that any? generates is nil
Oh, I see. should I use some? instead?
It doesn't matter, when you instrument a function and the spec for that function specs one of its args as a function that is how it works
Okay, do you have a recommendation on how to rewrite the spec to avoid the null error?
You can't without sacrificing the generality (replacing any? with int?)
You could maybe replace fspec with fn? or ifn?, not sure if those have generators though
Right, inc is not in fact a function of any? to any? which is why it fails.
Though, I'm still unclear, I'm guessing you have used s/instrument. That would not run a generator over inc, it should just wrap inc in a way that it validates the args passed to it. So I'm not sure where that nil comes from.
until will be wrapped and try to validate that functions passed into it satisfy the specs
https://clojurians.slack.com/archives/C1B1BB2Q3/p1541895238242400
That's some wild slack-fu, I guess I had forgotten all about that. How big of a generation run does it do? Seems it would be pretty slow.
Ok, it's control by that flag clojure.spec.alpha/*fspec-iterations* and defaults to 21.
Found the issue: https://ask.clojure.org/index.php/3818/disable-fspec-validation-during-instrumentation
I personally would have assumed what Alex suggests:
> Another option that has been proposed for this is to make the instrumented function also wrap the function arg in instrumentation according to its spec.
Best you just use fn? in your case then. And if you really want 1-ary? maybe you can do:
(defn unary-fn? [f]
(and (fn? f)
(try
(f ::sentinel)
true
(catch clojure.lang.ArityException _ false)
(catch Exception _ true))))
But feels a bit hacky, in that it has the same issue as what the ask says, which is it will execute the function on valiation. But also maybe it's fine since it seems fspec also does that.