speculative

https://github.com/borkdude/speculative
mfikes 2018-10-26T04:39:42.000800Z

There is now a candidate patch in https://dev.clojure.org/jira/browse/CLJS-2793 that might help with proper behavior when specing variadic ClojureScript fns.

šŸ‘ 1
slipset 2018-10-26T05:35:20.001800Z

Nice! Iā€™m impressed! Itā€™d be super interesting to me at least to know something about the workflow you use to fix bugs as these.

slipset 2018-10-26T05:36:37.003700Z

I looked at the code in question since the stacktrace pointed me to it, but when I saw the code, I backed very slowly away. Too scared:)

mfikes 2018-10-26T11:33:30.007200Z

Yeah, that is a particularly vexing piece of code. Even David finds it super challenging. But, one the next step is testing this thing, so if you are doing any speculative work, try using a locally built version with that patch. I'm going to get that patch into this https://github.com/mfikes/patch-tender

borkdude 2018-10-26T07:37:27.004700Z

I noticed that running the cljs tests with the cljs-test runner is much much faster than with our script: https://github.com/borkdude/speculative/blob/cljs-test-runner/deps.edn#L17 With the runner:

$ time clj -A:test:cljstests2

Testing speculative.core-test

Ran 19 tests containing 65 assertions.
0 failures, 0 errors.
clj -A:test:cljstests2  16.97s user 0.90s system 314% cpu 5.680 total
With our script:
$ time clj -A:test:cljstests
WARNING: Wrong number of args (4) passed to cljs.core/range at line 191 /Users/Borkdude/Dropbox/dev/clojure/speculative/test/speculative/core_test.cljc

Testing speculative.core-test

Ran 19 tests containing 65 assertions.
0 failures, 0 errors.
clj -A:test:cljstests  100.56s user 3.30s system 348% cpu 29.830 total

borkdude 2018-10-26T07:37:55.005Z

Not sure why this is.

borkdude 2018-10-26T07:46:35.005600Z

Maybe because it used Nashorn.

slipset 2018-10-26T08:27:55.006100Z

@bbrinckā€™s inteview on ā€œThe Replā€ podcast is quite interesting.

borkdude 2018-10-26T08:30:57.006400Z

yeah, I listened to it last Wednesday

borkdude 2018-10-26T11:28:30.006900Z

Got something working for :ret and :fn checking. Not quite there yet, but it does something šŸ™‚ https://github.com/borkdude/speculative/blob/a05836d28b056267aa487ceef5253dba7802c793/test/speculative/core_test.cljc#L11

mfikes 2018-10-26T11:34:36.007500Z

Yeah, Node is much faster than Nashorn. The problem with Node is it is challenging to get the error code out of it when using`cljs.main`

borkdude 2018-10-26T11:35:15.007700Z

yeah, the test runner does it properly somehow

borkdude 2018-10-26T11:37:00.007900Z

probably something in doo

bbrinck 2018-10-26T13:08:43.009400Z

Iā€™m excited to see so much activity on speculative - nice work!

bbrinck 2018-10-26T13:09:21.010200Z

Iā€™ve been thinking about if/how expound could be extended to take advantages of this work

bbrinck 2018-10-26T13:11:05.011800Z

One issue that Alex noted is that many core functions intentionally work on abstract concepts like ifn? and seq? which may yield somewhat confusing error messages.

bbrinck 2018-10-26T13:12:55.013300Z

e.g. getting an error from map that says the second argument:

should satisfy

  ifn?
