beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Ty 2020-12-23T00:05:29.261500Z

Something at work that's been really painful is people breaking staging by simply not testing their code properly. I want to use lisp to generate some integration tests that verify the status of the API. Using JS the tests really aren't too bad but it's a ton of boilerplate. I'd love to write a DSL or something in Lisp to generate the integration tests. The end result will have to be working javascript code, though. Could anyone point me in the right direction? This is an opportunity for me to learn clojure better, solve a problem that will make my life easier when I get back to work in jan, and potentially convince my CTO that clojure is a real thing. Someone pointed me at parenscript. That's not really what I'm looking for. I don't want to write the tests in lisp and transpile them, I want to write lisp code that generates javascript code that does the testing for me.

seancorfield 2020-12-23T00:08:19.263300Z

@tylertracey09 If you can describe the sorts of actions or sequences of actions that need to be exercised by the tests, you could probably write Clojure Specs for those actions. Then you could use s/exercise to generate random, conforming sequences of actions. Then you'd just need code that took each action as input and wrote out the JS equivalent to taking that action against the API.

seancorfield 2020-12-23T00:10:12.265Z

We do something similar at work for some of our apps: we write Specs for "user actions", use those to generate sequences of "pretend users", and then we have an interpreter that runs those actions against the application being tested. That last stage for you would be to generate JS that ran those actions instead of just running the actions directly.

Ty 2020-12-23T00:11:36.267600Z

That last part is the one I'm least mentally prepared for right now. Is there an example I can reference that shows an idiomatic way to generate code in other languages? Or rather just generating larger things from templates of some kind? Ideally I would want to write little templates and replace parts of them rather than write long string things in lisp itself.

seancorfield 2020-12-23T00:11:43.268100Z

It's something that Eric Normand covers in his three "Property-Based Testing with test.check" courses at http://purelyfunctional.tv -- $49 / month. Definitely worth the money for at least a month or two while you work through those courses (some of his other courses are great too: his REPL-Driven Development course is worth a month's subscription just on its own).

Ty 2020-12-23T00:12:01.268600Z

I guess I could just read the templates from a file and replace strings of a certain format with actual data?

seancorfield 2020-12-23T00:12:21.269100Z

We use Selmer for template-driven generation of content -- mostly HTML but you could use it to generate JS too.

Ty 2020-12-23T00:12:37.269400Z

Interesting, I'll check into it. Thanks!

seancorfield 2020-12-23T00:12:39.269600Z

https://github.com/yogthos/Selmer

Ty 2020-12-23T00:12:57.269900Z

Would the specs approach be viable for cljs as well?

seancorfield 2020-12-23T00:13:20.270300Z

You mean for generating cljs at the end instead of JS?

Ty 2020-12-23T00:13:45.270700Z

No I meant for writing my little program in cljs instead of full clojure

Ty 2020-12-23T00:14:05.271500Z

Not that I'm particularly opinionated I just have a clojurescript envrionment/project up and running already.

seancorfield 2020-12-23T00:14:15.271800Z

I've no idea how much of the generative side of Spec (and test.check) is available for cljs. I don't do any cljs (just Clojure for a decade).

Ty 2020-12-23T00:15:04.272700Z

Gotcha. No worries I think I have some other ideas on that front as well if it doesn't pan out. Selmer looks dead simple, exactly what I needed for that portion.

seancorfield 2020-12-23T00:15:04.272800Z

Looks like test.check is written mostly as .cljc so it ought to work for cljs.

seancorfield 2020-12-23T00:15:48.273500Z

Selmer is pure Clojure, no cljs. But I think there are similar libraries that work for cljs. At least, I'd expect there to be...

holymackerels 2020-12-23T00:57:17.273700Z

thanks, that was super helpful

Nassin 2020-12-23T02:37:56.277Z

For a function that takes two args, is it possible to pass a single function call to it and destructure the arguments?

Nassin 2020-12-23T02:39:46.277900Z

(defn two []
  [3 4])

(defn add [x y]
  (+ x y))

Nassin 2020-12-23T02:40:15.278400Z

(add [[x y] (two)])

holymackerels 2020-12-23T02:41:07.279200Z

is what you want calling add with the value in two? it could be apply add (two)*

