speculative

https://github.com/borkdude/speculative
borkdude 2019-01-11T08:59:09.068600Z

a lot of functions have the following args spec:

(s/def ::filter-fn-args
  (s/alt :transducer (s/cat :f ::ss/ifn)
         :seq (s/cat :f ::ss/ifn :coll ::ss/seqable)))
I’m not happy with the name, who knows a better one?

borkdude 2019-01-11T08:59:49.069200Z

This spec is used e.g. for filter , keep, remove and partition-by

flowthing 2019-01-11T09:33:33.069400Z

filterable? 😛

flowthing 2019-01-11T09:33:56.069700Z

Ah, no, misread the spec.

borkdude 2019-01-11T09:34:34.070Z

the idea is you walk over some collection with a fn

borkdude 2019-01-11T09:34:56.070500Z

and the function can return a transducer or seq

alexmiller 2019-01-11T13:29:24.070900Z

I think “pred” is a more meaningful name than “f”

borkdude 2019-01-11T14:35:55.071300Z

yeah, I usually follow the names of the arguments of the actual function that’s being spec’ed

borkdude 2019-01-11T14:39:48.072200Z

I’m actually looking for a better name for ::filter-fn-args which is not good if you want to re-use this for keep, remove and partition-by. Maybe I should just inline it.

slipset 2019-01-11T15:07:15.072800Z

functor-transducer-args

slipset 2019-01-11T15:07:16.073Z

?

borkdude 2019-01-11T15:14:26.074200Z

that might be close.. but a functor cannot transform the outer data structure, which is what filter (leaving out things), keep and partition-by do

borkdude 2019-01-11T15:16:08.074700Z

the common things in these is that a function is applied to all elements in a sequence (the elements themselves may be transformed, or not)

alexmiller 2019-01-11T15:16:44.075100Z

bleh

borkdude 2019-01-11T15:17:28.075300Z

ok, ::bleh

borkdude 2019-01-11T15:17:36.075500Z

🙂

borkdude 2019-01-11T15:18:02.075900Z

::fn-over-seq … ooor just inline and not re-use it

alexmiller 2019-01-11T15:18:30.076400Z

while these functions all have the same signature, you are detecting repetition, not abstraction

borkdude 2019-01-11T15:18:50.077Z

good point

alexmiller 2019-01-11T15:19:05.077300Z

there is nothing to say that one of these functions won’t gain an arity or something in the future

alexmiller 2019-01-11T15:19:24.077900Z

repetition is not always a bad thing imho

borkdude 2019-01-11T15:20:00.078600Z

ok, you confirmed my inclination to inline it

alexmiller 2019-01-11T15:20:11.078900Z

and to some degree I think there is clarity to just having them each be separate but the same

borkdude 2019-01-11T15:20:40.079100Z

💡

borkdude 2019-01-11T15:29:35.079900Z

@slipset I tried to look up the “category theory” term for filter that works on more than just a list, and now I finally get what MonadPlus is about 😄, thanks https://stackoverflow.com/a/3011500/6264

borkdude 2019-01-11T15:31:00.080500Z

(well sort of, but now I have a clue)

borkdude 2019-01-11T15:51:25.081900Z

interesting remark in #clojure just now by @alexmiller > expectation for sequence functions should be that take something seqable and return something seqable there was a change recently in speculative which spec’ed the return value of some sequence functions more strictly: https://github.com/borkdude/speculative/commit/fcadecebb411cfa18af360991b2e4a6c1d8468b2 should we revert it?

borkdude 2019-01-11T15:53:32.082200Z

(the fn specs were restored in a later commit)

alexmiller 2019-01-11T15:55:42.082700Z

next/rest are a little different case

alexmiller 2019-01-11T15:56:15.083200Z

and I think their returns are constrained by ISeq

alexmiller 2019-01-11T15:56:57.083600Z

but on the seq-or-transducer, I think that should be seqable

borkdude 2019-01-11T15:58:53.083800Z

because you can not rely on map giving back a seq?

alexmiller 2019-01-11T15:59:12.084200Z

this is not about rely on, but about commitment

borkdude 2019-01-11T15:59:27.084500Z

what’s the difference?

alexmiller 2019-01-11T15:59:39.084800Z

