Fork me on GitHub
#clojure-spec
<
2017-07-23
>
thedavidmeister08:07:38

is there something like spec/or that just takes a list of predicates rather than the k/v list?

thedavidmeister08:07:52

i don't totally understand why spec/or and spec/and are different in this way

thedavidmeister08:07:06

also, what's the best way to use spec to see if something only has a certain set of keys?

schmee08:07:32

spec/or takes a k/v list so that when you conform, you see which of the branches was taken

schmee08:07:49

check out the “Composing predicates” here: https://clojure.org/guides/spec

schmee08:07:21

this is not needed for s/and, since there is no branching there (all predicates must be true)

schmee08:07:06

checking that something only contains a certain set of keys is sort of against specs pricinples, since key sets are supposed to be open

schmee08:07:33

of course you can do that anyway by using a custom predicate fn

thedavidmeister09:07:19

@schmee i have a map that contains some keys that represent "machine data" like ids/references and some that represent "user data" which is what the user actually typed in

thedavidmeister09:07:52

when an item has only machine data left in it i want to be able to clean it out of my db

thedavidmeister09:07:11

because that item no longer represents anything useful to the user

thedavidmeister09:07:20

so i want a spec that can tell when only machine keys are left

thedavidmeister09:07:46

so it is open when adding, but closed when making a decision about whether to "clean up"

schmee09:07:36

that can be done with a predicate fn spec

schmee09:07:02

but for instance s/keys has no way to say “these keys are not allowed”

thedavidmeister09:07:02

it's not that they're not allowed

thedavidmeister09:07:13

but that's not the right way to think about it in context

thedavidmeister09:07:47

i'm trying to detect something not restrict it

thedavidmeister09:07:51

(if (spec/valid? :item/item--machine-only) (do ...) (do ...))

thedavidmeister09:07:25

@schmee the extra keys are totally allowed but i do want to treat items that have only machine keys a bit differently

schmee11:07:08

perhaps something like this would work?

(s/def ::machine-keys (s/keys :req [:a :b :c]]))
(s/def ::human-keys (s/keys :req [:d :e]]))
(s/def ::all-keys (s/merge ::machine-keys ::human-keys))

schmee11:07:35

then you could validate thing separately when needed but still have a spec for the combination

thedavidmeister12:07:52

@schmee the problem is that ::machine-keys is valid when there are human keys too

thedavidmeister12:07:52

and human keys is valid for machine keys

schmee12:07:24

then do something like (and (s/valid? ::machine-keys foo) (not (s/valid? ::human-keys foo))

thedavidmeister00:07:33

@schmee but then :f would trigger

thedavidmeister00:07:45

i actually do want my keys to be open

thedavidmeister00:07:27

i don't want to have to stipulate every human key, and none of them are required, they're all optional, it's just important that there is at least one

thedavidmeister10:07:58

@schmee also, when you're doing spec/or and using keywords it still seems odd to force k/v

thedavidmeister10:07:18

e.g. (spec/or :foo/bar :foo/bar :a/b :a/b)

schmee11:07:51

the first keyword there is just a tag

schmee11:07:57

e.g

dev=> (s/def ::test (s/or :foo keyword? :bar int?))
:dev/test
dev=> (s/conform ::test 1)
[:bar 1]

schmee11:07:30

spec makes you name all the branches so that you can use that in error messages etc.

thedavidmeister12:07:26

@schmee but if the branch is just a keyword that is already registered it already has a tag, so it could use that for error messages

thedavidmeister12:07:42

hmmm how is it possible that

thedavidmeister12:07:04

(valid? ::foo a) and (valid? ::bar a) are both true

thedavidmeister12:07:23

but (valid? (spec/and ::foo ::bar) a) is false?

schmee12:07:31

it would be weird if there was a different syntax for registered keywords, better to have a uniform interface and accept the extra typing

schmee12:07:01

you can make a macro that does what you want

thedavidmeister13:07:41

@schmee mmk, any idea why spec/and can be false when each of the individual items are true?

schmee13:07:52

well, in your example you test that a is valid for ::foo and b is valid for ::bar

schmee13:07:09

then you test if ::foo and ::bar is true for b

schmee13:07:31

so you’re testing two different things

schmee13:07:35

the first doesn’t imply the second

thedavidmeister13:07:49

@schmee typo, it's all supposed to be a

thedavidmeister13:07:28

boot.user=> (spec/valid? :item/item--exists {:item/project #uuid "4e2db038-f64d-4805-95cf-720e9e7f1419", :db/id 1, :item/title "85", :item/list-id {:db/id 10}})
true
boot.user=> (spec/valid? :item/item--user-input {:item/project #uuid "4e2db038-f64d-4805-95cf-720e9e7f1419", :db/id 1, :item/title "85", :item/list-id {:db/id 10}})
true
boot.user=> (spec/valid? (spec/and :item/item--user-input :item/item--exists) {:item/project #uuid "4e2db038-f64d-4805-95cf-720e9e7f1419", :db/id 1, :item/title "85", :item/list-id {:db/id 10}})
false

schmee13:07:10

what does item/item--exists and :item/item--user-input look like?

thedavidmeister13:07:15

(spec/def :item/item--exists
 (spec/and
  :item/item
  (comp #(spec/valid? :item/id--exists %) :db/id)))

thedavidmeister13:07:29

(spec/def :item/item--user-input
 (spec/and
  :item/item
  #(not (all-keys-in? % base-keys))))

thedavidmeister13:07:37

(defn all-keys-in?
 [coll allowed-keys]
 (= #{}
  (clojure.set/difference
   (set (keys coll))
   allowed-keys)))

thedavidmeister13:07:34

but does it matter?

thedavidmeister13:07:44

how can (and true true) be false?

schmee13:07:20

that is a good question

thedavidmeister13:07:01

interestingly, {:item/project #uuid "4e2db038-f64d-4805-95cf-720e9e7f1419", :db/id 1, :item/title "85", :item/list-id {:db/id 10}} was generated by exercise for the spec/and

thedavidmeister13:07:40

wait, that's a lie 😛

thedavidmeister13:07:58

i generated it with slightly different code

thedavidmeister13:07:46

hmmm, so if i do exercise for :item/item--exists or :item/item--user-input independently i can generate :item/list-id

thedavidmeister13:07:51

but together i cannot