holymackerels 2020-12-23T02:41:29.279600Z

that's equivalent to add 3 4

Nassin 2020-12-23T02:43:50.280400Z

that changes the semantics, I want the last call to be add not apply

Nassin 2020-12-23T02:44:30.280900Z

I though this was possible with destructuring but don't remember

Nassin 2020-12-23T02:44:41.281200Z

going to read up on it again

Nassin 2020-12-23T02:45:37.281400Z

👍

2020-12-23T02:53:16.282400Z

@kaxaw75836 destructuring is a syntactic operation inside bindings (function args, let clauses, for clauses, etc.)

2020-12-23T02:53:27.282800Z

it can't be done in arbitrary forms

2020-12-23T02:54:07.283500Z

maybe a simpler way to put it: you can only destructure in specific contexts where you give a value a name

2020-12-23T02:55:36.284900Z

@kaxaw75836 for (add [[x y] (two)]) to work, it would need to rewrite the args to add before add sees them, and there's nothing (besides reader macros) that does that in clojure, and even reader macros don't turn one form into N forms in the parent

2020-12-23T02:56:46.285800Z

macros can rewrite parts of a form before emitting it (so something else with add inside it, like eg. let) can do precisely what you want

2020-12-23T02:56:59.286400Z

(let [[x y] (two)] (add x y))

Nassin 2020-12-23T02:57:14.286600Z

Ok, I see what you mean, yeah guessing hehe since I don't remember much Clojure

2020-12-23T02:57:38.287500Z

it's actually a really great property of clojure that makes the language easier to understand than most

2020-12-23T02:57:59.288200Z

(the fact that a child form can never rewrite a parent)

Nassin 2020-12-23T02:58:39.288900Z

