Fork me on GitHub
#clojure
<
2019-10-09
>
Alex Miller (Clojure team)00:10:06

s/nilable expects a spec

danieroux07:10:53

Please help me track down this. My :dev alias has :jvm-opts ["-XX:-OmitStackTraceInFastThrow" "--illegal-access=deny"] and I can verify that it is in effect with ps -ef | grep clojure.main | grep -- -XX:-OmitStackTraceInFastThrow | wc -l returning 1.

yuhan08:10:56

Hi, I noticed a small inconsistency in how the regular clojure reader and the syntax-quote reader treats map literals:

(type {:a 1})
;; => clojure.lang.PersistentArrayMap

(type '{:a 1})
;; => clojure.lang.PersistentArrayMap

(type `{:a 1})
;; => clojure.lang.PersistentHashMap

noisesmith17:10:12

code that cares about the difference between HashMap and ArrayMap is broken, IMHO

noisesmith17:10:53

you can use an insertion ordered map type if you need that property, ArrayMap will return a HashMap when you conj if it hits the size limit

yuhan18:10:24

Oh, I know it's an implementation detail and I'm not relying on it in any way - just wondering what this means for performance of syntax quoted maps in macro-generated code, because ArrayMaps are more efficient for smaller sizes

yuhan08:10:50

macroexpanding shows that syntax-quote always uses hash-map regardless of the number of elements, which it probably can't know at compile time due to unquote splicing

yuhan08:10:04

(macroexpand '`{:a 1})
;; => (clojure.core/apply
;;     clojure.core/hash-map
;;     (clojure.core/seq
;;      (clojure.core/concat (clojure.core/list :a) (clojure.core/list 1))))

yuhan08:10:37

also weird:

(type (hash-map))
;; => clojure.lang.PersistentArrayMap

yuhan08:10:57

is this worth filing a JIRA about or looking into?

reefersleep09:10:24

Is there such a thing as a hook for automatically indenting Clojure code upon merging (or somewhere in the process of PR reviewing) for github?

reefersleep09:10:37

Or in a github-unrelated project?

reefersleep09:10:03

We’re tired of indentation differences filling up PRs in our team, so we’d like to standardize

Alexander Heldt10:10:10

is there no way to lint on commit in clojure?

Alexander Heldt10:10:47

i prefer commit-hooks to force linting (with the obvious drawback that it might hinder something thats needs to be done ASAP) since it give people the chance to learn what the set standard is

dominicm10:10:53

Configuring the editors to behave the same is an arduous task

dominicm10:10:00

In some cases it isn't possible

danieroux10:10:34

Have a look at https://tonsky.me/blog/clojurefmt/ Which solves the problem differently, but we've been using it with great effect. Cursive does a few things differently from what I would expect (alignment in let blocks), and otherwise it works

noisesmith17:10:52

there's also cljfmt - I've used that as a pass/fail hook on PRs

dominicm09:10:43

zprint is the only formatter I know which attempts to do this for clojure

andrea.crotti10:10:21

lein cljfmt fix doesn't do the same kind of thing @dominicm?

dominicm10:10:13

@andrea.crotti Cljfmt is very minimalistic. For example it won't "fix" (because I hate this) [a. 10\n long-key. 4].

dominicm10:10:35

There's still a lot of differences in a project when you use cljfmt

dominicm10:10:43

It catches common things though.

andrea.crotti10:10:33

ah right zprint runs a lot faster though since it's using graalvm

reefersleep10:10:32

@dominicm @andrea.crotti @me1243 @danie cheers. Do you guys know of any work to hook up zprint or cljfmt to some central process, like github PRs?

reefersleep10:10:08

Or do you have any experience with it directly?

andrea.crotti10:10:54

@reefersleep we just have a job in CircleCI to fail the build if lein cljfmt check fails

reefersleep10:10:00

What’s your experience with this setup? Does everyone like it, or is it ruled by a majority (or a lead)?

reefersleep10:10:26

And what does a fail mean - does it mean that you need to run some auto-formatting process on your branch manually and amend your PR?

andrea.crotti11:10:23

yeah you just need to run lein cljfmt fix locally and commit/push

andrea.crotti11:10:45

well some people complained in the beginning, but if most people are using Emacs or Cursive, it's quite rare that it fails tbh

andrea.crotti11:10:11

as a pre-commit hook maybe lein cljfmt fix is too slow, but zprint might be fine

reefersleep12:10:54

Cool! When you introduced this, did you have one commit that did lein cljfmt fix on all of your existing code, to establish a baseline?

reefersleep12:10:23

we have stability problems with our PR checks at the moment, so adding another, and just for indentation, would annoy people greatly.

reefersleep12:10:40

Therefore it’d probably be nice to have as a commit hook as well.

dominicm10:10:57

I don't think you need anything zprint specific here. There's probably github hooks or services. Commonly people have a pre commit hook to run it automatically. That runs on people's machines.

mpenet10:10:06

anyone using zprint in a pre-commit hook, or basically as a gofmt like formatter for teams?

mpenet10:10:33

I am wondering what's the experience overall, I imagine it's not perfect

mpenet10:10:09

If you have a config that works well for you, feel free to share it too 🙂

dominicm10:10:17

If you do it at the pr level, then each commit contains reformatting. Makes it harder to review. If you have something that rewrites history then signatures become invalid.

dominicm10:10:29

Pre commit hook is best I think

reefersleep10:10:02

Sounds good, but this requires ensuring that each developer sets it up locally, which might be a hassle, or at least is not certain to be done thoroughly - isn’t a combination of pre-commit hook and PR hook the most safe and least noisy approach?

dominicm11:10:30

Pr level to fail ci as @andrea.crotti mentioned.

dominicm11:10:20

It is a hassle, but I think it's worse to have history be messed up

andrea.crotti11:10:05

I don't even need the pre commit hook because unless I do something deliberately weird Emacs clojure-mode does the right thing

andrea.crotti11:10:21

so well it depends on your team maybe but I don't think it's such a big deal

andrea.crotti11:10:26

they'll get used quickly

dominicm11:10:46

Cljfmt is designed to match emacs

dominicm11:10:09

Vim and intellij users then have to adjust settings to match, it's very annoying

dominicm11:10:47

Feels very hostile towards users of those editors if you pick based on a "preferred" editor. It elevates one above the others.

andrea.crotti12:10:26

Well I think you can't make everyone happy though

andrea.crotti12:10:38

If different editors have different defaults

andrea.crotti12:10:30

and we have vim /cursive users but I've never heard complains because of cljflmt

simonkatz17:10:36

A while ago I started work on a Git pre-push hook in Bash and Planck with the idea of allowing everyone in a team to use whatever indentation they like. It’s probably alpha quality at the moment. If there’s interest, I can look into tidying it up and getting it into a state where other people could use it with some confidence. If you think you might find it useful, or if you think it’s wrong-headed, please let me know! Some details: • After pulling, you indent the code as you like. (To make things easy for me, I added an Emacs command to re-indent all Clojure and ClojureScript files in a project and create a whitespace-change commit.) • When you push, a hook rewrites commits to use the canonical indentation. This is done with cljfmt, using options that only change indentation. (This could be made more flexible — it doesn’t have to be cljfmt.) • Any commits that only change indentation are discarded. • After the rewritten commits are pushed, a new local commit is created with your local indentation. • It takes care to keep your untracked files, worktree and staged files as they were. (That was pretty fiddly.) For anyone super-interested, there might be enough information in the README to give it a go. If not, ask me questions. It’s at https://github.com/simon-katz/nomis-git-formatting. It’s been tested (a while ago) with: - lein-cljfmt 0.6.4 - Leiningen 2.9.0 - Git 2.16.1 - Planck 2.19.0 - Clojure 1.10.0 - Bash 3.2.57 (which is really old, but it’s what comes with macOS; maybe I should upgrade) - macOS High Sierra 10.13.6

8
andrea.crotti17:10:06

Whoa that's quite cool

reefersleep11:10:20

@U06A9U5RP is this intended to make the commonly agreed formatting method invisible to the individual developer while he’s working on his branch?

simonkatz12:10:21

@reefersleep That’s kind of the idea, but local vs remote rather than branch vs master. Don’t argue about formatting and use whatever suits you, the individual developer. And the git history won’t have whitespace changes. It doesn’t seem that there’s much interest in this, though.

reefersleep12:10:48

When you say “use whatever suits you individually”, the code that you’re not currently worked on is all formatted according to the automatic process, yes?

reefersleep12:10:09

I’m trying to visualize how it would be to work with, and I don’t know if just having the indentation that you’ve just touched be suited to your style would be enough of a selling point for most developers. Your general idea is cool though 🙂

reefersleep12:10:04

Personally, I don’t know why I’d want to be viewing the file(s) I’m working on in a different indentation than I’d see initially on checking out master or viewing the files on github.

reefersleep12:10:16

I’d just use the agreed convention locally.

reefersleep12:10:01

And though I haven’t looked thoroughly at your thing, I might be worried that it’d cause more git management hassles than I’m already experiencing 😮

simonkatz12:10:16

Yeah, git management hassles is certainly something to consider. The idea is that after cloning or pulling, you reformat the whole project to suit you and create a whitespace-change commit. So everything you look at after that has your preferred indentation. This commit disappears when you push.

mpenet10:10:02

you can review with ignore whitespace on

mpenet10:10:11

gh has that on their ui as well

mpenet10:10:35

but yeah, pre-commit is probably the way to go

reefersleep10:10:43

It’s true that you can ignore whitespace, but it’s still noise in the file’s commit history. But that’s a different discussion 🙂

mpenet10:10:58

yes, it messes up stats too

mpenet10:10:53

I just learned about zprint and I am mostly curious about it. We use cljfmt but I have the feeling we could go further with zprint

reefersleep10:10:40

How do you use it?

reefersleep10:10:44

As a github hook?

Daniel Stephens12:10:06

For clojure metrics, is there something simpler than a histogram to just store one numerical value at a time? I just want to do (simple/update! (simple/simple "my-simple-metric-name") 10) and then be able to get 10 back from it on my metrics endpoint. With a histogram I seem to then only have access to a min, max or mean, or all the values in the sample.

reefersleep11:10:53

(let [one-numerical-value (atom 0)]
  (prn "Number is " @one-numerical-value)
  (swap! one-numerical-value inc)
  (prn "Number is now: @one-numerical-value))

reefersleep11:10:35

I mean, you’re talking about things in the context of statistics, but what you need does not seem statistics-related and is a quite simple request, unless I misunderstand you

reefersleep11:10:21

or (defonce one-numerical-value (atom 0))

Daniel Stephens12:10:40

So I want to register a metric with the default metrics registry so that I can get the value out on grafana, it's just bizarre to me that all of these somewhat complicated metrics are built in but there isn't just a store my value equivalent. In the end I went with a histogram of 1 value as it produces the correct graph for my dashboard, but it's a bit weird.

reefersleep12:10:13

I agree, I guess! If you’re bound by the framework, it does seem weird that that’s missing.

👍 4
Daniel Stephens13:10:21

I guess I can supply a uniformReservoir of size 1, just seems a bit verbose

Daniel Stephens13:10:41

(defn simple-hist [title]
  (.histogram mc/default-registry
              (mc/metric-name title)
              (reify MetricRegistry$MetricSupplier
                (newMetric [_] (Histogram. (mc/uniform-reservior 1))))))
Went with this for now, seems a bit wrong that I have to use mean to get my single value back. Feel like I'm missing something really obvious here for how to simple put and get a single value

pez13:10:02

I wonder if it documented somewhere that the Clojure reader accepts code like this:

(defn ❤️ [x]
  (str "I ❤️ " x))

(❤️ "Clojure")
The Reader page says: ”Symbols begin with a non-numeric character and can contain alphanumeric characters and *, +, !, -, _, ', ?, <, > and = (other characters may be allowed eventually).” Which doesn't seem to include emojies.

hugo13:10:22

That sentence doesn't seem to contradict the fact that the reader accepts emojis.

pez13:10:33

@hybas3, that's why I ask. It is not clear to me why the characters are enumerated if other characters can be included. Also ClojureScript does not accept that code.

dominicm13:10:14

The clojure reader is very lenient compared to what the specification says

andy.fingerhut13:10:58

The specification is intentionally not exhaustive of what characters that the implementation accepts

Alex Miller (Clojure team)13:10:15

in other words, it's not documented that this isn't accepted

andy.fingerhut13:10:15

The specification enumerates characters promised to be good in symbols.

Alex Miller (Clojure team)13:10:53

and I would say it's unlikely that your ability to use unicode there would be narrowed later

Alex Miller (Clojure team)13:10:59

so go for it :)

❤️ 4
carocad15:10:31

does this mean that the inability for clojurescript to accept this code is a bug :thinking_face: ? or is this rather a “runtime” decision not a grammatical one ?

Alex Miller (Clojure team)15:10:14

I'd say it's a host/dialect decision, so up to ClojureScript whether they want to accept

carocad16:10:30

fair enough :thumbsup::skin-tone-4:

mhuebert11:11:08

maybe ClojureScript could add support for this by modifying munge to turn special unicode characters into something acceptable in javascript variable names

mhuebert11:11:46

currently (munge "❤️") returns "❤️" which doesn’t work as a property name (`obj.❤️` is invalid)

dominicm13:10:24

Oh. That's interesting. I read it as an exclusive list.

pez13:10:40

I'll go for it. I love that the code works! I'm trying to help port parcera to cljc, which is why I ask. https://github.com/carocad/parcera/issues/8

Alex Miller (Clojure team)13:10:50

there are a handful of things that I would stay away from in symbols, like |

Alex Miller (Clojure team)13:10:47

(as that is a future possible delimited symbol character)

Joe Lane13:10:32

... what is a "delimited symbol"?

pez13:10:39

Character literals can not be emojis in the current Clojure implementation, btw.

Alex Miller (Clojure team)14:10:06

@lanejo01 if we were to have a way to specify symbols (and keywords) with spaces and any character in them, we would probably use surrounding | | to delimit those

Alex Miller (Clojure team)14:10:56

which is not say that we're doing that

Alex Miller (Clojure team)14:10:09

but if we did, that would probably be the character

Joe Lane14:10:11

Ahh, I see. Solving the classic (keyword " Ive got spaces") issue 🙂

Joe Lane14:10:19

Makes sense

Alex Miller (Clojure team)14:10:29

I'm pretty sure it wouldn't be ❤️

aw_yeah 4
Alex Miller (Clojure team)14:10:38

although that would be fun

bronsa14:10:06

and backwards compatible

jeroenvandijk14:10:31

I have keywords with | in them (no joke) :rolling_on_the_floor_laughing:

Alex Miller (Clojure team)14:10:43

You’ve been warned :)

👌 4
bostonaholic14:10:48

> just because you can, doesn’t mean you should

dangercoder16:10:50

Hi, I am using carmine. How do you handle single connection pooling? I want to have a connection pool at the same time I don't want to use the wcar* macro since I prefer to not use global state, I want to do something like: (transact conn body).

lukasz16:10:06

@jarvinenemil we ended up creating a Component for that

lukasz16:10:29

this way we can setup one connection/pool in the system and pass it around without using any macros

dangercoder16:10:02

I see, I will be going a similar route

dangercoder16:10:09

thanks ✌️

lukasz16:10:32

I might open source our component, I find it somewhat easier to use than Carmine

lukasz16:10:36

I'm biased of course 🙂

dangercoder16:10:16

that would be great!

Brian16:10:23

Why is it the this code

(defn test1
  "Upload the file from S3 to the database"
  []
  (let [a 5]
    (map
      (fn
        [x]
        (let [five 5]
          (println "inside let block")))
      [1 2 3]))
  "done")
will not end up printing "inside let block" but this code (same as above, just without the "done" return value)
(defn test1
  "Upload the file from S3 to the database"
  []
  (let [a 5]
    (map
      (fn
        [x]
        (let [five 5]
          (println "inside let block")))
      [1 2 3])))
will?

ghadi16:10:00

because map is lazy - in the first example the map'ed sequence is thrown away and "done" returned - in the second example the map'ed sequence is returned, and then the P in REPL tries to print it, and ends up realizing the lazy sequence

Brian16:10:54

Oh wow oh wow. That caused me such heart ache trying to figure out why everything had suddenly broken. Thank you!

8
andy.fingerhut16:10:00

mapv is a quick drop-in replacement that is eager

simonkatz17:10:36

A while ago I started work on a Git pre-push hook in Bash and Planck with the idea of allowing everyone in a team to use whatever indentation they like. It’s probably alpha quality at the moment. If there’s interest, I can look into tidying it up and getting it into a state where other people could use it with some confidence. If you think you might find it useful, or if you think it’s wrong-headed, please let me know! Some details: • After pulling, you indent the code as you like. (To make things easy for me, I added an Emacs command to re-indent all Clojure and ClojureScript files in a project and create a whitespace-change commit.) • When you push, a hook rewrites commits to use the canonical indentation. This is done with cljfmt, using options that only change indentation. (This could be made more flexible — it doesn’t have to be cljfmt.) • Any commits that only change indentation are discarded. • After the rewritten commits are pushed, a new local commit is created with your local indentation. • It takes care to keep your untracked files, worktree and staged files as they were. (That was pretty fiddly.) For anyone super-interested, there might be enough information in the README to give it a go. If not, ask me questions. It’s at https://github.com/simon-katz/nomis-git-formatting. It’s been tested (a while ago) with: - lein-cljfmt 0.6.4 - Leiningen 2.9.0 - Git 2.16.1 - Planck 2.19.0 - Clojure 1.10.0 - Bash 3.2.57 (which is really old, but it’s what comes with macOS; maybe I should upgrade) - macOS High Sierra 10.13.6

8
simonkatz17:10:04

(Sorry for the essay.)

emccue21:10:14

What builtin can get me a lazy sequence like this

emccue21:10:31

(... inc 0) => (0 1 2 3 4 5 ...)

emccue21:10:42

i feel like there is something but i just can't think of it

emccue22:10:26

Is there a way to attach some sort of explanation to a s/and spec?

emccue22:10:30

(def tile-ports #{:a :b :c :d :e :f :g :h})

(s/def ::tile (s/and map?
                     #(= (set (keys %)) tile-ports)
                     #(= (set (vals %)) tile-ports)
                     #(every? (partial apply not=) %)))

emccue22:10:59

the error i get when the spec doesnt match isn't the best to say the least

hiredman22:10:01

I believe if you implement the Spec protocol yourself you can do that, but I don't think there is a lot of documentation for how to do that, and I don't think there is an easier way (like there is for attaching custom generators)

hiredman22:10:33

also I don't think that last every? predicate is helping. it will always be true by definition for a map

emccue22:10:43

(i am also still using spec 1 because spec 2 is confusing)

emccue22:10:58

nope, that apply not= means that a key can't map to itself

emccue22:10:13

so {:a :a} is illegal

emccue22:10:28

which would be great if i could make make sense in an error

hiredman22:10:31

ah, right the apply

hiredman22:10:30

spec does include the name of failed predicates in errors, so if you pull the anonymous functions out and give them meaningful names it is at least slightly nicer

emccue22:10:20

yeah but then i have 3 extra functions in the namespace

emccue22:10:31

(defn valid-tile?
  "Returns if the given object is a valid tile"
  [tile]
  (and map?
       (= (set (keys tile)) tile-ports)
       (= (set (vals tile)) tile-ports)
       (every? (partial apply not=) tile)))

(s/fdef valid-tile?
        :args [:tile any?]
        :ret boolean?)

emccue22:10:59

(s/def ::tile valid-tile?)

emccue22:10:06

which gives worse errors for sure

emccue22:10:34

but feels nicer

hiredman22:10:38

you might as in #clojure-spec, there may be some library that has an implementation of Spec that will let you customize the describe errors

seancorfield22:10:22

Shouldn't that be :args (s/cat :tile any?)

emccue22:10:50

yep, keep forgetting that

emccue22:10:10

(valid-tile? 3)
Execution error (IllegalArgumentException) at orchestra.spec.test/spec-checking-fn$conform! (test.clj:97).
Key must be integer

emccue22:10:23

every time i make that specific error i get this fantastic error

hiredman22:10:56

then stop using orchestra

emccue22:10:09

(same error in not orchestra too)

seancorfield22:10:48

I would try defining named predicates for valid-keys? valid-vals? and no-self-mappings? for those three anon parts and then have (s/def :tile (s/and map? valid-keys? valid-vals? no-self-mappings?)) and see how that works.

emccue22:10:00

(valid-tile? "a")
Execution error (IllegalArgumentException) at orchestra.spec.test/spec-checking-fn$conform! (test.clj:97).
Key must be integer

emccue22:10:46

not complaining - i am definitely giving it wrong input

seancorfield22:10:50

(that's still with orchestra BTW 🙂 )

hiredman22:10:23

user=> (valid-tile? 3)
Execution error (IllegalArgumentException) at user/valid-tile? (REPL:5).
Don't know how to create ISeq from: java.lang.Long
user=>

hiredman22:10:35

that is the "vanilla" error

hiredman22:10:28

right, the valid-tile? predicate in your message up above never actually calls map?

emccue22:10:29

yeah, i somehow managed to infect my repl

emccue22:10:38

(defn random-tile
  "Generates a random tile"
  []
  (let [shuffled-ports (shuffle tile-ports)
        pairs (mapcat (juxt vec (comp vec reverse))
                      (partition 2 shuffled-ports))]
    (into {} pairs)))
Syntax error macroexpanding clojure.core/defn at (tile.clj:34:1).
#object[clojure.spec.alpha$and_spec_impl$reify__2183 0x782df094 "[email protected]"] is not a fn, expected predicate fn

emccue22:10:02

what exactly are regex ops

emccue22:10:15

it feels like the generation features of spec don't work if i dont use them

emccue22:10:26

(well, they wouldn't)

emccue22:10:35

and all the docs are oriented toward them

emccue22:10:48

but if they aren't a predicate what exactly

hiredman22:10:05

they do work if you don't use them

emccue22:10:27

i saw in the code there is a mapping of some builtin predicates to generators for them

emccue22:10:32

which makes sense

hiredman22:10:40

a predicate with some other stuff

hiredman22:10:50

a spec is sort of a predicate with other optional stuff

hiredman22:10:05

and the other optional stuff is required for generators

hiredman22:10:46

spec has some built in mappings from predicates to generators, but you can also combine your custom predicates with generators

hiredman23:10:03

regex ops are a particular set of spec combinators that build specs out of specs, that behave similar to regex operations, but on sequential collections instead of on strings

hiredman23:10:42

but all the combinators (not just the regex ops) will generate examples if the base specs they are built out of can generate examples

emccue23:10:51

Finally, we can use alt to specify alternatives within the sequential data. Like cat, alt requires you to tag each alternative but the conformed data is a vector of tag and value.

emccue23:10:15

i get the same feeling reading some clojure docs as I do reading my E+M textbook

emccue23:10:19

like the author clearly gets it

emccue23:10:37

but my point of reference is just way too far removed

Alex Miller (Clojure team)23:10:37

regex ops describe the structure of a sequential collection

Alex Miller (Clojure team)23:10:00

like regular expressions describe the structure of characters in a string

emccue23:10:38

i am with you so far, but the metaphysics of that kinda bugs me - if that makes sense

Alex Miller (Clojure team)23:10:58

no, it doesn't. But I am the author above of the text above so... :)

Alex Miller (Clojure team)23:10:36

but I am happy to make it clearer if I can

emccue23:10:50

i understand that i can test a collection where i expect an int and then a string like this

emccue23:10:07

(s/cat :some-name integer? :other-name string?)

emccue23:10:16

makes perfect sense to read

emccue23:10:32

it checks that the first thing is an int, second thing is a string

emccue23:10:42

and if either is wrong it uses the name for better error reporting

emccue23:10:55

the name cat is weird, but whatever

emccue23:10:16

but i don't understand what cat is

emccue23:10:24

or rather, what the return value is

Alex Miller (Clojure team)23:10:56

short for catenate (base for concat, etc)

emccue23:10:03

and once i step outside of the golden path of spec supported stuff it feels like i am missing something

emccue23:10:31

like, i can use cat and keys to make specs that test if a collection fits some condition or that some keywords in a map fit

Alex Miller (Clojure team)23:10:06

when evaluated, the form above returns a spec object

Alex Miller (Clojure team)23:10:39

the spec api methods mostly take a spec and a value, then check whether the value is valid according to the spec

Alex Miller (Clojure team)23:10:10

(with varying behavior - valid? returns a boolean, conform tells you why it matched, explain tells you why it didn't match, etc)

emccue23:10:48

but like in this scenario - which i will admit is pretty toy - a custom predicate I write isnt a spec

hiredman23:10:06

it just is missing a recipe for generating data

Alex Miller (Clojure team)23:10:23

predicates (function objects) are also valid specs

emccue23:10:26

well, it can act as one, but it doesn't have the same error reporting magic that other specs have

hiredman23:10:43

you just need to define a generator for it

Alex Miller (Clojure team)23:10:52

generators don't affect the error message

emccue23:10:03

so a "spec"

spec -> predicate || namespaced keyword reference to a spec || a "spec" object

emccue23:10:19

and that third one is what all of the builtin functions return

Alex Miller (Clojure team)23:10:44

there are several different things happening in the impl here, which adds some confusion here

Alex Miller (Clojure team)23:10:50

spec 2 is much more regular in this regard

Alex Miller (Clojure team)23:10:30

the spec forms (s/and etc) are really two things

Alex Miller (Clojure team)23:10:58

they are macros, and they are also symbolic forms describing a spec operation

Alex Miller (Clojure team)23:10:20

when evaluated, they return the spec object implementing the form

Alex Miller (Clojure team)23:10:05

predicates (symbols that resolve to vars referring to functions) are evaluated in the course of that expansion and turn into function objects

Alex Miller (Clojure team)23:10:53

function objects are opaque and as such are a bit more challenging to produce good errors. there are some hacks in the impl there to work backward from the function objects.

hiredman23:10:11

the way explain, which is what prints the errors works, is it walks down the spec building and path and when it hits some that doesn't match it uses the path it built down to the point for the errors. which means it is easy to report the point of error for "local failures" that are just about a value at the bottom of the spec, but you want to take a higher level failure "checking these values against each other" and report more path information which doesn't exist (because the check happens higher in the tree)

hiredman23:10:03

so you can't use spec's path builder for the error message to include the more local information, because the predicate operates at a level higher then that, so in order to make it work you need to implement the Spec protocol yourself, get the built path passed from spec, then generate your own extra information

Alex Miller (Clojure team)23:10:33

which I would not recommend

hiredman23:10:36

this is kind of a bad idea because spec 1 is a dead end

Alex Miller (Clojure team)23:10:46

well, I wouldn't recommend it in spec 2 either :)

hiredman23:10:49

there are also terrible ways you could write local predicates that operate based on that higher level knowledge, e.g. something with binding

Alex Miller (Clojure team)23:10:05

your suggestions are getting worse :)

hiredman23:10:15

I said it was terrible

Alex Miller (Clojure team)23:10:08

spec bottoms out at predicates and the level of feedback you can provide is pass/fail

Alex Miller (Clojure team)23:10:14

one idea that has been raised is that instead of a simple "invalid" code you could provide a map with more useful information

kenny23:10:56

I am definitely very interested in this. Returning more info than just true/false would greatly improve errors.

kenny23:10:36

Example I ran into a few days ago: We have a spec with a s/and predicated called no-metric-cycles?. It does this:

(defn no-metric-cycles?
  [metrics]
  (not (boolean (find-cycle-in-metrics metrics))))
It's transforming rich info about an error into a simple true/false value. I'd be much more interested in seeing the result of that find-cycle-in-metrics function.

emccue23:10:25

i wish i had advice on how to make the docs clearer, but im not even clear on what it is that confuses me at any given point

kenny23:10:42

Is it possible to write a macro that generates a core.match match macro on-the-fly?

kenny00:10:14

It seems like this is inherently not possible :thinking_face:

kenny00:10:19

Actually this may be possible by calling the clj-form function that the match macro calls.

kenny02:10:24

Found a much cleaner approach, albeit slightly less efficient.