Fork me on GitHub
#clojure
<
2018-12-19
>
jaide03:12:05

What’s the recommended way to validate input in a macro? Say I expect a map with one key-value pair. Do I use conditionals and throw an error or is there a better way?

madstap04:12:48

Use spec with fdef. (s/fdef foo :args (s/cat :m (s/and map? #(= 1 (count %)))))

jaide04:12:28

For continual, run-time validation?

Alex Miller (Clojure team)04:12:04

One option is to write a spec with fdef - those will be automatically checked

Alex Miller (Clojure team)04:12:38

Another is to manually validate the input and throw an IllegalArgumentException or other similar one - there are a just a few treated like this by the new exception stuff

jaide04:12:16

Thanks a ton. I was looking at the implementation of https://github.com/clojure/clojure/blob/clojure-1.9.0/src/clj/clojure/core.clj#L4590 for reference. Is there any guidance on when you should consider spec vs manual validation?

madstap12:12:54

Both fdef (on a macro) and that validation code in for are checked at macroexpansion time and not at runtime.

Jin Choi03:12:55

Can you implement a function iterate* of your own version, not using clojure.lang.Iterate/create?

Alex Miller (Clojure team)04:12:33

Yes, and it used to be implemented this way before Clojure 1.7

Jin Choi01:12:36

Beautiful code. Thanks 🙂

Jin Choi03:12:31

And does yours work well with (take 10 (drop 100000000 (iterate* inc 1)))?

Hadi03:12:01

how to invoke method .whereEqualTo in class Query which is already extended in here (com.google.cloud.firestore CollectionReference)

hiredman04:12:54

I would check the java docs for whatever you are doing, my guess is whereEqualTo is a java varargs methods, which would mean you need to put the varargs in to an array

hiredman04:12:07

actually it looks like that error is coming from inside the google firestore code, likely because of a depedency versioning conflict

mogenslund08:12:39

Can anyone guide me to some example of how to use prepl? Google is not much help and the API documentation refers to parameters, I do not know how to configure, so some working example would be very helpful. I am not going to use it for anything special, yet, just experimentation.

Alex Miller (Clojure team)14:12:15

I’m going to write something up soon, just haven’t gotten to it

👍 16
michael_teter14:12:18

Can anyone recommend a good current (2017 or newer) book or detailed guide on web development with Clojure? Since there's no Rails or Phoenix for Clojure, it's hard to know which tools and libraries are ideal for general webdev.

michael_teter14:12:59

(or is my old 2014 Web Development With Clojure book still relevant?)

Eric Ervin22:12:54

A second edition of that book came out in 2016. If I remember them correctly, the 2nd edition has a lot more about the Luminous framework and Clojurescript. From the 1st edition, the ring/compojure/liberator bit is still relevant. The lib-noir library it mentions might be ancient history.

Noah Bogart14:12:30

I don't have any recommendations, but given what I've found when reading about this stuff, I'd say that the fundamentals haven't changed a lot (Clojure hasn't changed much since 2014), only the popular frameworks

michael_teter14:12:27

I guess my concern is that since I'll be learning a lot of new things, I don't want to invest energy on a component that's going out of style. Just hoping to find a set of tools that will last a couple/few years

Noah Bogart14:12:51

Reagent seems to be the current de jour react framework (with reframe supporting), and fulcro seems to be the other big name in frameworks

fulcro 8
michael_teter14:12:15

ok, thanks. I'll start looking there

Noah Bogart14:12:11

If you want to try fulcro, this is the spot to give it a go: http://book.fulcrologic.com/

michael_teter14:12:23

Oh super, thank you

lmergen14:12:51

@michael_teter there's also http://www.luminusweb.net/ , a "web framework" which is more like an opinionated set of libraries making up a proper web service

👆 8
💯 4
lmergen14:12:54

consider it a great starting kit

lmergen14:12:10

the documentation is pretty good as well

manutter5114:12:34

Yeah, these days I use luminus + re-frame to set up a basic web app shell and go from there.

lmergen14:12:21

i would initially leave out re-frame, it's a world in itself that might distract, depending on what you want to achieve

michael_teter14:12:40

Ok, if Luminus is popular/relevant, and its website promotes the book I already own, then perhaps I'll just start with the book despite its age 🙂

lmergen14:12:52

luminus is very stable

☝️ 8
lmergen14:12:02

has been around for years

michael_teter14:12:06

I don't need front end so much right now, so if I understand correctly that's where re-frame would be involved

manutter5114:12:20

Right, re-frame is front-end.

lmergen14:12:23

re-frame is react on steroids

lukas.rychtecky15:12:47

@michael_teter It depends what you need. If you want to create a web site, I’d go to Luminus. If you want to go for an API service I’d got for https://github.com/duct-framework/duct

michael_teter15:12:37

Thanks @lukas.rychtecky . I'm not doing an API, at least not at the moment. But I'll put duct in my save-list 🙂

👍 8
koala punch15:12:50

how do people generally new up maps for tests

koala punch15:12:06

so if i have a function that accepts a map as a parameter and accesses various keys

koala punch15:12:17

and i want to then feed in values from a test

koala punch15:12:05

i’m just manually creating the maps in the tests at the moment

koala punch15:12:26

but there’s no code completion so i end up flipping between the two files manually generating the map

koala punch15:12:29

feels a bit clunky

koala punch15:12:11

i was going to have a go at clojure spec to generate some values

koala punch15:12:37

i’m used to a typed language, java kotlin etc

koala punch15:12:50

when as i’m creating it, intellij suggests parameters for me

futuro16:12:00

@christopher.paul in my deftest body I generally let-bind the arguments to the function I'm testing.

koala punch16:12:20

i mean that if i have a function that accepts a map

koala punch16:12:25

its hard to then create the map for the test

koala punch16:12:31

but maybe i shouldn’t be passing maps into functions

koala punch16:12:41

i’ve posted some data to an endpoint as json

koala punch16:12:51

i’m then doin g alittle manipulating and sending it somwhere else

koala punch16:12:01

so i want to test the manipulation is right, at the moment maps are being passed around

cjsauer16:12:13

clojure spec gen is definitely a solid approach

koala punch16:12:28

yea that’s what i’m leaning towards

futuro16:12:47

Yeah, if you're trying to test out a ton of different values, spec is solid.

cjsauer16:12:58

Check out the exercise-fn fn I meant to say the check fn

cjsauer16:12:16

It can gen args based on a fn spec and validate that your constraints hold for all n invocations

futuro16:12:33

W.r.t passing maps into functions, I've found that to be the best approach for having maintainable code, and the openness of clojure maps makes them great for extensibility.

koala punch16:12:52

so if i have a function that accepts a map

koala punch16:12:55

and returns another

koala punch16:12:06

i’m trying to find the best way to assert that the mapping has been done correctly

koala punch16:12:24

in java tdd i’d create an input object and verify an output object

koala punch16:12:50

not sure whta the equivalent is in the lisp world

futuro16:12:07

I think that's what the link @cjsauer posted helps with.

futuro16:12:54

I'm not doing generative testing, so in my (deftest test-foo... form I have a let binding that defines my input and I have test assertions that the output of the function matches whatever shape I wanted.

koala punch16:12:15

yea that’s what i’ve been doing so far

koala punch16:12:20

but feel like i’m missing a trick

futuro16:12:24

where spec would help me by generating all of the input data and validating the output shape, I'm doing that by hand.

koala punch16:12:26

i probably need to get underneath spec

koala punch16:12:40

spec examples all have ::keys

futuro16:12:43

It definitely sounds like spec will line up more with what you're looking for.

koala punch16:12:49

does that mean that the map also needs to have ::key?

futuro16:12:01

Or rather, generative testing is what you're looking for, and spec can do that.

koala punch16:12:10

(def person {:firstname “chris”})

koala punch16:12:27

does that mean i can only use spec if the keys are :: ?

taylor16:12:14

you can use qualified or unqualified keywords, that's what the *-un keys are for in s/keys specs

taylor16:12:14

(s/keys :req-un [::firstname]) would work for unqualified :firstname keys in maps, but ::firstname refers to a spec for the key's value

cjsauer16:12:41

I believe what you're after is the :fn argument to fdef. It's the "glue" between input and output, it relates them via predicates.

:fn A spec of the relationship between args and ret - the
  value passed is {:args conformed-args :ret conformed-ret} and is
  expected to contain predicates that relate those values
https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html#clojure.spec.alpha/fdef

futuro16:12:41

A ::key expands into a namespace qualified key.

futuro16:12:52

So if you had a person namespace, and in it you did (def person {::firstname "chris"}) that would expand into {:person/firstname "chris"}

cjsauer16:12:15

>does that mean i can only use spec if the keys are :: ? No, spec can verify unqualified keywords as well

futuro16:12:21

Maybe said more concisely, the :: expands into :namespace/

koala punch16:12:10

i guess i need to just read up on spec

koala punch16:12:22

i sort of see how it can generate input and validate output

koala punch16:12:12

but it seems like it will validate the shape but not the contents

taylor16:12:42

it will validate the contents if you define a spec that matches the key spec's name

taylor16:12:55

(s/def ::firstname string?)

taylor16:12:08

(s/def ::my-map (s/keys :req-un [::firstname]))

taylor16:12:45

that will require a map that has :firstname key and check its value against ::firstname spec

taylor16:12:31

FYI there's also a #clojure-spec channel

koala punch16:12:16

but maybe that’s all it’s meant to do

futuro16:12:04

Based on the way I've heard spec talked about, I believe it was designed to allow validating contents.

koala punch16:12:07

so if the thing it returns has a list property that is ok to be empty

koala punch16:12:23

it might not be ok for itto be empty based on the input it was given

futuro16:12:46

That seems to be Rich's thing about spec vs statically typed languages: statically typed languages can't tell you about the content in a rich way, only in a vague shape way.

koala punch16:12:09

sorry i need to read mor about spec, thanks for your help

futuro16:12:15

:thumbsup:

cjsauer16:12:11

I'd start with the spec guide: https://clojure.org/guides/spec And then you can take a look at test.check: https://github.com/clojure/test.check There are some good generative testing examples in there.

cjsauer16:12:33

Generative testing works really well when the input and output are related by some invariant. Sorting for example, the output better be monotonically increasing/decreasing, or we've violated the invariant. Or e.g. a full-name function: the output better contain both first and last name somewhere in the output string. Functions that are map -> map might be more difficult to test via invariants due to their high degree of dimensionality; it really depends on what the function is doing...trying to test it generatively may illuminate design flaws, however, and help you decomplect things.

koala punch18:12:54

yea map to map is what i’m getting at

koala punch18:12:14

its easy to verify the shapes of inputs and outputs, but probablly better the examine the results in specific tests

koala punch18:12:28

otherwise it feels like the spec would get really complex

jaide19:12:41

(deftest my-macro-invalid-bindings-test
  (testing "my-macro throws error with invalid bindings"
    (is (thrown? IllegalArgumentException
                 (my-macro [] {})))
    (is (thrown? IllegalArgumentException
                 (my-macro [x] {x x})))))
I’m trying to write a unit test to make sure invalid input sent to my macros throws an IllegalArgumentException. However, they are raising errors when the test file is run. What is the recommended way to test a macro?

schmee19:12:50

I think I did it by using eval on the form

jaide19:12:28

It now runs but it’s throwing a ComplierException now

seancorfield20:12:48

@jayzawrotny That's on 1.10? Macro-expanding errors are wrapped in CompilerException in order to add all the location/phase data etc.

jaide20:12:14

Yeah 1.10

jaide20:12:19

Wait, I mean

seancorfield20:12:29

It would be nice if thrown? checked both the main exception and the cause. I'll add that to the clojure.test design wiki page that just got created.

jaide20:12:54

(eval `(my-macro [] {}))
` => CompilerException

Alex Miller (Clojure team)20:12:44

this was the subject of the discussion on clojure ml yesterday

Alex Miller (Clojure team)20:12:19

still deciding what, if anything, to do about it

seancorfield20:12:46

I added a note to the clojure.test page about it. I'll link to that issue.

Alex Miller (Clojure team)20:12:22

I have created variant assert-exprs to thrown? to look at cause in some cases. Checking both seems weird to me, but I guess is a viable alternative

Alex Miller (Clojure team)20:12:48

it’s best to look at the current impls of thrown? etc and do something similar

jaide21:12:13

Thanks again, I really appreciate it.

fiddlerwoaroof21:12:56

Is there any discussion of the performance impact of tap> when there's no active taps?

dpsutton21:12:19

do you mean if no taps are ever added or if all active taps have been removed?

dpsutton21:12:20

seems no problem? the tap loop isn't started up in the former case and it will remain blocked if the tapq is empty?

dpsutton21:12:07

(defonce ^:private tap-loop
  (delay
   (doto (Thread.
          #(let [t (.take tapq)
the take there will just patiently wait i believe

fiddlerwoaroof21:12:22

Cool, I've just been wondering about the viability of using tap> in production to get on-demand monitoring of data pipelines and such

fiddlerwoaroof22:12:08

And the documentation isn't really clear about whether this is primarily targeted towards dev-time use cases or not

Jan K22:12:40

I think tap> will always cost one add+take from an java.util.concurrent.ArrayBlockingQueue, even with no active taps

fiddlerwoaroof22:12:16

It's a non-blocking queue, isn't it?

fiddlerwoaroof22:12:28

From the docs "Will not block."

dpsutton22:12:05

ah yes. the values will go into that queue. i confused the tapq for values and the tapset for taps

Jan K22:12:06

It uses .offer to put, so it will drop beyond 1024 items and never block

jaide22:12:31

Created a solution for

(is (thrown? (eval `(my-macro [] {}))))
Could I get a code review please? Is this something worth putting into a library?
(is (thrown-in-macro? IllegalArgumentException `(my-macro [] {})))

seancorfield23:12:19

If you use Throwable->map instead of datafy, it'll run on Clojure 1.9 I believe? (when was that introduced)

jaide01:12:07

Thanks I’ll give that a shot

jaide01:12:47

Also that function is not searchable from http://clojuredocs.org and can not be added to see also of related functions. Should I file a bug with clojuredocs?

seancorfield02:12:35

Worth a try. I don't know how up to date it is supposed to be. I normally go to the API reference docs since they're auto generated and always up to date.

jaide02:12:38

Thanks. The page does exist, https://clojuredocs.org/clojure.core/Throwable-%3Emap, it’s just odd how hidden it is