yep, that's is exactly how I solved right now, was worried about recur not being in tail position with a let but it didn't complain (let [[x y (add)] (recur x y

2020-12-23T02:59:01.289300Z

right, tail positions look recursively into the expression tree

2020-12-23T02:59:09.289700Z

you can also recur from eg. a branch of an if

Nassin 2020-12-23T03:00:27.290100Z

yep

2020-12-23T06:29:33.292100Z

Yes, apply was the correct answer. It lets you call a function with the arguments being a sequence instead of having to be explicitly given.

(add (two))
;; Becomes
(add [3 4])
Which can work, but for it to work like that you need to make add into:
(defn add [[a b]]
  (+ a b))
Which I think is what you meant by using destructuring. This solution requires changing the function arguments to take a sequence as the first arg, which you then destructure into your arguments. You can also instead change the call site to do this, and that's done using apply.
(defn add [a b]
  (+ a b))
;; And now if you call it like
(apply add (two))
;; It becomes
(apply add [3 4])
;; Which becomes
(add 3 4)

2020-12-23T06:31:32.295100Z

I find that when writing functions that are a tad bit complex I tend to use a number of let bindings to process different steps instead of inlining them into one functional pipeline. Is this an acceptable Clojure style? I enjoy constructing the oneliners but I tend to not enjoy reading them after the fact.

phronmophobic 2020-12-23T06:42:41.297400Z

I find using let bindings to be great. I do also use the threading macros which have the main benefit of not requiring names for intermediate values.

2020-12-23T09:49:01.300Z

Hello! In the loop documentation page, there is this example:

(loop [xs (seq [1 2 3 4 5])
       result []]
  (if xs
    (let [x (first xs)]
      (recur (next xs) (conj result (* x x))))
    result))
I wonder to know why the vector is transform to a seq. (seq [1 2 3 4 5]) For better performance?

2020-12-23T10:20:54.300200Z

because empty vector is truthy, empty seq is not

(if [] 1 2) ;; => 1

2020-12-23T10:22:17.300400Z

but I think there is no need to transform vector to seq in loop declaration. it is better to check emptiness of a collection using (seq col)

(loop [xs [1 2 3 4 5]
       result []]
  (if (seq xs)
    (let [x (first xs)]
      (recur (next xs) (conj result (* x x))))
    result))

👍 2
2020-12-23T10:26:16.300600Z

Perfect answer! Thx @delaguardo 🙂

2020-12-23T10:32:13.301300Z

> but I think there is no need to transform vector to seq in loop declaration. it is better to check emptiness of a collection using Yes tottaly agree. It would have been clearer if it had been at the level of the condition and I would not even have asked about this.

2020-12-23T11:00:24.304100Z

To be even clearer - calling seq on an empty collection returns nil - not an empty sequence

Carlos Alcantara 2020-12-23T11:27:43.307100Z

Hello! If the range function is lazy, why does the following function create an infinite loop in REPL?

(defn even_squares
"Returns a seq of even squares less than the given number"
  [max]
  (for [x (filter even? (range))
        y [(* x x)]
        :when (< y max)] y))
but works fine with iterate inc 0

clyfe 2020-12-23T11:33:29.307200Z

:when is a filter not a break; when you take past the condition it enters n infinite loop looking to reify a head

Carlos Alcantara 2020-12-23T11:52:09.307400Z

thanks @claudius.nicolae, I just realized it also doesn't work with iterate inc 0

Carlos Alcantara 2020-12-23T12:17:55.311900Z

Could I ask what the idiomatic way to write this function?

clyfe 2020-12-23T12:28:29.312100Z

:while instead :when

Carlos Alcantara 2020-12-23T12:35:11.312300Z

but that also still creates an infinite loop

clyfe 2020-12-23T12:40:12.312500Z

You still end up with an infinite lazy seq, but now it always reifies an element, wheres before it would be stuck in an infinite loop after max takes

clyfe 2020-12-23T12:42:12.312700Z

Clojure 1.10.1
(defn even-squares [max]
  (for [x (filter even? (range)),
        y [(* x x)],
        :while (< y 100)] y))
#'user/even-squares
user=> (take 2 (even-squares 100))
(0 4)

clyfe 2020-12-23T12:44:27.312900Z

actually both have the same problem if you try to take beyond the elements that are yielded, at some point it just loops and never finds more

Carlos Alcantara 2020-12-23T12:44:49.313100Z

I think I found it

(defn even_squares
  [max]
  (take-while (partial > max) 
     (map (fn [x] (* x x)) (filter even? (range)))))

👍 2
Carlos Alcantara 2020-12-23T12:50:01.313400Z

thank you again for the help @claudius.nicolae

Raymond Usbal 2020-12-23T15:13:57.315Z

Hello, may I ask how popular is Fulcro among Clojure devs?

Raymond Usbal 2020-12-23T15:14:16.315300Z

For a beginner, would you recommend Fulcro?

borkdude 2020-12-23T16:20:19.315700Z

@raymond150 There is a thread about fulcro here: https://www.reddit.com/r/Clojure/comments/kibrfs/fulcro_as_fullstack_framework_or_else/

Raymond Usbal 2020-12-23T16:27:21.316100Z

@borkdude thanks!

2020-12-23T16:31:40.316200Z

calling first or next already invokes seq internally, which is a cached transformation - the seq call in the bindings is redundant but not an extra computation

2020-12-23T16:52:28.316400Z

OK, this explains why it works without seq. Thank you for this good information @noisesmith!

2020-12-23T16:59:10.316600Z

Relying on first and next is not really safe, eg.

(loop [xs []
       result []]
  (if xs
    (let [x (first xs)]
      (recur (next xs) (conj result (* x x))))
    result))
;; => Execution error (NullPointerException) at user/eval138 (REPL:5)
So seq is required somewhere, in bindings or in condition expression

👍 1
2020-12-23T17:01:08.316800Z

@delaguardo yeah, I hadn't considered the case where xs isn't a literal, but style wise I would put the seq call inside the if

2020-12-23T17:01:30.317Z

yep, I suggested to do the same

seancorfield 2020-12-23T18:18:41.322300Z

@raymond150 If you haven't already found it, there's a #fulcro channel here, so you could ask there about how beginners have gotten on with it.

Shantanu Kumar 2020-12-23T19:47:03.325800Z

Has anybody compared cljfmt and clj-kondo and weighed their pros and cons? Any other linter that people use?

mafcocinco 2020-12-28T19:34:58.172700Z

funny that apples and oranges are quite similar and it seems not unreasonable to be comparing them.

2020-12-23T19:51:42.326100Z

cljfmt is not a linter, its a code formatter

seancorfield 2020-12-23T19:52:19.326400Z

🍎 🍊

2020-12-23T19:52:23.326600Z

Good complimentary linters would be: clj-kondo, eastwood and kibit

2020-12-23T19:52:59.326800Z

Joker is another one, but it overlaps almost 1:1 with clj-kondo, with the latter supporting more things. So I would just fgo clj-kondo and not bother with joker

2020-12-23T19:53:42.327Z

Only clj-kondo is fast enough to be editor integrated and run on every code change. For eastwood and kibit, I'd run them less often, like pre-commit, or at build, etc.

Shantanu Kumar 2020-12-23T19:59:42.327800Z

Thanks!

2020-12-23T20:07:43.331400Z

I'm trying to solve a problem where I want to set a series of default values in a map only if the map is missing values. I can't create the map ahead of time but rather have to do this post map creation. Currently I have a function that takes a map, key, value and checks if the map has that key, and if it doesn't returns an updated map with the default value set (otherwise returns the same map). I am threading this my original map to multiple calls of this function like so:

(defn set-default [m key value]
  (if (m key)
    m
    (assoc m key value)))

(-> my-map
    (set-default :kw1 "default1")
    (set-default :kw2 "default2")
    ... etc)
I am sure there's a better way to do this. Any suggestions where to start? Edit: merge really should do the trick, right...

holymackerels 2020-12-23T20:12:43.332300Z

yeah, merge my-map into one with the defaults

🙌 1
2020-12-23T21:02:00.334400Z

Hi, I am using clj-http to get some image resources as follows:

(http/get url {:as :byte-array})
The server responses with 308 Permanent redirect. By looking into clj-http documentation, it seems to me that the request should be automatically redirected and I should get 200 response for the redirect. Instead, I am getting 308 response map. What am I doing wrong?

clyfe 2020-12-23T21:08:27.334800Z

Are you on most recent version?

dpsutton 2020-12-23T21:08:28.335Z

did you try the :redirect-strategy :lax?

phronmophobic 2020-12-23T21:35:12.338100Z

doesn’t having the seq call in the if statement (rather just once at the beginning) just add an additional seq call per iteration? I believe I added the example referenced above based on someone’s advice in this slack. I thought it might have even been @noisesmith ‘s idea

2020-12-23T21:36:00.338300Z

IIRC the seq of a vector is cached

phronmophobic 2020-12-23T21:38:09.340300Z

sounds good. I doubt it makes much of a practical difference. Just wondering for my own edification. the example with the seq call on the outside appears more than once as an example in the docs

2020-12-23T21:38:30.340600Z

oh - looking at the source it might not be cached

phronmophobic 2020-12-23T21:40:13.342700Z

oh right, that makes sense as if it was cached, you might have extra seqs hanging around in memory after iterating over a vector that is kept around after the iteration.

phronmophobic 2020-12-23T21:45:23.344800Z

next returns a seq (or nil if empty) and doesn’t calling seq on a seq just return itself? I think the “extra” seq calls would have a minimal impact

2020-12-23T21:48:46.345Z

(ins)user=> (def v [1 2 3])
#'user/v
(ins)user=> (identical? (seq v) (seq v))
false
the overhead of seq'ing it is low, but it's not cached like I thought it was

👍 2
phronmophobic 2020-12-23T22:00:33.345200Z

ah, I was thinking about the second iteration where it will have already been converted to a seq by next:

(let [x (next (seq [1 2 3]))]
  (identical? x (seq x)))
;; true

2020-12-23T22:47:25.345600Z

Yes, updated just now.

2020-12-23T22:48:05.345800Z

Actually maybe not, let me check.

2020-12-23T22:50:46.346Z

Ok, so updating from 3.10.3 to 3.11.0 fixed the problem.

👍 1
2020-12-23T22:51:24.346200Z

Ya, I did, but it should not matter since I am using GET request, this changes the behaviour for POST requests. But updating to the latest version fixed the problem.

dpsutton 2020-12-23T22:54:29.346500Z

agree. just thinking of things to try to investigate

2020-12-23T22:57:47.346700Z

thanks a lot for ideas 🙂