and seqable is a less constrained commitment (regardless of whatever it actually does now)

alexmiller 2019-01-11T15:59:51.085100Z

less constrained, but sufficient

borkdude 2019-01-11T16:00:05.085500Z

right. for optimization it might return a vector or whatever, you never know right

alexmiller 2019-01-11T16:00:54.086500Z

there are some subtle cases where being able to return something seqable, means you don’t have to force the creation of a seq object, and in some cases the perf difference of this is important

borkdude 2019-01-11T16:02:24.086900Z

ok, good to know. I think I’ll document this in the guidelines for future specs

borkdude 2019-01-11T16:23:24.087300Z

btw @alexmiller do you know such an example from the top of your head?

alexmiller 2019-01-11T16:24:12.087900Z

I ran into it when I was doing the 1.7 transducers work on range, repeat, cycle, iterate, etc but I don’t remember the specific case

2019-01-11T16:30:20.092100Z

I can see arguments that map, filter should commit to seqable - But from my view they have a distinct "return-type" compared to take-last. Probably wrong but... map can't return nil and I would think that is true for all "transducable"(?) functions. take-last on the other hand can return nil.

2019-01-11T16:34:00.092900Z

soo... (s/and seqable? some?)?

alexmiller 2019-01-11T16:37:27.093400Z

seqable? includes nil

alexmiller 2019-01-11T16:38:36.094300Z

if map returned nil I wouldn’t say it was wrong

2019-01-11T16:39:54.095Z

Hmm... Can't say I see how it would return it

alexmiller 2019-01-11T16:40:10.095300Z

I’m not saying it does now

alexmiller 2019-01-11T16:40:38.096Z

I’m saying conceptually, if it did, I think that would be acceptable

borkdude 2019-01-11T16:45:59.098100Z

As in, your code should not break if it did.

borkdude 2019-01-11T16:46:55.099Z

Although the docstring says that it returns a lazy-seq. Not all docstrings cover the types very precisely though, so that’s not something to go by 100% of the time.

2019-01-11T16:49:53.100900Z