is not super descriptive unless you know clojure. But I was thinking about extending custom error messages for predicates (https://github.com/bhb/expound#error-messages-for-predicates) to build up a set of more human-readable strings for predicates

bbrinck 2018-10-26T13:16:47.015800Z

So the error could read something like ā€œshould be a function (or a value that can be called like a function, such as a keyword, set, or map)ā€

šŸ‘ 1
bbrinck 2018-10-26T13:18:54.018800Z

These could be defined in an optional ns within expound, or maybe a new project. I suppose they could be defined in speculative, but that had the downside of requiring a dependency on expound.

borkdude 2018-10-26T13:32:07.019500Z

Nice! Probably the responsibility of rendering spec errors to text belongs to expound?

bbrinck 2018-10-26T13:56:23.021400Z

I agree it shouldnā€™t go in speculative, but not sure it should go in expound either - these messages are really a user-defined configuration for expound

bbrinck 2018-10-26T13:57:39.022800Z

Perhaps it should just be a separate library for now, but I may move it into expound later (and put it in a ns that the user can optionally load)

bbrinck 2018-10-26T14:02:55.024300Z

Yeah, a separate library might be best, since this library would depend on both speculative and expound (whereas I wouldnā€™t want expound to depend on speculative, and speculative shouldnā€™t depend on expound either)

borkdude 2018-10-26T14:05:37.024600Z

you could always make a speculative-expound library šŸ˜‰

šŸ‘ 1
borkdude 2018-10-26T14:06:25.025200Z

or expound-core where you hide the name of speculative as an implementation detail šŸ˜‰

borkdude 2018-10-26T16:45:25.025700Z

@slipset I noticed you removed the ā€œinlinedā€ specs, but they still have value imo.

slipset 2018-10-26T16:46:04.026300Z

Yeah, I they serve a documentation purpose.

slipset 2018-10-26T16:46:25.026800Z

I was wondering about including a really.speculative ns for these kinds of specs šŸ™‚

borkdude 2018-10-26T16:47:15.027300Z

They can be checked in tests. You only have to be a bit careful. https://github.com/slipset/speculative/blob/e0e2f192c7da8c78f89a4d14c38ea8de11ac4ccc/test/speculative/core_test.cljc#L16

borkdude 2018-10-26T16:47:45.027700Z

And it doesnā€™t hurt to instrument them, even if they are inlined.

slipset 2018-10-26T16:47:55.027900Z

ok, Iā€™ll revert

slipset 2018-10-26T16:48:41.028400Z

reverted.

borkdude 2018-10-26T16:48:45.028500Z

About this issue: I fixed this in the ā€œstyle guideā€ branch: https://github.com/slipset/speculative/issues/43#event-1929317840

borkdude 2018-10-26T16:50:25.029Z

(under the umbrella of using meaningful names for alternative arities)

slipset 2018-10-26T16:50:44.029300Z

this language is crazy!

borkdude 2018-10-26T16:50:56.029500Z

yeah šŸ˜‰

slipset 2018-10-26T16:51:02.029700Z

user> (get nil nil nil)
;; => nil
user> (get #{'foo} 'foo)
;; => foo
user> (associative? #{'foo})
;; => false
user>

borkdude 2018-10-26T16:51:32.030400Z

I guess it isnā€™t an associative because you cannot (assoc #{} 0)

slipset 2018-10-26T16:52:07.031100Z

exactly, but given that sets are basically just maps of keys onto themselves, one could argue that

slipset 2018-10-26T16:52:20.031700Z

(assoc #{} 'foo 'foo) should work

borkdude 2018-10-26T16:52:31.032Z

agree (although I remember some discussion about this, of which I cannot remember the exact points)

borkdude 2018-10-26T16:53:00.032500Z

I think it had to do with the semantics of some stuff where you get could in trouble if this was the case

borkdude 2018-10-26T16:53:10.032700Z

(sorry for the vagueness)

borkdude 2018-10-26T16:53:50.033200Z

I already changed stuff to ifn? and seqable? in the style guide branch as well, as I made it part of the proposal

borkdude 2018-10-26T16:54:27.033800Z

(referring to https://github.com/slipset/speculative/issues/46)

slipset 2018-10-26T16:55:15.034200Z

Nice!

slipset 2018-10-26T16:56:01.035200Z

I think a couple of fns that would be interesting to have specs on is the stuff in clojure.set which fails in strange ways if you pass eg vectors instead of sets.

borkdude 2018-10-26T16:56:21.035500Z

agreed

slipset 2018-10-26T17:00:11.035900Z

What kindā€™a language is this šŸ™‚

slipset 2018-10-26T17:00:12.036100Z

user> (get '(1) 0)
;; => nil
user> 

slipset 2018-10-26T17:01:37.036500Z

user=> (get 1 1 )
nil
user=> (get "string" 1 )
\t
user=>

slipset 2018-10-26T17:01:52.036800Z

basically the spec for get is useless.

borkdude 2018-10-26T17:56:42.037700Z

@slipset would you mind having a look at the style-guide branch that also has some code I wanna merge.

slipset 2018-10-26T17:57:06.037900Z

Will do

borkdude 2018-10-26T17:57:43.038200Z

AFAIK nothing controversial in it anymore. šŸ™‚

borkdude 2018-10-26T18:00:34.038500Z

Iā€™ll try to rebase it on master now

borkdude 2018-10-26T18:03:40.038700Z

Rebase done

borkdude 2018-10-26T18:09:35.039300Z

@slipset about get: I think thereā€™s a lot of frog area in there (undefined, not important to spec, like (get 1 1))

borkdude 2018-10-26T18:10:09.039900Z

put another way: I think itā€™s fine to write a spec that doesnā€™t allow 1 as a collection to get from

mfikes 2018-10-26T18:14:50.040800Z

If you look at the source for get in ClojureScript, youā€™ll see that there is a cond checking for different types, with an :else that returns nil

borkdude 2018-10-26T18:16:54.041500Z

@mfikes anything you disagree about the style-guide before I merge it? we can continue to change it always of course šŸ˜‰

borkdude 2018-10-26T18:17:12.041800Z

oh, so it is actually non-frog behavior then

mfikes 2018-10-26T18:17:15.042100Z

cljs.user=> (extend-type number
       #_=>  ILookup
       #_=>  (-lookup [_ _] 42))
nil
cljs.user=> (get 1 17)
42

borkdude 2018-10-26T18:17:40.042700Z

so it has to be something like (s/cat :thing any? ...)

mfikes 2018-10-26T18:18:07.043100Z

Yeah, any?

borkdude 2018-10-26T18:18:20.043300Z

extensible implies any?

mfikes 2018-10-26T18:20:08.043900Z

I donā€™t see any issues with the style guide. Use of s/alt is new to me. Iā€™ll read up on it.

borkdude 2018-10-26T18:20:15.044100Z

it came from Alex.

borkdude 2018-10-26T18:20:43.044700Z

I think itā€™s because arguments always talk about a (s/cat ...) and you use s/alt in s/cat

borkdude 2018-10-26T18:21:23.045100Z

but maybe @alexmiller can enlighten us a bit

borkdude 2018-10-26T18:22:11.045600Z

s/or might be more reserved for logical or cases

borkdude 2018-10-26T18:23:15.046Z

although the docstring of both are equal

slipset 2018-10-26T18:26:42.046300Z

Nice work on the style-guide and the resulting code, Michiel.

slipset 2018-10-26T18:27:13.046700Z

Feel free to merge šŸ™‚

slipset 2018-10-26T18:29:16.048300Z

Regarding functions like (s/* any?) => (s/* any), maybe we should mark them in popular.md as not really worth implementing?

slipset 2018-10-26T18:29:56.049100Z

Problem is that itā€™s hard to figure out if anything that wide/generic without actually implementing the spec, and then it sort of is there.

borkdude 2018-10-26T18:32:14.049700Z

whatā€™s a function that has this ā€œtypeā€? str for example has the lhs, but not the rhs?

borkdude 2018-10-26T18:33:34.050300Z

I agree on the general direction of most important/interesting specs first

slipset 2018-10-26T18:38:41.050800Z

get seems to be this kind of function

borkdude 2018-10-26T18:39:20.051500Z

(get any? any? any?) => any? hmm yes

slipset 2018-10-26T18:39:43.052100Z

actually, (get any? any? any?) => any?)

borkdude 2018-10-26T18:39:58.052600Z

yes with s/? for the last one šŸ™‚

slipset 2018-10-26T18:41:08.054600Z

also a bit doubtful wrt if, when, or, and, not. But then since the first is a special form, and the next three are macros, we can probably leave them be.

borkdude 2018-10-26T18:41:30.055100Z

agreed, same with ->, etc.

bbrinck 2018-10-26T18:42:26.056500Z

just something to consider: itā€™s possible there may be multiple useful specs for a function. stu mentioned that if you add a spec and a working program starts failing, then thatā€™s not a useful spec (ā€œspec should work for you, you shouldnā€™t work for the specā€) but OTOH, I imagine some cases (esp. beginners) where a more restrictive spec would be useful to avoid likely errors

borkdude 2018-10-26T18:42:33.056900Z

or maybe -> has something like (-> any? (s/* (s/or list? ifn?)))ā€¦

bbrinck 2018-10-26T18:43:08.057700Z

not sure if thatā€™s something speculative could support via different namespaces, or if thatā€™s too complex

borkdude 2018-10-26T18:43:38.058800Z

@bbrinck we had a conversation before what the rationale of speculative should be. who do we target?

borkdude 2018-10-26T18:43:55.059300Z

personally I hope to get something out of it for my own dev experience

bbrinck 2018-10-26T18:44:00.059500Z

but i can certainly imagine an existing project opting into the most backwards compatible specs, but using ā€œtighterā€ specs that avoid more more bugs as time goes on

borkdude 2018-10-26T18:45:02.061900Z

if you make specs tighter for beginners (than actually is the case in core) itā€™s hard to foresee what programs they wonā€™t be able to write anymore

slipset 2018-10-26T18:45:40.063100Z

My initial thoughts for speculative was two: 1) provide better error messages through spec. 2) Make something that might be included in clojure.core if they want it.

slipset 2018-10-26T18:46:26.064800Z

But I hadnā€™t made up my mind which of the two was more important, because I didnā€™t realize that theyā€™d class so early/often.

bbrinck 2018-10-26T18:46:41.065200Z

Right, thereā€™s sort of a tension here

bbrinck 2018-10-26T18:47:05.066Z

(I totally agree with saying ā€œnoā€ to certain use cases, just pointing out that Iā€™m unclear on what Iā€™d choose right now)

slipset 2018-10-26T18:47:05.066100Z

So, I wrote a spec for / which didnā€™t allow for division by zero.

bbrinck 2018-10-26T18:47:45.067600Z

e.g. for get - is (get 1 1) useful? Does anyone really use that? Technically itā€™s valid

slipset 2018-10-26T18:47:51.067800Z

Which makes sense for 1), because it gives a better error message for the user, but violates 2)

borkdude 2018-10-26T18:48:31.068900Z

extensibility implies any?

bbrinck 2018-10-26T18:48:58.070200Z

agreed

slipset 2018-10-26T18:49:06.070700Z

So, at least for now, I tend to listen whenever @alexmiller chimes in with some advice šŸ™‚

bbrinck 2018-10-26T18:49:15.071100Z

My point is that I can see at least two valid goals for a set of specs

borkdude 2018-10-26T18:49:17.071300Z

if you have a closed world like maria.cloud itā€™s a different story, but then I can imagine you instrument different specs depending on the level of the ā€œgameā€

bbrinck 2018-10-26T18:49:59.072700Z

1. should be backwards compatible with existing behavior, even if behavior is a bit strange 2. help all users avoid common mistakes when using functions (see surprising behaviors with using set ns)

bbrinck 2018-10-26T18:50:16.073400Z

totally valid to pick 1 over 2!

slipset 2018-10-26T18:50:45.074600Z

I guess one could imagine that speculative.core contains stuff that aligns with clojure.core, and that another namespace really.speculative could contain stricter specs.

bbrinck 2018-10-26T18:51:34.075800Z

another vector is perf, I can at least imagine a spec that is more correct, but more expensive to verify (especially ones that validate anonymous functions)

slipset 2018-10-26T18:51:47.076100Z

or speculative.orly?

bbrinck 2018-10-26T18:52:17.077Z

so I can imagine a user opting into the more correct, but more expensive specs (e.g. the second arg is map isnā€™t really ifn?, itā€™s a function with specific properties)

bbrinck 2018-10-26T18:52:31.077700Z

but for most users, the cheaper, ifn? check may be plenty

borkdude 2018-10-26T18:52:35.078Z

perf really is affected even with the small amount of specs now in speculative. I tried it in a big app which becomes significantly slower (backend + frontend)

bbrinck 2018-10-26T18:52:52.078400Z

yeah

bbrinck 2018-10-26T18:53:18.079500Z

anyway, love what you all are doing, and Iā€™m not trying to say which is the right priority šŸ™‚

borkdude 2018-10-26T18:53:29.080200Z

I can imagine a more clever workflow with not having instrumentation on all the time. Only in cases like: what the heck went wrong here. Letā€™s turn it on.

slipset 2018-10-26T18:53:43.080900Z

ifn? is actually the only correct thing for map:

slipset 2018-10-26T18:53:48.081200Z

user=> (fn? :key)
false
user=>

bbrinck 2018-10-26T18:53:51.081400Z

just noting that if we know the goals of speculative, someone else could potentially fork and write a different set of specs with different goals šŸ™‚

slipset 2018-10-26T18:54:11.082100Z

@bbrinck I really appreciate the discussions/feedback

bbrinck 2018-10-26T18:55:16.083Z

> what the heck went wrong here yeah. if it were safe to retry functions, itā€™d be interesting to run without instrumentation by default, then re-run on error. not easy to do though ā€¦

slipset 2018-10-26T18:55:48.084Z

Also, wrt forking, Iā€™d really appreciate a discussion prior to forking šŸ™‚

bbrinck 2018-10-26T18:56:07.085Z

I know ghostwheel does some analysis of which functions may be pure and tries to warn, but then you get into a whole other set of conventions and tooling that is likely way out of the scope of the discussion šŸ™‚

bbrinck 2018-10-26T18:56:42.086100Z

@slipset Will do. In all reality, I wonā€™t have time to do that anytime soon

bbrinck 2018-10-26T18:57:24.087400Z

And maybe, as you noted, there is space for multiple ns with different specs w different tradeoffs, only some of which will ever go into core

slipset 2018-10-26T18:57:34.087600Z

I mean, I havenā€™t at all decided on the priorities, and Iā€™d hope that we could share work. I really donā€™t care if the project is forked, itā€™s just a shame if work is duplicated for no reason.

slipset 2018-10-26T18:58:11.088700Z

The biggest upside for me wrt this project is the stuff that Iā€™ve learned and the people I work with.

bbrinck 2018-10-26T18:58:37.089200Z

@slipset Re: ifn?, I only mean that technically, that spec is not tight enough ā€¦ map doesnā€™t take any ifn?, it takes an ifn with certain conditions on params and return value

bbrinck 2018-10-26T18:59:25.090300Z

maybe reduce is a better example here actually ā€¦ the second arg to reduce has a certain signature, not just fn?

slipset 2018-10-26T18:59:39.090500Z

@bbrink true, there should be a way of checkin arities for ifns

bbrinck 2018-10-26T19:00:04.091300Z

yeah, youā€™d need to dive into fspec IIRC, but that uses test.check for validation, so more expensive

slipset 2018-10-26T19:01:58.092600Z

But as you said, validation for that was just insanely expensive

bbrinck 2018-10-26T19:03:11.094200Z

yep, that makes sense, test.check is generating examples there ā€¦ and most examples likely arenā€™t very useful there because any? will generate a lot of not useful data in this case

borkdude 2018-10-26T19:24:11.097600Z

Merged something that checks our :ret + :fn specs

alexmiller 2018-10-26T19:37:44.099300Z

Arg specs are describing syntax, which is an ideal case for regex spec. alt is the regex choice spec.

alexmiller 2018-10-26T19:40:20.100500Z

This is a long standing debate and there is a ticket for it

bbrinck 2018-10-26T19:45:17.100700Z

I havenā€™t looked at the specifics of this ticket, but in general for questions like this, I wonder if each project could make a decision and include the appropriate spec

bbrinck 2018-10-26T19:46:09.100900Z

as opposed to waiting for a single canonical spec for each case, which requires a lot of effort to get right across every core function

borkdude 2018-10-26T21:20:57.101600Z

The checking works pretty good. I didnā€™t manage to make

(check `= [0 0])
work. It gives:
ERROR in (=-test) (Error:NaN:NaN)
expected: (check (quote cljs.core/=) [1])
  actual: #error {:message "Call to #'cljs.core/= did not conform to spec:\nIn: [1] val: :cljs.spec.alpha/invalid fails at: [:args] predicate: any?\n:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha4961]\n:cljs.spec.alpha/value  ([1] :cljs.spec.alpha/invalid)\n:cljs.spec.alpha/args  ([1] :cljs.spec.alpha/invalid)\n:cljs.spec.alpha/failure  :instrument\n", :data {:cljs.spec.alpha/problems [{:path [:args], :pred cljs.core/any?, :val :cljs.spec.alpha/invalid, :via [], :in [1]}], :cljs.spec.alpha/spec #object[cljs.spec.alpha.t_cljs$spec$alpha4961], :cljs.spec.alpha/value ([1] :cljs.spec.alpha/invalid), :cljs.spec.alpha/args ([1] :cljs.spec.alpha/invalid), :cljs.spec.alpha/failure :instrument}}
WAT?

mfikes 2018-10-26T22:13:56.102800Z

Will check if https://dev.clojure.org/jira/browse/CLJS-2793 fixes ^ when back to computer

mfikes 2018-10-26T22:51:34.103300Z

I'm getting this with ClojureScript 1.10.339

cljs.user=> (check `= [0 0])
true