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-04T10:05:39.201400Z

Yet another question about spec. This time about the semantics of the sequence designators such as s/alt, s/+, s/* etc. an element such as (s/+ even?) means (if I understand correctly, a sequence of 1 or more objects for which the even? predicate is true. but I can substitute a spec name for even? and say (s/+ ::big-even) and that will be a sequence of 1 or more objects each of which validates the ::big-even spec. However, what does (s/+ (s/alt :x even? :y prime?)) mean. I would guess that it means a sequence of 1 or more elements each of which either satisfy even? or satisfy prime? But if I define

(s/def ::even-or-prime (s/alt :x even? :y prime?)) 
what does this mean? (s/+ ::even-or-prime) It is a sequence of 1 or more sequences (each of which contains elements which are either even or prime), or is it a sequence of 1 or more integers each of which is either even or prime?

Jim Newton 2020-11-04T10:06:38.202100Z

I'm pretty sure spec has a way to designate both, as they are both possible things the user might want to express. right?

borkdude 2020-11-04T10:07:46.202500Z

@jimka.issy s/alt is for regexes, use s/or for normal predicates

borkdude 2020-11-04T10:08:48.203Z

(s/+ (s/alt ....)) means a sequence of one or more alternatives

borkdude 2020-11-04T10:09:36.203500Z

it doesn't mean each, this can be expressed with s/every or s/coll

Jim Newton 2020-11-04T10:11:23.205200Z

Sorry, my question too convoluted. Let me ask simpler. If ::foo is a spec defined somewhere using s/def , what does (s/+ ::foo) mean? Does it mean a sequence of 1 or more objects which each validate ::foo ?

borkdude 2020-11-04T10:11:48.205500Z

yes

Jim Newton 2020-11-04T10:12:49.205800Z

So it is not referntially transparent?

Jim Newton 2020-11-04T10:13:07.206200Z

I.e. substituting the expression for ::foo in place, changes the meaning?

borkdude 2020-11-04T10:13:32.206600Z

I don't see why that would be the case?

Jim Newton 2020-11-04T10:17:21.209700Z

suppose

(s/def ::foo (s/alt :x even? :y prime?))
Now (s/+ (s/alt :x even? :y prime?)) matches a sequence of 1 or more integers each of which are either even or prime, right? but (s/+ ::foo) matches a sequence of 1 or more objects which each satisfy :foo , i.e., a sequence of sequences each of which contain even or prime integers.

borkdude 2020-11-04T10:20:34.210200Z

@jimka.issy Please give a working example. E.g.:

user=> (s/def ::foo (s/or :x even? :y neg?))
:user/foo
user=> (s/conform (s/+ ::foo) [2 4 -11])
[[:x 2] [:x 4] [:y -11]]
user=> (s/conform (s/+ (s/or :x even? :y neg?)) [2 4 -11])
[[:x 2] [:x 4] [:y -11]]

borkdude 2020-11-04T10:23:05.211Z

A spec is only applied to one thing, not to all elements of a collection, unless you use s/every or s/coll-of.

borkdude 2020-11-04T10:24:55.211700Z

user=> (s/def ::nums-or-strings (s/or :nums (s/every number?) :strings (s/every string?)))
:user/nums-or-strings
user=> (s/conform ::nums-or-strings [1 2 3])
[:nums [1 2 3]]
user=> (s/conform ::nums-or-strings ["foo" "bar"])
[:strings ["foo" "bar"]]

Jim Newton 2020-11-04T10:24:57.211800Z

yes, but you answer avoids my question. I'm not asking how to write a good spec. I'm asking about the semantics.

borkdude 2020-11-04T10:25:29.212400Z

And I asked you about a counter-example of referential transparency. A working one without imaginary functions like prime? which does not exist in core.

Jim Newton 2020-11-04T10:25:54.212700Z

Ok here is a very simple one.

vlaaad 2020-11-04T10:26:17.213600Z

I mean you don’t really need prime? source to know its semantics

Jim Newton 2020-11-04T10:26:28.213900Z

(s/+ ::foo) matches a sequence for which all the elements are either even or negative.

vlaaad 2020-11-04T10:26:55.214400Z

looks right

borkdude 2020-11-04T10:27:15.214900Z

Your words are ambiguous to me. Do you mean: every element is a ..., or on a case by case basis?

Jim Newton 2020-11-04T10:27:52.215600Z

lets stick with integers and sequences thereof.

Jim Newton 2020-11-04T10:28:35.216400Z

Now define

(s/def ::foo2 (s/+ ::foo))

Jim Newton 2020-11-04T10:29:31.217500Z

what does (s/+ ::foo2) mean. It does not mean a sequence of 1 or more objects which of which match ::foo2

borkdude 2020-11-04T10:30:00.218200Z

What does ::foo mean in your code here?

borkdude 2020-11-04T10:30:38.219600Z

some kind of integer that's either this or that?

Jim Newton 2020-11-04T10:30:54.220Z

(s/def ::foo (s/or :x even? :y neg?))

borkdude 2020-11-04T10:31:22.221400Z

then ::foo2 means multiple even or negs, but they can be mixed. it's not all or nothing

vlaaad 2020-11-04T10:31:23.221500Z

I would. say (s/+ ::foo2) is a seq of 1 or more objects each of which is is ::foo2, why would you say it isn’t that?

Jim Newton 2020-11-04T10:31:33.221900Z

Wait. I think I'm confused. Let me work out a more consistent example. And come back to you in 5 minutes. OK?

👍 1
borkdude 2020-11-04T10:31:39.222100Z

ok

Jim Newton 2020-11-04T10:35:28.222800Z

taking this conversation to a thread. I hope everyone follows.

Jim Newton 2020-11-04T10:37:36.222900Z

(s/def ::foo (s/or :x even? :y neg?))
(s/def ::foo2 (s/+ ::foo))

Jim Newton 2020-11-04T10:38:00.223100Z

Now ::foo matches even and negative integers

Jim Newton 2020-11-04T10:38:27.223300Z

(s/+ ::foo) matches a non-empty sequence of even and negative integers

vlaaad 2020-11-04T10:39:00.223500Z

sounds right

1
Jim Newton 2020-11-04T10:39:40.223900Z

but (s/+ ::foo2)does not match a non-empty sequence of objects which match ::foo2

vlaaad 2020-11-04T10:40:16.224200Z

ah, I think that’s because of how regex ops nest

Jim Newton 2020-11-04T10:40:54.224400Z

Jim Newton 2020-11-04T10:41:07.224800Z

yess!!!!!!!!

Jim Newton 2020-11-04T10:41:36.225Z

this was my original question (s/+ x) does not match a non empty sequence of things which match x

borkdude 2020-11-04T10:41:48.225200Z

@jimka.issy If you want to do this, you need to use s/spec around the ::foo2

➕ 1
borkdude 2020-11-04T10:42:09.225400Z

This has to do with regex nesting indeed, not with referential transparency. Read the spec guide, which explains this

borkdude 2020-11-04T10:42:59.225700Z

> When regex ops are combined, they describe a single sequence. If you need to spec a nested sequential collection, you must use an explicit call to spec to start a new nested regex context.

vlaaad 2020-11-04T10:43:08.225900Z

(s/valid? (s/+ (s/spec ::foo2)) [[2 -3 4] [2 -3 4] [2 -3 4]]) => true

Jim Newton 2020-11-04T10:43:53.226200Z

I have read down https://clojure.org/guides/spec#_sequences.

Jim Newton 2020-11-04T10:44:10.226400Z

Did I miss the paragraph about when regex ops are combined.

borkdude 2020-11-04T10:44:21.226600Z

I assume you know how Ctrl-f works ;)

Jim Newton 2020-11-04T10:44:23.226800Z

Besides, I'm confused about what "regex" operator means.

borkdude 2020-11-04T10:44:52.227Z

regex operators are things like s/+, s/?

Jim Newton 2020-11-04T10:44:57.227200Z

sometimes it means string regexps. and some times it means sequence operations.

borkdude 2020-11-04T10:45:25.227500Z

in the context of spec, regex means a spec which describes a sequence of things

borkdude 2020-11-04T10:46:39.227700Z

The idea of spec is based on this paper: http://matt.might.net/papers/might2011derivatives.pdf where that terminology comes from

Jim Newton 2020-11-04T10:47:45.227900Z

so does this mean that if a sequence operator is directly within another sequence operator it has this special modal/merging meaning. otherwise it has its normal meaning?

borkdude 2020-11-04T10:47:46.228100Z

(or maybe not that one, but the paper where that paper is based on)

borkdude 2020-11-04T10:48:13.228300Z

> When regex ops are combined, they describe a single sequence. If you need to spec a nested sequential collection, you must use an explicit call to spec to start a new nested regex context.

borkdude 2020-11-04T10:48:36.228500Z

s/alt returns a regex op, s/or is for combining predicates

borkdude 2020-11-04T10:48:46.228700Z

so s/or is not a regex op.

Jim Newton 2020-11-04T10:49:26.229Z

have you seen this paper? https://www.lrde.epita.fr/dload/papers/newton.18.meta.pdf

borkdude 2020-11-04T10:50:08.229200Z

I haven't

Jim Newton 2020-11-04T10:51:51.229400Z

and this one, where I introduced the derivate for implementing something quite similar to spec in common lisp https://www.lrde.epita.fr/dload/papers/newton.16.els.pdf

Jim Newton 2020-11-04T10:52:53.229600Z

the system I devised (in 2016) and after, is called RTE (regular type expressions).

borkdude 2020-11-04T10:53:18.229800Z

cool!

Jim Newton 2020-11-04T10:53:19.230Z

What i'm trying to do now is figure how similar or different it is from spec at a theoretical leve.

Jim Newton 2020-11-04T10:53:32.230200Z

So I'm trying to see if I can "compile" a spec into an RTE.

Jim Newton 2020-11-04T10:54:13.230400Z

if so, then an RTE can me validated in linear time with no backtracking.

Jim Newton 2020-11-04T10:54:36.230600Z

as I understand it, a spec takes exponential time worst case, so users avoid worst cases.

Jim Newton 2020-11-04T10:55:13.230800Z

however, since I have found no academic papers on spec, and I am not an expert of spec, I'm just guessing. Maybe I'm completely wrong about my supposition

borkdude 2020-11-04T10:55:15.231Z

@jimka.issy I'm interested in that for clj-kondo as well. Clj-kondo has a tiny type system in which I try to avoid backtracking of type checking arguments. Currently by just not allowing sophisticated constructs :)

Jim Newton 2020-11-04T10:55:45.231600Z

interesting*

Jim Newton 2020-11-04T10:56:43.231900Z

I submitted a paper recently to the Dynamic Languages Symposium where I introduced a Simple Type System for use in dynamic languages. The paper was rejected. One of the reasons for rejection was the revewers didn't see practical applications.

Jim Newton 2020-11-04T10:57:45.232100Z

a simple type system allows you to reason about types (where type is defined as any set of values) . Furthermore intersections, unions, and complements of types are also types.

Jim Newton 2020-11-04T10:58:19.232300Z

If your type logic can conclude that a type is empty, then you've usually found a bug in the user's code.

borkdude 2020-11-04T10:58:29.232500Z

It sounds like it could be very useful for clojure. Are you aware of the work done in core.typed?

Jim Newton 2020-11-04T10:58:42.232700Z

Thats Ambrose?

borkdude 2020-11-04T10:58:47.232900Z

yes

Jim Newton 2020-11-04T10:59:11.233100Z

He's looked at my work and is interested in helping out. but everyone is busy.

Jim Newton 2020-11-04T10:59:41.233300Z

I'd also love to have a student researcher interested in working with clojure.

borkdude 2020-11-04T10:59:44.233500Z

It looks like your work could help improve clj-kondo's type system since linear checking is necessary for performance

Jim Newton 2020-11-04T11:00:33.233700Z

what's clj-kondo written in? is it written in clojure?

borkdude 2020-11-04T11:00:40.233900Z

yes

Jim Newton 2020-11-04T11:01:02.234100Z

are you more of a hacker, or more of an academic?

Jim Newton 2020-11-04T11:01:23.234300Z

or more of an engineer? I don't mean that in any way insulting. sorry if it sounds as such....

borkdude 2020-11-04T11:01:29.234500Z

haha. I do have a CS master degree, but not a Phd in type systems ;)

borkdude 2020-11-04T11:02:24.234700Z

clj-kondo focuses on usefulness though, whereas something like core.typed is maybe more an academic project

borkdude 2020-11-04T11:02:35.234900Z

I don't care about publishing papers, I want the tool to help me

Jim Newton 2020-11-04T11:04:13.235100Z

I'm not sure why my next best step should be. For the moment I have an implementation of genus, (the simple type system in clojure) and RTE (regular type expressions) which allows genus to represent sets of sequences which match regular expressions in terms of types. Genus claims to be extensible. For the moment my experimental task is see if I can indeed extend genus (in user space) to incorporate the spec type. I.e., given a spec, the type will be the set of all objects which validate the spec.

Jim Newton 2020-11-04T11:05:33.235300Z

spec types will be treated as opaque types such as arbitrary predicate. except in the case where I can compile a spec into more fundamental types in which case the system can reason about them.

borkdude 2020-11-04T11:05:46.235500Z

Another tool I made recently also uses clojure.spec for searching code: https://github.com/borkdude/grasp This could potentially made faster Another project which tries to use spec at compile time: https://github.com/arohner/spectrum It's experimental in nature.

Jim Newton 2020-11-04T11:06:10.236Z

for example it will be able to decide whether the set of objects satisfying spec1 and also spec2 is empty. or decide whether two given specs are equivelent.

Jim Newton 2020-11-04T11:07:53.236200Z

in order for RTE to represent the regular type expression as a deterministic finite automaton, it needs to be able to determine whether two given types are disjoint. This is currently not possible with spec in the most general case, but could be possible in many particular cases.

Jim Newton 2020-11-04T11:08:50.236400Z

As I understand spec's internal data structure uses non-deterministic finite automata, thus the exponential behavior in the worst case.

Jim Newton 2020-11-04T11:09:42.236600Z

ANYWAY, maybe now you understand a bit better the reason for my strange beginner questions about the semantics of spec ???

borkdude 2020-11-04T11:09:54.236800Z

yes, thanks for explaining!

borkdude 2020-11-04T11:10:26.237Z

The authors of spec usually emphasise that spec is not a replacement for a type system, but a runtime validation system.

Jim Newton 2020-11-04T11:10:43.237200Z

in my opinion there is no difference

borkdude 2020-11-04T11:10:51.237400Z

What might be of interest, I have a collection of specs for core functions here: https://github.com/borkdude/speculative

borkdude 2020-11-04T11:11:18.237800Z

As in, spec could be expressed as a case of dependent typing?

Jim Newton 2020-11-04T11:12:54.238Z

what is speculative? I didn't immediately understand by reading the intro.

borkdude 2020-11-04T11:13:12.238200Z

> a collection of specs for core functions

Jim Newton 2020-11-04T11:13:40.238400Z

For me a type is a set of values. And a type system allows programmers to designate and reason about certain types.

Jim Newton 2020-11-04T11:13:44.238600Z

that's what spec does.

borkdude 2020-11-04T11:14:42.239300Z

yes, if you define it like that, it makes sense. but this set of values can often not be known at compile time, which makes the difference between static compile time checking

Jim Newton 2020-11-04T11:15:45.239500Z

what is s/fdef ?

borkdude 2020-11-04T11:16:54.239700Z

clojure.spec supports defining specs for checking function arguments and return values using fdef

Jim Newton 2020-11-04T11:17:39.239900Z

yes many times you can indeed know a lot about a type at compile time. in those cases, compilers like the SBCL common lisp compiler can create more efficient code, or tell the user about errors or unreachable code.

Jim Newton 2020-11-04T11:18:17.240100Z

so an fdef decribes what a valid function call site looks like?

borkdude 2020-11-04T11:18:40.240400Z

yes

Jim Newton 2020-11-04T11:19:45.240700Z

I can see how the predicate based assumption of spec, could be a bottle neck for a code analyzer like kondo.

borkdude 2020-11-04T11:20:15.240900Z

I initially used spec in clj-kondo, but I found the performance not good enough

Jim Newton 2020-11-04T11:20:42.241100Z

one think that I've noticed is that many many many of these predicates can be rewritten as a simple type check. And I have code which automates this.

Jim Newton 2020-11-04T11:21:02.241300Z

ahhh, so you have abandoned that piste now?

borkdude 2020-11-04T11:22:04.241500Z

the format now uses something more explicit: you provide the seq of types per arity. so you don't have to do any backtracking to to match a seq based on some s/alt expression. which makes more sense to me considering the structure of multi-arity functions.

borkdude 2020-11-04T11:22:31.241700Z

and a seq of types is usually a fixed number of things or a fixed number of thing + a variable amount of things of the same type

borkdude 2020-11-04T11:22:46.241900Z

I think this matches 99% of cases how people call functions in clojure

Jim Newton 2020-11-04T11:23:06.242200Z

for example

(gns/canonicalize-type '(satisfies int?))
  ==> (or Long Integer Short Byte)
(or ...) is a union type. when intersected and unioned with other types, it can be arranged so that redundant checks are eliminated.

borkdude 2020-11-04T11:23:37.242400Z

clj-kondo also supports union types, it's just #{:int :string}

borkdude 2020-11-04T11:24:12.242600Z

it also tries to infer returns types and threads types through let expressions

borkdude 2020-11-04T11:24:58.242800Z

Example:

Jim Newton 2020-11-04T11:25:02.243200Z

so it has type inferencing?

borkdude 2020-11-04T11:25:43.243400Z

It has inferencing yes, but limited to the things it knows, else it's any? and it won't complain

Jim Newton 2020-11-04T11:26:05.243600Z

I wrote something pretty similar some years ago for SKILL++ which was a proprietary lisp used by the company I worked for for many years.

Jim Newton 2020-11-04T11:28:50.243800Z

in my system, called "loathing" it would see that foo returns type string (because of the definition), and the call site inc expects an integer. So the type at that point is (and integer string) which is the empty-type.

borkdude 2020-11-04T11:29:47.244Z

clj-kondo does something similar. but it just has a simple graph of things that are reachable or not: you can never go from string to int, so this is an error

borkdude 2020-11-04T11:30:09.244200Z

but you can go from any to int, or any to string, so this is not an error

Jim Newton 2020-11-04T11:30:16.244400Z

however, if inc had input type (or integer string), then the intersection would be (and string (or string integer)) = string != empty-set

borkdude 2020-11-04T11:30:31.244600Z

yep, same thing in kondo

borkdude 2020-11-04T11:31:37.244800Z

e.g.: this will not give a warning:

(defn foo [x]
  (if (int? x)
    x
    (str x)))

(inc (foo 1))

Jim Newton 2020-11-04T11:32:11.245200Z

You mentioned that you are not interested in publications. But I need to make publications. It's part of my job.

borkdude 2020-11-04T11:32:29.245400Z

Well, I meant that this is not the goal of clj-kondo.

Jim Newton 2020-11-04T11:32:36.245600Z

I need to get several publications out of this work.

borkdude 2020-11-04T11:32:39.245800Z

It's not the product of academic research

borkdude 2020-11-04T11:32:51.246Z

I have nothing against making publications

borkdude 2020-11-04T11:33:05.246200Z

I do have my name on one publication ;)

Jim Newton 2020-11-04T11:33:33.246400Z

and I think there are several interesting results to publish. But i'm a very poor publicist. I don't do well at convincing people of the interest or novelty of my work.

borkdude 2020-11-04T11:34:11.246600Z

If you could get practical benefit out of your work by e.g. improving clj-kondo or a similar tool, I'm sure it will convince people

borkdude 2020-11-04T11:35:32.246800Z

@jimka.issy I'm sharing this for fun, but it's also related: https://borkdude.github.io/re-find.web/ Find functions based on example in and outputs

borkdude 2020-11-04T11:35:46.247Z

This uses the speculative specs to search for matches

Jim Newton 2020-11-04T11:37:01.247200Z

the clojure community is friendly for the most part. although I do see some snide comments now and then.

Jim Newton 2020-11-04T11:37:11.247400Z

So it is usally pretty easy to get questions answered.

Jim Newton 2020-11-04T11:37:40.247600Z

I would love to have some spec examples for test cases.

borkdude 2020-11-04T11:37:44.247800Z

I would say the clojure community is better in that respect than some others

Jim Newton 2020-11-04T11:38:27.248Z

Could I solicit you to give me some spec examples, simple and complex. some which are trivial, and some which are very slow to validate?

Jim Newton 2020-11-04T11:38:53.248200Z

For the moment I'm just worried about sequences, not functions or maps or data structures. My code is not advanced enough to handle those.

Jim Newton 2020-11-04T11:39:20.248400Z

I want to try to compile these to RTE and see if I can come up with some cases where RTE is faster.

Jim Newton 2020-11-04T11:39:35.248600Z

if RTE is never faster, then i'm in trouble

borkdude 2020-11-04T11:39:37.248800Z

You could take a look at speculative, which has more than hundred specs

borkdude 2020-11-04T11:40:59.249Z

I don't have a particular example of a slow spec, but I guess you can fabricate one based one which does backtracking

Jim Newton 2020-11-04T11:41:18.249300Z

when analyzing code statically, i suppose you are usually looking at nested sequences, not really at maps and such.

borkdude 2020-11-04T11:41:58.249500Z

This tool: https://github.com/borkdude/grasp searches through code. It applies the spec at every sub-tree of an s-expression

borkdude 2020-11-04T11:42:09.249800Z

So performance improvements would be really noticeable there

borkdude 2020-11-04T11:42:49.250200Z

I have to go now. speak to you later

Jim Newton 2020-11-04T11:45:20.250400Z

ok, thanks. talk later

Jim Newton 2020-11-04T11:45:40.250600Z

BTW i filed an issue for kondo, was it helpful?

Jim Newton 2020-11-04T11:47:42.251600Z

another spec question. Are all spec definitions global? If I'm writing test cases, I'd love to introduce local definitions that don't pollute the global space.

borkdude 2020-11-04T11:48:05.251700Z

Yes, thank you. I'm not sure if clj-kondo will be able to support this, as it sees one file as a standalone unit and in-ns doesn't really match with this model. So either using in-ns with :refer on the original namespace requires you to help clj-kondo with some annotations or config, or clj-kondo has to make non-trivial changes.

borkdude 2020-11-04T11:48:44.251900Z

I will think about this for a while

Jim Newton 2020-11-04T11:49:13.252100Z

gocha

borkdude 2020-11-04T11:54:25.253200Z

@jimka.issy Yes, spec has one global registry (compare spec keywords to RDF uris for example). Malli (#malli) which aims to be an alternative schema library has local registries as well.

Jim Newton 2020-11-04T11:55:48.253900Z

I suppose for test cases I can use ::xyzzy to keep the symbols in my local namespace.

Jim Newton 2020-11-04T11:59:00.255100Z

Do I understand correctly that there is no intersection equivalent of s/alt for regex semantics. I.e., s/or is to s/alt as s/and is to what?

borkdude 2020-11-04T11:59:41.255300Z

s/&

Jim Newton 2020-11-04T12:05:55.255700Z

ahh, it's not included in https://clojure.org/guides/spec#_sequences, at least not in the table with the others

Jim Newton 2020-11-04T12:08:25.256900Z

syntactically does s/& work like s/alt and s/cat in that I need to tag the components? or like s/and where no tags are used?

borkdude 2020-11-04T12:09:27.257200Z

@jimka.issy In the guide, search for: > Spec also defines one additional regex operator, &, which takes a regex operator and constrains it with one or more additional predicates. to see an example

Jim Newton 2020-11-04T12:19:07.258Z

ahh so s/& is not really analogous to s/alt it is yet a third syntax and semantic.

borkdude 2020-11-04T12:19:48.258400Z

I thought you were asking analogous to s/and. > I.e., s/or is to s/alt as s/and is to what?

borkdude 2020-11-04T12:25:59.263400Z

s/alt and s/& are supposed to be used to build regexes, s/and and s/or are just general ways of combining predicates

borkdude 2020-11-04T12:26:06.263700Z

but sometimes they do the same thing

Jim Newton 2020-11-04T12:26:42.264100Z

It looks (from the documentation) that s/& is not analgous to s/and in the same way that s/alt is analogous to s/or. if they were analogous I'd expect s/& to work like this.

(s/* (s/alt :x (s/cat :a neg? :b even?) :y (s/cat :c odd? :d pos?)))
(s/* (s/& (s/cat :a neg? :b even?) (s/cat :c odd? :d pos)))
but instead it works like this
(s/* (s/& (s/cat :a neg? :b even?) #(even? (count %))))

borkdude 2020-11-04T12:27:18.264600Z

See the difference here:

user=> (s/conform (s/alt :foo int? :bar string?) [1])
[:foo 1]

user=> (s/conform (s/or :foo int? :bar string?) [1])
:clojure.spec.alpha/invalid
user=> (s/conform (s/or :foo int? :bar string?) 1)
[:foo 1]

Jim Newton 2020-11-04T12:29:06.265200Z

yes s/or matches an object, and s/alt matches a sequence of objects

Jim Newton 2020-11-04T12:35:16.267Z

So why doesn't this return true?

(s/valid? (s/* (s/&  (s/cat :a neg? :b even?)  
                     (s/cat :c odd? :d pos?))) 
          [-3 4 -5 2])
it is both a sequence of neg even occurring 0 or more times, and also a sequence of odd pos occuring 0 or more times

Jim Newton 2020-11-04T12:36:05.267600Z

If I replace s/& with s/alt (and insert the required keys), it works as expected.

(s/valid? (s/* (s/alt :x  (s/cat :a neg? :b even?)  
                      :y  (s/cat :c odd? :d pos?))) 
          [-3 4 -5 2])

Jim Newton 2020-11-04T12:49:41.271500Z

It looks to me (on first and second reading of the specification) that (s/& pattern predicate) means that the subsequence which matches the pattern must as a sequence match the predicate. So the following

(s/cat :a (s/& (s/* string?) #(even? (count %)))
       :b (s/& (s/+ int?)    #(odd? (count %))))
matches a sequence which begins with an even number of strings and ends with an odd number of integers. Is my interpretation correct? I don't think these are really regular expressions any more. I have to think about it, but my suspicion is that this cannot be solved without using a stack.

Jim Newton 2020-11-04T13:35:44.271700Z

I don't think s/& is a regular expression operation. It doesn't seem regular. as far as I can tell, it requires the implementation to remember the sequence being tested. a regular expression demands that every decision be based only one the current object of the sequence, and a state. whereas only a finite number of states are allowed. In this case if there are N states, you cannot test a sequence of length N+1.

Jim Newton 2020-11-04T14:00:50.273Z

How can I recognize an object such as the one returned by

(s/* (s/alt :x  (s/cat :a neg? :b even?)  
            :y  (s/cat :c odd? :d pos?)))
s/spec? returns false . type returns clojure.lang.PersistentArrayMap . s/get-spec returns nil.

alexmiller 2020-11-04T14:13:02.274500Z

isn’t there a regex?

Jim Newton 2020-11-04T14:15:53.275200Z

so regex? returns non-nil. is that one I should use?

alexmiller 2020-11-04T14:16:03.275400Z

Yeah

Jim Newton 2020-11-04T14:18:04.276Z

seems to be any object for which ::op returns boolean true.

Jim Newton 2020-11-04T14:19:09.276400Z

where ::op is :clojure-spec-alpha/op

Jim Newton 2020-11-04T14:19:25.276600Z

fair enough