Fork me on GitHub
#beginners
<
2021-06-17
>
Franco Gasperino17:06:11

Working with spec, im writing a custom generator to produce UUID values. Experimenting with this, i've come up with the following:

(def gen-uuid (spec/gen 
                (into [] 
                  (take 1000
                    (repeatedly #(str (java.util.UUID/randomUUID)))))))
=> ; Execution error (NullPointerException) at java.util.regex.Matcher/getTextLength (Matcher.java:1770).
; null
However, the form works as expected outside the gen call. What am i missing?

dpsutton17:06:12

what's the docstring of spec/gen?

Franco Gasperino17:06:02

the argument must be a spec itself. Maybe i'm confused that a set can be used as an argument

hiredman17:06:54

there is no set in your code

Franco Gasperino17:06:15

There is in the clojure spec guide:

First example has the following:
(def kw-gen (s/gen #{:my.domain/name :my.domain/occupation :my.domain/id}))
(gen/sample kw-gen 5)
I was unaware of its behavior here

hiredman17:06:26

yes, but your into call is constructing a vector

Franco Gasperino17:06:39

correct. my confusion is how a set behavior differs. I'm missing something fundamental about set behavior as a spec, compared to other sequences

alexmiller17:06:49

sets are specs of enumerated values

alexmiller17:06:02

other colls are not

Franco Gasperino17:06:31

alright. ill noodle on the implications of that

Franco Gasperino17:06:41

(def uuid-regex
  (re-pattern #"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))

(spec/def ::uuid?
  (spec/with-gen
   (spec/and string? #(re-matches uuid-regex %))
   #(spec/gen (into #{} (take 100 (repeatedly (fn [] (str (java.util.UUID/randomUUID)))))))))

(spec-gen/sample (spec/gen ::uuid?))
This works when the vector is replaced with a set

Franco Gasperino17:06:31

I have a related question regarding custom generators

hiredman17:06:14

you may also want to check out the built in uuid generate in test.check (https://github.com/clojure/test.check/blob/master/src/main/clojure/clojure/test/check/generators.cljc#L1520-L1553) that spec uses as the generator for the clojure.core/uuid? predicate

Franco Gasperino17:06:14

(def uuid-regex
  (re-pattern #"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))

(spec/def ::uuid?
  (spec/with-gen
    (spec/and string? #(re-matches uuid-regex %))
    #(spec/gen
      (into #{}
       (take 100
        (repeatedly (fn [] (str (java.util.UUID/randomUUID)))))))))

(defn truncate-uuid [x] (apply str (take-last 12 x)))

(spec/fdef truncate-uuid
   :args (spec/cat :uuid ::uuid?)
   :ret string?)

;; clojure.spec.test.alpha :as spec-test
(spec-test/check `truncate-uuid)

=> ({:spec
  #object[clojure.spec.alpha$fspec_impl$reify__2525 0x4d86324 "[email protected]"],
  :clojure.spec.test.check/ret {:result true, :pass? true, :num-tests 1000, :time-elapsed-ms 45, :seed 1623952390632},
  :sym eventflow.schema/truncate-uuid})

Franco Gasperino17:06:48

in this case, is the check, which is leveraging the custom generator, only supplying check with values coming from the generator?

Franco Gasperino17:06:03

or will it create produce values matching the spec predicate which are not produced by the generator as part of the check?

Franco Gasperino18:06:18

A better illustration may be from https://clojure.org/guides/spec "Custom Generators"

hiredman18:06:40

you are asking if the spec will try "negative" cases?

Franco Gasperino18:06:56

yes, when a custom generator is applied within the spec

hiredman18:06:07

no, custom generator or not

hiredman18:06:49

a spec says the result is defined over some set of inputs, and says nothing about the result over inputs not in the that set

Franco Gasperino18:06:24

how would you suggest i reason about a spec for the following:

(defn in?
  "Return true if x is in coll, false otherwise."
  [coll x]
  (if (some #(= x %) coll) true false))

(spec/fdef in?
  :args (spec/cat :coll coll? :x any?)
  :ret boolean?)

(spec-test/check `in?)

Franco Gasperino18:06:22

the generator works. can i be certain that the affirmative case returning true is exercised without a custom generator? If not, how can i apply a custom generator and then affirm the inverse?

alexmiller18:06:51

one technique is to use a custom generator that you know generates both cases

Franco Gasperino18:06:55

or maybe more terse, how do i associate the argument :coll with :x both in the true and false case within the generator

hiredman18:06:10

no, you'll want a custom generator if you want to spec that

alexmiller18:06:35

s/fdef can have a :fn spec that receives both the conformed input spec and the conformed ret value

alexmiller18:06:18

for something like this, it's helpful to make a generator that generates the :x, then builds the :coll to contain it

Franco Gasperino18:06:45

ok. i see the :fn kwarg now in fdef

hiredman18:06:12

I would create a custom generator using, uh, I forget the test.check stuff, but there are combinators that say "generate something by choosing one this collection of generators, and generate from that" and have a generator that is completely random in coll and x

hiredman18:06:30

and a generator that generates a random coll and chooses an x from it

alexmiller18:06:13

gen/fdef is a useful combinator for the latter (and gen/bind for the former)

Franco Gasperino18:06:00

thanks for the feedback. quite helpful

Franco Gasperino18:06:37

on the philosophy of spec and test, do you only write a spec for testing, not conforming, as part of your flow, or only where necessary / appropriate?

Franco Gasperino18:06:37

in many spots, it can add a significant amount of code overhead when a form can be proven in the repl. what do you find works best on teams?

alexmiller18:06:34

I am judicious in where I write them - mostly for tricky low-level stuff where it's best to generate large input sets for verification, or for api-level contracts

alexmiller18:06:46

where instrumentation can be useful during dev

Franco Gasperino18:06:17

do you have a preferred path regarding external data on the application boundaries? Conform during runtime? Generators for testing?

popeye17:06:47

I am trying to destruct map

(let [keys [:my_id (:myid input)]]
     _ (println keys))

popeye17:06:21

i want to rename value from :myid to :my_id

hiredman17:06:30

"Destructuring is a way to concisely bind names to the values inside a data structure."

hiredman17:06:47

it is not a way to modify datastructures

hiredman17:06:06

(the quote is first line in the guide linked above)

sova-soars-the-sora18:06:39

making a calendar app in clojurescript... any recommendations on how I can find the number of days in a month programmatically, and the day of the week for a given date?

Michael Stokley18:06:53

i'd recommend using a date and time library. moment.js, for example

Michael Stokley18:06:00

it's more difficult than i'd ever want to mess with, especially in the context of robust third party libs

sova-soars-the-sora18:06:28

Right on. I have not worked with bringing in third party libs to CLJS at all.. gotta learn how to do that

Michael Stokley18:06:29

looks like there are cljs libs, too

ndonolli18:06:23

cljs-time seems fine, but if you are thinking of pulling in a js library to interop on, I'd advise against moment.js. It's deprecated and the reasons why are documented on their website

sova-soars-the-sora18:06:13

Rockin. Tryin out cljs-time now.. it seems like it has all the fxns I would need to make a simple cal app

ndonolli18:06:28

date-fns is a date library that is more functionally oriented and more modular, if you wanna go the js route

👍 2
sova-soars-the-sora18:06:58

So shadowcljs doesn't support cljsjs ... which makes sense because it's NPM based... gotta figure out how to bring the dependency in

sova-soars-the-sora18:06:50

thank you... scanning now

sova-soars-the-sora18:06:28

also asked in shadow-cljs... If a library exists in NPM such as "date-fns" do I still need to include it in the `:dependencies [[]]`  key in shadow-cljs.edn?

sova-soars-the-sora18:06:43

Oh I see.. npm install date-fns covers the dependencies. ^.^

valtteri18:06:01

Without dependencies you can do something like

(require '[goog.date :as gdate])
(gdate/getNumberOfDaysInMonth 2021 0)
31

valtteri18:06:51

Google Closure Library has incredible amount of things in it. https://google.github.io/closure-library/api/goog.date.html#

sova-soars-the-sora18:06:07

Oh that's sweet. That's probably the way to go

sova-soars-the-sora19:06:07

Hmmm. I don't know enough about cljs/js interop yet to know how to get the weekday for a given date

sova-soars-the-sora19:06:57

I tried something like (. weekDay (gdate/Date 2023 1))

sova-soars-the-sora19:06:04

but that doesn't work ... I am probably missing something vital

valtteri19:06:49

(.getWeekday (gdate/Date.))

valtteri19:06:30

Not on a computer anymore so can’t verify :/

sova-soars-the-sora19:06:41

Thanks 😃 I am trying (.info js/console (.-getWeekday (gdate/Date. 2023 1 1))) but I get a function definition printed in the console ;/

sova-soars-the-sora19:06:13

.Weekday works but the hyphen does not

sova-soars-the-sora19:06:23

.getWeekday instead of .-getWeekday

sova-soars-the-sora19:06:38

super ninja coder skills don't need no computing devices ^.^

ndonolli21:06:19

Ooh yeah the hyphen is used for property access, without is for method invocation

Lukas18:06:30

Hey, sorry to bother you. I try to remove forms from a tree structure. My solution is ... let's call it messy, so I just wanted to ask some experts if there is a more concise way of doing this?

(defn remove-tree [pred tree]
    (loop [n '()
           [h & rst] tree]
      (let [e (if (pred h)
                n
                (conj n
                 (if (list? h)
                   (remove-tree pred h)
                   h)))]
        (if rst
          (recur e rst)
          e))))

(comment 
;; simple example for testing: remove expr begining with +
   (remove-tree
     (fn [x]  (and (list? x) (= '+ (first x))))
     '(-
       (- 1 1 (+ 1 1))
       3
       (+ 1 2)
       (- 2 3 (- 1 1))))
   ;; => (((1 1 -) 3 2 -) 3 (1 1 -) -)
   ,)
Furthermore, I still have to reverse the result 🙈 I also tried walk but I couldn't figure out a solution with it.

phronmophobic18:06:58

you could do this with https://github.com/redplanetlabs/specter, but I like using zippers!

(require '[clojure.zip :as z])
(defn zip-walk
  "Depth first walk of zip. edit each loc with f"
  [zip f]
  (loop [zip zip]
    (if (z/end? zip)
      (z/root zip)
      (recur (-> (z/edit zip f)
                 z/next)))))

(defn tree-zip [obj]
  (z/zipper list?
            identity
            (fn [node children]
              (into '() (remove #{::remove} children)))
            obj))

(defn remove-tree [pred tree]
  (zip-walk (tree-zip tree)
            (fn [obj]
              (if (pred obj)
                ::remove
                obj))))

Lukas18:06:36

Awesome, thanks for the input

phronmophobic19:06:00

zippers work really well for functionally editing trees

hiredman20:06:53

walk works too

(clojure.walk/prewalk
 (fn [item]
   (if (list? item)
     (mapcat
      (fn [x]
        (if (and (list? x)
                 (= '+ (first x)))
          []
          [x]))
      item)
     item))
 '(-
   (- 1 1 (+ 1 1))
   3
   (+ 1 2)
   (- 2 3 (- 1 1))))
The main thing is you just have to keep straight the list you are splicing vs the list your are looking for to splice out

hiredman20:06:31

(that mapcat can just be a filter)

Lukas20:06:41

Oh, very cool. Thank you. I had (tbh still have) some troubles to wrap my head around how walk works, but this helps a lot

sova-soars-the-sora21:06:17

(doseq [o 2
	    d (range 1 31)
  :let [c (mod (+ d o) 7)]]
  	   (println d [c c]))
is giving me a "Don't know how to create ISeq from java.lang.Long" ... but if I remove the o 2 and hardcode in a (+ d 2) it works what am I doing wrong with doseq?

noisesmith14:06:57

user=>    
(ins)user=> (doseq [:let [o 2] d (range 1 31) :let [c (mod (+ d o) 7)]] (println d [c c]))
1 [3 3]
2 [4 4]
3 [5 5]
4 [6 6]
5 [0 0]
6 [1 1]
7 [2 2]
8 [3 3]
9 [4 4]
10 [5 5]
11 [6 6]
12 [0 0]
13 [1 1]
14 [2 2]
15 [3 3]
16 [4 4]
17 [5 5]
18 [6 6]
19 [0 0]
20 [1 1]
21 [2 2]
22 [3 3]
23 [4 4]
24 [5 5]
25 [6 6]
26 [0 0]
27 [1 1]
28 [2 2]
29 [3 3]
30 [4 4]

sova-soars-the-sora21:06:12

are there limitations on what I can put in the :let clause?

borkdude21:06:46

@sova doseq expects you to provide sequences (rather: seqable object) in the right hand binding. 2 is not a seqable.

sova-soars-the-sora21:06:22

I see. (repeatedly 2) also did not work ;x

borkdude21:06:32

try (repeat 2)

borkdude21:06:03

(btw, that doseq won't step in that case)

borkdude21:06:42

what you probably wanted: (let [o 2] (doseq [d ...]))

sova-soars-the-sora21:06:09

i'll get back to you on if i solved the halting problem or not :rolling_on_the_floor_laughing:

borkdude21:06:46

how do you know if solving the halting problem will ever stop and when to stop trying?

sova-soars-the-sora21:06:12

beginnings and ends are a duality, for every end there must be a beginning. it's a simple boolean 😛

sova-soars-the-sora21:06:08

actually that still doesn't solve the halting problem... some beginnings don't end in a reasonable amount of time. hit the brake and if it doesn't do anything, eject? =D

sova-soars-the-sora21:06:12

all those runaway computations must get tired eventually

vinurs22:06:54

hello, i have a 2 dimensional array m x n like this 1 2 3 4 ..... m 1 1a 2a 3a 4a .... ma 2 3 4 ... n every element in the array has it own range, eg. 1a (-0.1 + 0.15 ), 2a ( -0.2 + 0.4), then how can i iterate all possible values for this array then do sth for each possible value for -0.1 ... +0.15 for -0.2 ... + 0.4

......
about m x n nested for loops

vinurs22:06:27

one step is 0.01, so like this: -0.1 -0.09 -0.08 ... 0, 0.01, 0.02....0.15

vinurs22:06:59

@U11BV7MTK every element's range is different

dpsutton22:06:59

How many values are in the range (-0.1 +-0.15)?

vinurs22:06:52

0.01 is a step, -0.1 -0.09 -0.08 like this