Pipeline with map functions. Last function in pipe does a (when % (println "still something"). Probably bad structure by me, but I often check for something.

alexmiller 2019-01-11T16:51:58.101300Z

this is actually the place where you should use seq

alexmiller 2019-01-11T16:52:12.101700Z

(when (seq %) (println ...))

alexmiller 2019-01-11T16:52:29.102Z

that is bad code when working with sequences

alexmiller 2019-01-11T16:52:41.102300Z

ok if working with collections

2019-01-11T16:53:14.102700Z

because map cannot be expected to return something?

2019-01-11T16:54:14.103700Z

It might be general code, perhaps I don't even know if it is a sequence or not?

borkdude 2019-01-11T16:55:44.104300Z

you were talking about a pipeline with map functions. that should at least return a seqable?.

borkdude 2019-01-11T16:56:00.104600Z

(assuming no Exceptions happened)

2019-01-11T17:01:43.108400Z

Say that we have heterogeneous collection. I do some kind of transformation of all elements. (map my-multi-method coll) int -> #(* % %) seqable -> #(map inc %)

borkdude 2019-01-11T17:02:46.108700Z

can you give a full example? I have trouble understanding this one

borkdude 2019-01-11T17:08:46.109200Z

ok and now your point?

2019-01-11T17:11:28.111800Z

I might want do checks later if (when (nth x coll) (do-something)). But I have no idea if this is a "real" nil or not

borkdude 2019-01-11T17:12:04.112300Z

what is the goal of the check?

borkdude 2019-01-11T17:14:48.113900Z

I mean, what is your definition of a “real nil”

alexmiller 2019-01-11T17:16:04.115300Z

In that gist you posted, I would expect you would want to return a vector, not the result of map

alexmiller 2019-01-11T17:16:52.116200Z

So I would use mapv or ‘into’ to []

alexmiller 2019-01-11T17:17:08.116500Z

Or vec

borkdude 2019-01-11T17:19:28.116900Z

vector -> vector makes sense

borkdude 2019-01-11T17:25:19.119700Z

@alexmiller is your point that empty vectors are less often interchanged with nil than empty sequences?

2019-01-11T17:32:15.120200Z

Perhaps I have too strict(wrong) of a view regarding what kind of thing map computes over.

2019-01-11T17:33:13.120500Z

still I find this interesting: https://clojure.org/reference/lazy

2019-01-11T17:33:29.120900Z

Changed: Sequence fns (map, filter etc) return seqs, but not nil You’ll need to call seq on their return value in order to get a seq/nil seq also serves as test for end, already idiomatic (when (seq coll) ...) allows full laziness doesn’t support nil punning since sequence fns no longer return seq/nil

2019-01-11T17:34:50.121700Z

but it is not a reference doc though

borkdude 2019-01-11T17:35:22.122100Z

🖕

2019-01-11T17:36:34.123600Z

also it is about what changed, not about the future. So your point still stands

borkdude 2019-01-11T17:37:26.124500Z

I think we should stick to the general contract of seq functions which is seqable in and out. Like, you’re also not going to cast the return value of something which implements IFoo because you know it’s a Bar under the hood?

borkdude 2019-01-11T17:38:18.125100Z

but it would be better if we had this commitment in a reference doc somewhere, so we could point to it

borkdude 2019-01-11T17:42:19.125300Z

does it exist somewhere?

alexmiller 2019-01-11T17:48:38.126100Z

nil and empty vector are different things

alexmiller 2019-01-11T17:49:35.127400Z

Esp in a use case where you are using positional vectors like this one

alexmiller 2019-01-11T17:49:53.128100Z

But with sequences nil “puns” as an empty sequence

2019-01-11T18:41:17.135900Z

I am interested in knowing a counterexample. What kind of code are we writing today that is hindered by disallowing nil? Wouldn't that just be a conditional (if (map..)) that have no chance of executing the later branch? Then when clojure possibly change in the future, that branch might start executing. The removal of nil-punning as described was a breaking change. That breaking change should probably also be reflected in eventual specs.

borkdude 2019-01-11T18:43:17.137Z

My reason for reverting the change is that we don’t want to spec implementation details. () or nil as a return value from a sequence function is an implementation detail, not part of the contract

2019-01-11T18:50:43.141600Z

But it is something that changed for users when writing programs. Code that used to do (if (map ...)) stopped working when the change was made. We shouldn't write program today expecting nil as return, at best they will break in the future. A lot of things are implementation details.

2019-01-11T18:51:44.142700Z

map stopped returning nil by design and would have broken all code dependent on that behaviour

borkdude 2019-01-11T18:52:32.143200Z

Are you referring to the historic document that is not reference documentation?

2019-01-11T18:52:38.143400Z

yes

borkdude 2019-01-11T18:53:31.144200Z

today the contract for sequence functions is seqable? to seqable?, whether map returns () or nil is not something you should be thinking about

borkdude 2019-01-11T18:54:45.145400Z

any opinions on this @mfikes or @slipset?

2019-01-11T18:55:45.146500Z

(if (map a-fn coll) (do-something) (do-else))

mfikes 2019-01-11T18:56:29.147400Z

Of the top of my head, I’ve thought map returns (s/nilable seq?)

2019-01-11T18:57:01.148Z

That is correct, it is part of a contract though? - yeah minus nil

mfikes 2019-01-11T18:57:19.148800Z

Actually, perhaps nix the nilable part

borkdude 2019-01-11T18:57:35.149300Z

that’s the question. also: should we consider different return specs for different seq functions, or treat them under the same contract

mfikes 2019-01-11T18:58:58.150900Z

Is the rough idea seqable? in, seq? out?

borkdude 2019-01-11T18:59:01.151100Z

@mfikes given that () or nil may be treated as the same value (punning), the question is: is this important enough to spec differently

mfikes 2019-01-11T18:59:33.151700Z

Trying to think of things that might return nil

borkdude 2019-01-11T18:59:34.151800Z

@mfikes start reading here: https://clojurians.slack.com/archives/CDJGJ3QVA/p1547221885081900

2019-01-11T19:00:30.153100Z

map, remove, filter can't return nil - I think it is a bug to write code that relies on them returning nil. If that changes in the future, so will your execution

borkdude 2019-01-11T19:01:32.153900Z

you are turning things around: you should not rely on them to return () instead of nil, that should be un-important

borkdude 2019-01-11T19:01:49.154200Z

since empty seqs can be nil-punned

borkdude 2019-01-11T19:02:16.154700Z

so you should not rely on them returning a more specific value than seqable?

2019-01-11T19:02:57.155100Z

there are no such things as empty seqs

2019-01-11T19:03:05.155300Z

I think

borkdude 2019-01-11T19:03:09.155500Z

’()

2019-01-11T19:05:25.155800Z

isn't that an emptylist?

borkdude 2019-01-11T19:06:02.156700Z

(seq? '()) ;; true

2019-01-11T19:06:38.157700Z

anyhow, I am attacking this mostly from ide tooling angle. I would like to be able to mark code doing (if) on the return of a map as incorrect

borkdude 2019-01-11T19:07:30.158700Z

Another example where this is relevant:

(re-seq #"\w+" "")
According to the docs it should return a lazy seq. It returns nil. This should not be regarded as important, since nil and () can be used interchangeably.

borkdude 2019-01-11T19:08:47.159700Z

@andreas862 as @alexmiller pointed out, you should always call seq on a sequence if used as a truthy value

2019-01-11T19:09:35.159900Z

Sure, but that is why my tooling should mark the bare if-statement

2019-01-11T19:11:02.160100Z

Yes, but what if that changes in the next version of Clojure, so that nil isn't possible anymore? Nil punning on that stops working. That should probably have an effect on the spec then

borkdude 2019-01-11T19:11:33.160300Z

You are again turning it around. You should never nil pun on a result of a sequence function…

2019-01-11T19:12:07.160500Z

Yes, and we can have tools that helps with enforcing that

2019-01-11T19:12:50.160700Z

Or...

2019-01-11T19:12:52.160900Z

hm

borkdude 2019-01-11T19:12:55.161100Z

Sure, but those tools should be able to leverage seqable? and nothing more than that

2019-01-11T19:15:52.161300Z

is (when (re-seq ...)) a bug?

borkdude 2019-01-11T19:16:04.161600Z

yes

borkdude 2019-01-11T19:16:50.161800Z

depends. you should call seq on it if you want to confirm if it’s non-emty

borkdude 2019-01-11T19:17:18.162Z

it’s irrelevant if it returns nil or an empty seq when it didn’t find anything

2019-01-11T19:18:33.162200Z

interesting, got any example for re-seq returning an empty-seq instead of a nil?

borkdude 2019-01-11T19:19:58.162400Z

that should not matter

borkdude 2019-01-11T19:20:19.162600Z

you’re not supposed to look at the implementation and then figure out that it’s never going to

borkdude 2019-01-11T19:36:35.164500Z

Another example where you shouldn’t rely on seq? “Returns a sequence of values”

(vals nil) ;;=> nil
(vals {}) ;;=> nil
Same for keys.

2019-01-11T19:40:04.166600Z

My argument has always been about the ones that I classified as not nil-punnable. map, filter, remove etc. I did know about some others returning nil

2019-01-11T19:40:32.167200Z

But have to say that I do probably use some nil return directly

2019-01-11T19:40:39.167500Z

have a lot of code to fix

2019-01-11T19:41:17.167800Z

probably re-seq, not sure about the rest

borkdude 2019-01-11T19:43:07.168300Z

you were depending on re-seq being able to return nil, despite the docstring?

borkdude 2019-01-11T19:43:44.169100Z

I think in that case you should use re-find

borkdude 2019-01-11T19:43:56.169600Z

if you want to check if there is at least one match

2019-01-11T19:45:05.170600Z

not that sure about re-seq, have to check. But there is a big risk that I would use one of them if I found that it seemed to leave nil for empty.

borkdude 2019-01-11T20:41:09.174Z

We already discussed this in the very start of speculative: https://github.com/borkdude/speculative/issues/45 That’s why we made it seqable? in the first place. Sorry I forgot about this @andreas862. Should have remembered when I merged the change.

2019-01-11T20:43:12.175700Z

a non-nil seqable isn't invalidated by that issue.

2019-01-11T20:43:23.176Z

seq? goes out the door though

borkdude 2019-01-11T21:19:52.177400Z

For now I reverted back to #45. Any changes to this we should discuss in that issue or at least keep the most important notes there, so we can fall back on that.