clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
suren 2020-09-22T00:46:06.218Z

Why does lein repl throws Execution error (ClassNotFoundException) at <http://java.net|java.net>.URLClassLoader/findClass (URLClassLoader.java:466). when I try to run a function in my project?

seancorfield 2020-09-22T00:49:44.218100Z

Impossible to answer without knowing more about exactly what you are trying to do and exactly what the error is (i.e., more of a stacktrace etc).

sparkofreason 2020-09-22T00:54:43.218300Z

That was it. Had to make a little java shim that setContextClassLoader before loading Clojure. Is there any way to do this with just clojure?

suren 2020-09-22T00:55:33.218500Z

Got it working. Previously I used

(ns app.db)
(get-posts)
This threw
Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:466).
But when I use
(use 'app.db)
(get-posts)
It works. Don't exactly know why.

seancorfield 2020-09-22T01:20:06.218700Z

You should probably use (require 'app.db) and then (app.db/get-posts)

seancorfield 2020-09-22T01:20:23.218900Z

use is discouraged (and isn't available in ClojureScript as I recall).

seancorfield 2020-09-22T01:20:51.219100Z

(use 'app.db) is the same as (require '[app.db :refer :all]) which, again, is discouraged.

seancorfield 2020-09-22T01:21:34.219300Z

(require '[app.db :as db]) followed by (db/get-posts) would be preferable -- always use an alias rather than referring in names.

seancorfield 2020-09-22T01:22:12.219500Z

(ns app.db) will create an empty namespace called app.db so that's almost certainly not what you want in the REPL.

suren 2020-09-22T01:22:37.219700Z

Thanks, I will keep that in mind.

seancorfield 2020-09-22T01:22:59.220Z

As I said (ns app.db) creates an empty namespace. Nothing defined in it.

seancorfield 2020-09-22T01:23:18.220200Z

It sounds from this that you're fairly new to Clojure?

suren 2020-09-22T01:23:50.220400Z

Yup you are right. I am trying to jump to Clojure landscape.

seancorfield 2020-09-22T01:23:57.220600Z

I'd recommend asking questions in #beginners -- you'll find people are more patient there and less likely to assume knowledge you might not have yet.

seancorfield 2020-09-22T01:24:21.220800Z

The experienced folks in #beginners have opted in to helping new folks in great detail.

suren 2020-09-22T01:24:22.221Z

Hmm sweet. I will jump to that channel.

1
nixin72 2020-09-22T03:55:24.222900Z

Is there a Clojure/(Script) library for using Secure Remote Password Protocol? I know there's some Java/(Script) ones, but curious if there's something that exists for Clojure already or if I'm gonna have to define a wrapper around existing ones.

chetchan 2020-09-22T04:26:18.226600Z

Hello, Does any use Google BigQuery API with in Clojure? I am trying to query some insert jobs in a non-US non-EU region but when I do a get call to fetch the status of the job I get 404 Job not found This is my job spec which has location set in it

(defn- load-job-spec [table]
  (let [{:keys [project-id dataset-id table-name table-suffix]} table
        table-id (str table-name "$" table-suffix)
        time-string (-&gt;&gt; (LocalDateTime/now) (.format (DateTimeFormatter/ofPattern "yyyy-MM-dd_HH-mm-ss")))
        job-id (str table-name "-" table-suffix "-" time-string)
        job-reference (doto (JobReference.) (.setLocation "australia-southeast1") (.setJobId job-id) )
        table-reference (doto (TableReference.)
                          (.setProjectId project-id)
                          (.setDatasetId dataset-id)
                          (.setTableId table-id))
        config-load (doto (JobConfigurationLoad.)
                      (.setDestinationTable table-reference)
                      (.setEncoding "UTF-8")
                      (.setWriteDisposition "WRITE_TRUNCATE")
                      (.setSkipLeadingRows (Integer. 1))    ;; CSV headers
                      (.setMaxBadRecords (Integer. 0))      ;; Failure on garbled records
                      (.setAllowQuotedNewlines (Boolean/TRUE)))
        job-config (doto (JobConfiguration.) (.setLoad config-load))
        job (doto (Job.)
              (.setJobReference job-reference)
              (.setConfiguration job-config))]
    job))
This below code is where first run an insert job to load a CSV file into BQ dataset in Australia region and then try the get status on it with a get call below, but the get fails with 404 job not found. I am not sure how I can pass location in the get call. Any help would be great. Thx
(defn load-file-bigquery [fullpath]
  (let [name (s/replace fullpath #"^.*4244_" "")
        name (s/replace (s/lower-case name) #"_\d{6}.csv$" "")
        name-parts (s/split name #"_")
        table-suffix (last name-parts)
        table-name (s/join "_" (butlast name-parts))
        content (FileContent. "application/octet-stream" (File. fullpath))
        project-id (cfg/config :project-id)
        job-spec (load-job-spec {:project-id   project-id
                                 :dataset-id   (cfg/config :dataset-id)
                                 :table-name   table-name
                                 :table-suffix table-suffix})
        job-id (-&gt;&gt; job-spec (.getJobReference) (.getJobId))
        _ (log/info "BigQuery Load Job : " job-id " : " fullpath)
        bq @bigquery-conn                                   ;; Blocks until initialised
        job-obj (-&gt;&gt; bq
                    (.jobs)
                    (#(.insert % project-id job-spec content))
                    (.execute)
                    )
        status (-&gt;&gt; job-obj (.getStatus) )]

    (log/info "job-obj outside "job-obj)
    (loop [status status]                                   ;; Waiting until successfully processed
      (log/info job-id " : " (-&gt;&gt; status (.getState)))
      (if (= "DONE" (-&gt;&gt; status (.getState)))
        (do (log/info "Status seems done?")
          (if-let [errors (.getErrors status)]
            (do
              (log/info "seems like we have errors")
              (vec (map #(.getMessage %) errors)))
            nil))
        (do
 
          (Thread/sleep 3000)

          (recur (-&gt;&gt; bq
                      (.jobs)
                      (#(.get % project-id job-id))
                      (.execute)
                      (.getStatus))
                 ))))))
I have the equivalent java code but I need this to work in Clojure code above
Job job = <http://bigquery.jobs|bigquery.jobs>().insert(PROJECT_ID, runJob).execute();
String status = job.getStatus().getState();
while(!status.equalsIgnoreCase("DONE")) {
  status = <http://bigquery.jobs|bigquery.jobs>().get(PROJECT_ID, job.getId()).execute().getStatus().getState();
  System.out.println("Status: " + status);
  Thread.wait(1000);
}

2020-09-22T05:36:54.228Z

= is not the same thing as the equalsignorecase method

2020-09-22T05:41:55.229200Z

My guess is you have some error creating the job, but you never print errors outside before starting to loop

chetchan 2020-09-22T06:20:55.229800Z

sorry for confusing post, i put the java code just as an equivalent

chetchan 2020-09-22T06:22:19.230100Z

job runs ok, however I cannot get the status unless I pass the location to get call

(-&gt;&gt; bq
                      (.jobs)
                      (#(.get % project-id job-id))
                      (.execute)
                      (.getStatus))
How can I know that if the .get here has a particular method in it that I can use etc? Does it resolve automatically if it exists since I am referring to the java classes here

wombawomba 2020-09-22T08:00:02.231100Z

How come (let [foo "bar"] (eval (read-string "foo"))) doesn’t work? How can I get eval to pick up locally scoped bindings?

wombawomba 2020-09-22T08:10:43.231300Z

Alright, I found this, which says that I either need to def or declare my bindings/vars: https://stackoverflow.com/a/6221829

wombawomba 2020-09-22T08:11:51.231700Z

Unfortunately, defing doesn’t seem to work either… Or, it does when I do it in the REPL, but not in my actual namespace. Any ideas why?

vlaaad 2020-09-22T08:23:52.232400Z

You’ll need a macro that captures lexical environment and feeds it into eval

Raziyeh Mohajer 2020-09-22T09:40:19.234200Z

Hello everyone, I'm using the jackdaw library for connecting to Kafka I want to use a key.serializer and value.serializer for Kafka producer other than Kafka's own serializers. this is my code for the producer

(def serialize nippy/freeze)

(def Kafka-Edn-Serializer
  (reify Serializer
    (serialize [this topic headers data]
      (serialize data))
    (serialize [this topic data]
      (nippy/freeze data))
    (configure [this _ _])
    (close [this])))

(def producer-config
  {"bootstrap.servers" "localhost:9092"
   "key.serializer"    (-&gt; Kafka-Edn-Serializer
                           .getClass
                           .getName)
   "value.serializer"  (-&gt; Kafka-Edn-Serializer
                           .getClass
                           .getName)
   "acks"              "all"
   "client.id"         "foo"})

(defn -main []
  (jc/producer producer-config)
  )
I can run it in repl but when I use lein run to run this I got the following error
Invalid value producer_example$reify__12915 for configuration key.serializer: Class producer_example$reify__12915 could not be found.
How should I solve this problem?

wombawomba 2020-09-22T10:01:14.234400Z

makes sense, thanks 🙂

2020-09-22T10:42:37.234600Z

Have you tried using deftype instead of reify?

2020-09-22T10:43:12.234800Z

I think reify doesn't create an actual type, unless aot compiled.

Raziyeh Mohajer 2020-09-22T11:04:39.235Z

I will try it thanks

fabrao 2020-09-22T12:46:19.237600Z

Hello all, are you using lein to create templates for base project? Is there other way instead of lein to do it?

vlaaad 2020-09-22T13:11:25.237900Z

mkdir src &amp;&amp; echo '{}' &gt; deps.edn 😁

vlaaad 2020-09-22T13:12:07.238200Z

ah, you asked about creating templates, not a particular template to use instead of lein..

vlaaad 2020-09-22T13:12:54.238400Z

see https://github.com/seancorfield/clj-new

Ashwin Bhaskar 2020-09-22T14:03:58.238900Z

I am using https://github.com/uswitch/opencensus-clojure tracing library which follows the opencensus standard. Here is my code:

(def routes ["/" [["ping" {:get (constantly {:status 200
                                             :body   "pong"})}]
                  ["foo" {:post (-&gt;&gt; foo-handler
                                      (trace/span "foo-file-handler"))}]
                  [true (constantly {:status 404})]]])

(m/defstate server
  :start (jetty/run-jetty (bd/make-handler routes) {:port  8080
                                                    :host  "localhost"
                                                    :join? false})
  :stop (.stop ^Server server))

(m/defstate tracer
  :start (jaegar/report "foo-service")
  :stop (jaegar/shutdown))
From the repl, I start the `server` and `tracer` states. I have `jaegar-all-in-one` docker running in my machine. I can see the `jaegar` dashboard in my browser too. But the traces don’t show up there when I call the API. I tried to narrow the problem down by just calling `(trace/span "some-trace" (println "foo))` from the repl. Even this does not show up on the dashboard. What am I missing here?

Ashwin Bhaskar 2020-09-23T11:43:37.021600Z

My bad, the traces are probabilistic as mentioned in the README of the library. I was using the default probability which less than 0.1 (I guess). I changed the probability to 1. Now the traces show up

isak 2020-09-22T15:54:26.243200Z

For calls to assoc with multiple key-value pairs, would it make sense to use a transient? (In this code): https://github.com/clojure/clojure/blob/ee3553362de9bc3bfd18d4b0b3381e3483c2a34c/src/clj/clojure/core.clj#L192-L199

isak 2020-09-22T16:19:31.243400Z

Maybe not, because not everything passed to assoc will be a IEditableCollection

alexmiller 2020-09-22T17:16:14.244500Z

☝️

fabrao 2020-09-22T17:22:50.244700Z

@vlaaad yes, creating templates for use as starting of a new project

strsnd 2020-09-22T17:52:49.246400Z

Are there idioms in clojure which allow me to partially process a seq and return the rest of the sequence without iterating over it twice (as with split-at)? It is easy to write myself but wondering if I am missing something?

Linus Ericsson 2020-09-24T11:06:05.003Z

Maybe you could us reduce in combination with reduced which terminates reduce loops. Then you can add both the processed items results and the rest of the sequence.

strsnd 2020-09-24T13:08:32.019Z

@emccue coming out of a an iterator-seq I have three types of objects, which are not interleaved. the first type of objects I need to put in some kind of map, while the rest is being processed using earlier created map and are processed with side effects. Naturally partition-by would be useful but it keeps the head of the first stream alive and thus I run into out of heap. For me it is important to not walk this long stream multiple times while also not keeping any heads alive while processing them. a custom loop recur works for the moment but feels non-idiomatic.

strsnd 2020-09-24T13:08:48.019200Z

@oscarlinusericsson I haven't thought about reduced interface, I will play with this idea a bit. Thanks!

emccue 2020-09-24T13:28:54.033600Z

The simplest cases of loop/recur do usually map to a call to reduce

emccue 2020-09-24T13:28:57.033800Z

so

emccue 2020-09-24T13:32:09.034Z

(let [long-lazy-seq ...
      data-from-before {...}
      process! (partial do-side-effect! data-from-before)]
  (loop [remaining long-lazy-seq
         collected []]
    (if (empty? remaining)
      collected
      (if (predicate? (first remaining))
        (do (process! (first remaining))
            (recur (rest remaining) collected))
        (recur (rest remaining)
               (conj collected (first remaining)))))))

emccue 2020-09-24T13:32:24.034200Z

so this (which is what it sounds like you have)

emccue 2020-09-24T13:33:03.034400Z

can map to a separate reducing function (which is kinda hard to name, but c'est la vie)

emccue 2020-09-24T13:35:57.034700Z

(let [long-lazy-seq ...
      data-from-before {...}
      process! (partial do-side-effect! data-from-before)
      reducing-function (fn [collected item]
                          (if (predicate? item)
                            (do (process! item)
                                collected)
                            (conj collected item)))]
  (reduce reducing-function [] long-lazy-seq))

emccue 2020-09-24T13:36:11.034900Z

so roughly this - reduced isn't super helpful except to terminate early, which it doesn't sound like you want to do

strsnd 2020-09-24T13:37:47.035100Z

Hm, thanks. My code indeed looks a bit like your earlier one. I wanted to actually have some kind of split in between when it operated on the first types of objects and the second types, which seems hard to do in reduce (e.g. using a transient to construct the map of the first elements).

strsnd 2020-09-24T13:38:54.035400Z

While I generally prefer map/reduce, in this case something like:

(let [[first-elem-map r] (do-while is-first-elem? process r)
      [_ r] (do-while (is-second-elem? side-effect-on-second-fn r)
      _ (do-while (last-element? side-effect-on-thid-fn))]
  ...)
looks nicer to me.

emccue 2020-09-24T13:43:32.035900Z

I usually don't like reduce and prefer the explicit loop, but thats me. You can maybe handle the different cases via a dispatch thing

emccue 2020-09-24T13:43:54.036100Z

so a multimethod or just a condition on what function to call

emccue 2020-09-24T13:44:09.036300Z

but conceptually you need to handle each item in the same "place"

emccue 2020-09-24T13:45:19.036500Z

splitting a lazy sequence into logical parts necessitates that at least one will hold on to the head while the one you choose to evaluate first is running

emccue 2020-09-24T13:47:07.036700Z

you can maybe "queue up" side effects with filter

emccue 2020-09-24T13:50:36.036900Z

(let [long-lazy-seq ...
      data-from-before {...}
      process! (partial do-side-effect! data-from-before)]
  (-&gt;&gt; long-lazy-seq
       (filter (fn [item] (if (predicate? item)
                             (do (process! item) false)
                             true)))
       (reduce (fn [collected remaining-item]
                 (conj collected remaining-item)
               [])))

emccue 2020-09-24T13:50:54.037100Z

but uhh, beauty is in the eye of the beholder

strsnd 2020-09-24T14:03:06.037300Z

I think I stay with my do-while approach, which looks like that:

(defn do-while [pred f s coll]
  (let [c (first coll)]
    (if (pred c)
      (recur pred f (f s c) (rest coll))
      [s coll])))
I might try to use a bit of chunking/batching there depending if that shows up in performance profiles.

👍 1
strsnd 2020-09-24T14:06:13.037900Z

I tried the multimethod approch as well, but this loop might process a lot of items and I am unsure if I add just too much overhead in that case.

strsnd 2020-09-24T14:09:23.038100Z

The filter stuff looks like a neat idea, but I agree, it feels weird to do anything else in filter than filtering.

wombawomba 2020-09-22T19:22:57.248400Z

How can I run a shell command in the foreground (i.e. with stdin/-out/-err attached) in Clojure? Ideally I’d like the command to believe it’s in a TTY as well.

alexmiller 2020-09-22T19:23:30.248800Z

there is a clojure.java.shell namespace with some support for this (not sure about tty)

alexmiller 2020-09-22T19:23:58.249300Z

lately though I've actually been using Java interop directly to the newer Java ProcessBuilder api

alexmiller 2020-09-22T19:24:45.249900Z

that has full support for a variety of options of handling in/out/err

ghadi 2020-09-22T19:24:49.250200Z

seconding that ^

ghadi 2020-09-22T19:25:01.250800Z

java >=9 give you a completion callback too

wombawomba 2020-09-22T19:25:06.251Z

hmm.. how would I attach stdin/out/err with clojure.java.shell?

alexmiller 2020-09-22T19:25:47.251500Z

if you (doc clojure.java.shell/sh) you can read all the options

alexmiller 2020-09-22T19:25:56.251800Z

but I find ProcessBuilder is actually easier to use

wombawomba 2020-09-22T19:26:39.252300Z

I did look at that doc, but AFAICT it doesn’t support what I’m trying to do

alexmiller 2020-09-22T19:27:14.252800Z

if you want more clojure-y flavor, there is stuff like https://github.com/clj-commons/conch

alexmiller 2020-09-22T19:27:58.253800Z

if you're doing a lot of this stuff, that can be pretty nice too

wombawomba 2020-09-22T19:29:43.255400Z

I’m also not sure if there’s an easy way to run in the foreground with the standard streams hooked up using the ProcessBuilder API. I tried Googling for it but the answers I found all involve manually redirecting output from/to the process (e.g. https://stackoverflow.com/a/17393606), which seems a bit cumbersome

wombawomba 2020-09-22T19:34:46.258200Z

I also tried using conch, but couldn’t figure out how to get it working. The closest I could get was doing something like:

(require '[me.raynes.conch.low-level :as sh])
(def proc (sh/run "bash"))
(future (sh/stream-to-out proc :out))
(future (binding [*out* *err*] (sh/stream-to-out proc :err)))
(sh/feed-from proc *in*)
However, this doesn’t really work because everything gets buffered (for instance, I need to manually send an EOF via ctrl-D for my input to get sent to the process).

seancorfield 2020-09-22T19:36:41.258700Z

Isn't that because *in* and *out* are buffered streams?

seancorfield 2020-09-22T19:37:13.259300Z

Doing unbuffered console I/O in Java is kind of a pain, as I recall...

wombawomba 2020-09-22T19:38:39.259600Z

Yeah that’s probably it

ghadi 2020-09-22T19:42:53.260700Z

look at inheritIO options on process builder

wombawomba 2020-09-22T19:43:37.261600Z

@ghadi heh, I actually just tried that 🙂 basically doing this: https://stackoverflow.com/a/49706743 works, except that I can’t see my output

wombawomba 2020-09-22T19:45:08.263100Z

e.g. running echo hello in my bash proc works, but I can’t see what I’m typing:

$ (def pb (java.lang.ProcessBuilder. (into-array String ["bash"])))
$ (.inheritIO pb)
$ (.waitFor (.start pb))
bash-3.2$ hello
bash-3.2$

wombawomba 2020-09-22T19:49:46.264Z

perhaps this could be because I’m in a REPL though..

wombawomba 2020-09-22T19:51:43.264700Z

Yeah, that was it 🙂 It works when I use lein trampoline run

wombawomba 2020-09-22T19:51:49.264900Z

Thanks!

agigao 2020-09-22T20:05:24.266Z

@alexmiller Hi Alex, any plans about the next edition Clojure Applied and/or Programming Clojure?

alexmiller 2020-09-22T20:06:15.266500Z

hasn't really been much new since 3ed of Programming Clojure so no plans there

alexmiller 2020-09-22T20:06:47.267200Z

funny enough, we just passed the 5 yr anniversary for Clojure Applied and I was talking to the publisher about it this week. considering whether to do a new ed

alexmiller 2020-09-22T20:07:14.267500Z

anything you'd be burning to have included? :)

seancorfield 2020-09-22T20:08:37.268400Z

@alexmiller Would a new Ed of C.A. deprioritize the use of records somewhat? I recall you mentioning something about that.

seancorfield 2020-09-22T20:09:14.268800Z

(and could it also normalize the use of qualified keywords in hash maps from the get-go? 🙂 )

alexmiller 2020-09-22T20:10:27.269400Z

yes on maps vs records, dunno on qualified keywords

alexmiller 2020-09-22T20:10:51.269800Z

I have a list of ideas already, just wondering what others would be interested in :)

alexmiller 2020-09-22T20:14:11.272300Z

having just gone through the whole book again, I think probably 85% is still pretty solid. I mostly have stuff I'd want to add to it (spec - would replace the schema use for validation and expand generative testing in ch 8), clj stuff, would like to add stuff on macros and java interop

👍 1
dpsutton 2020-09-22T20:15:38.272600Z

big plus on generative testing

2020-09-22T20:16:20.273Z

(to add to that, what I often struggle with in generative testing is how to define my properties)

2020-09-22T20:16:31.273400Z

(or, what, ‘good’ properties are)

2020-09-23T21:35:07.087500Z

So I read through the generative testing section again. I have a hard time pinpointing what my exact issue is when I said that I find it difficult to define properties. I think the section is very clear, and definitely hints to what I need. But, I don’t have the intuition when to choose property based testing except for some set patterns (serialize/deserialize springs to mind). Another problem I experience in practice is that properties I do come op with are very broad (serialize -> deserialize = identity) which I struggle to properly assess how useful those are (in a proof burden kinda sense, this generatively tested property ‘buys’ me the assurance that the system does or does not do X). Or, I have trouble finding properties underlying implementations (say, the textbook example of a sort algorithm guarantees orderness with ‘orderness’ being the property and ‘quick-sort’ being a way to get to that) which leads to a feeling of testing by ‘asserting the implementation’ (if you get what I mean). So I guess that most places I read about property based testing explain me how (if I may use an analogy) a screwdriver works, show me examples of screws, but so far, I don’t feel like I have a grasp on starting to realise that a thing is a screw. (Does that make sense?)

2020-09-23T21:36:08.087700Z

(Not all about the book, btw, I’m just trying to … also for myself, pinpoint what makes it hard for me to grasp property based testing)

2020-09-23T21:40:04.087900Z

(I guess you heard this a million times, but the last listing on page 154 is duplicated on 155, or actually the other way around)

alexmiller 2020-09-23T22:00:36.089900Z

it does make sense - I think it is truly difficult to discern what properties are useful to validate. I spent a lot of time thinking about it when writing that chapter. spec has highlighted an additional way to think about it in terms of the in/out of a function too and that's what I'd like to add (although spec 2 is likely to going to change in practice quite a bit)

alexmiller 2020-09-23T22:01:13.090100Z

and yes, there are a number of copy edit issues in the book. we've got lots of errata to go through

slipset 2020-09-24T06:35:27.000300Z

I would think that we’d need to tease the subject a part a bit. I would imagine that the job of Clojure Applied would be to showcase how property based testing could/should be used in Clojure. Writing extensively on how to do property based testing in the general case would probably be outside the scope of C A? Oscar Wickström has a book on this https://leanpub.com/property-based-testing-in-a-screencast-editor and did a talk on it at flatMap(Oslo) in 2019 https://2019.flatmap.no/talks/wickstrom in which, IIRC, there were som links to other resources on property based testing.

2020-09-24T19:27:18.063400Z

That sounds interesting, I’ll definitely watch the talk

alexmiller 2020-09-22T20:16:36.273600Z

the only hard parts are defining properties and defining generators :)

alexmiller 2020-09-22T20:16:40.273800Z

also the only parts :)

alexmiller 2020-09-22T20:17:31.274800Z

I took my best swing at this in the current edition, would be curious if you've read it and what you thought

dpsutton 2020-09-22T20:17:38.275100Z

https://grep.app/search?q=prop/for-all is an assist. and looking for test.check :as is another good search term

2020-09-22T20:17:57.275400Z

Its at the office, I happen to go there tomorrow, I’ll read through it

2020-09-22T20:18:18.275800Z

Yeah I found a blog from the F# community

2020-09-22T20:18:39.275900Z

oh thats also clever!

alexmiller 2020-09-22T20:19:54.276800Z

the existing book covers generative testing with test.check, I'd mostly be looking to augment that by talking about how spec plays into it

2020-09-22T20:20:27.276900Z

uh no, there was another with pretty pictures

2020-09-22T20:20:55.277100Z

Ah yes, this one: https://fsharpforfunandprofit.com/posts/property-based-testing-2/

seancorfield 2020-09-22T20:30:51.277600Z

Yeah, +💯 on using Spec instead of Schema and adding more generative testing stuff. I've been working my way through @ericnormand’s Property-Based Testing courses online and finding lots more places to use generative testing than I imagined possible!