Fork me on GitHub
#beginners
<
2022-04-22
>
Jon Olick03:04:45

isn’t lazy-seq basically the same thing as delay?

Jon Olick03:04:05

Like a marriage between repeatedly and delay

jumar07:04:45

There's some relationship between them. In Joy of Clojure, they used it build a variant of lazy seqs: https://github.com/jumarko/clojure-experiments/blob/master/src/clojure_experiments/books/joy_of_clojure/ch06.clj#L291-L329

didibus01:04:40

Ya kind of, at a high level.

noisesmith19:04:38

a big difference is you can print a delay without forcing it.

zackteo04:04:08

Hello, how do I give my map argument to a function like so

(defn mk-pool
  [& {:keys [cpu-count]}]
  cpu-count))
(mk-pool {:cpu-count 5}) doesn't seem to work

phronmophobic04:04:06

What version of clojure are you using? I think that syntax was added in 1.11.

phronmophobic04:04:24

You use the following syntax with any version any version 1.2.0 or later:

(mk-pool :cpu-count 2)

zackteo04:04:01

ooo so (mk-pool {:cpu-count 5}) works in clojure 1.11? Im on 1.10.3

zackteo04:04:07

thank you!

zackteo04:04:44

is there some place i can read about this?

pez08:04:27

I want to make a thunk out of a function call w/ some args. (partial my-fn arg1 arg2) works, but makes it look like I'm expecting more args to be added... Should I care? Is there a nicer way to do it?

delaguardo08:04:12

#(my-fn arg1 arg2) as a possible alternative, but personally I wouldn't care

pez08:04:49

Ah, thanks! And of course! I tend to avoid #() so it was out of my mental reach. 😃 I think I prefer #() over partial here, b/c the reasons in OP.

Ben Sless11:04:31

If you do it more than once, it warrants making a macro out of it. I like using $ for it:

(defmacro $ [& body] `(fn ~'thunk [] ~@body))

pez11:04:34

(($ (str 'foo 'bar)))
"foobar"
Very nice! Is it possible to get the function name in there as well? So that instead of
($ (str 'foo 'bar))
#function[user/eval63202/thunk--63203]
We would get
($ (str 'foo 'bar))
#function[user/eval63202/str-thunk--63203]

Ben Sless14:04:16

Not as simple, but sure:

(defmacro $ [& body]
  (let [name (or (and (sequential? (first body))
                      (symbol? (ffirst body))
                      (-> body ffirst name (str "-thunk") symbol))
                 'thunk)]
    `(fn ~name [] ~@body)))

($ (str "foo" "bar"))

🙏 1
Ben Sless14:04:32

out of curiosity, what are you thunking (about)?

pez14:04:22

I'm using a third party library and don't want to unit test it, so am using a ”command”-ish approach where I have different implementations for :test and for :prod (I'm using multimethods for it). For tests I don't want to write a lot of different implementations so have made just one that expects a thunk.

👍 1
mister_m15:04:34

I seem to have shot myself in the foot pretty good - I am unable to start my cider repl due to a class not found exception being encountered after adding both muuntaja and cheshire to my lein project at the same time. Looking at the trace it appears that the error is coming from something in muuntaja . I presume this is because these two libraries maybe have conflicting versions of dependencies on the classpath. I am able to get going again if I remove my cheshire dependency. How would I resolve something like this without removing the dependency? Also, how can I prevent getting into this situation in the first place?

mister_m15:04:31

I ran lein deps :tree-data and there are a lot of suggestions on exclusions. It is a pretty large dependency tree so maybe that is expected.

mister_m15:04:34

What do these exclusions do really?

dorab15:04:52

Generally, exclusions tell the dependency resolver to not include a particular dependency of an artifact (because a different but hopefully compatible version will be included in a different artifact).

Scott Starkey15:04:19

Is there a common idiom for the following or?

(defn candy? [x]
   (or (= x :butterfinger)
       (= x :snickers)
       (= x :zagnut)))

dgb2316:04:31

Could be a set

dgb2316:04:04

Sets are functions, if you pass an element it contains you get the element back, otherwise nil

dgb2316:04:16

Don’t mind me, missed the other comments 😅

fogus (Clojure Team)15:04:00

you could use a set: (contains? #{:snick :bf :zag} x)

Martin Půda16:04:37

Isn't it more idiomatic to just use set as function?

(#{:butterfinger :snickers :zagnut} :zagnut)
=> :zagnut
(#{:butterfinger :snickers :zagnut} :nut)
=> nil

fogus (Clojure Team)16:04:37

You can, but predicates should return true/false. You could also use (boolean (#{:b :s :z} x)) but I personally like the clarity of using contains? YMMV

👍 1
1
Scott Starkey15:04:21

That’s better!

Miguel17:04:40

How could I go about setting the MathContext for my whole application? Would something like this be truly global, including multiple threads? Is there a more idiomatic way of doing this? (alter-var-root #'*math-context* (constantly (MathContext. 5 RoundingMode/HALF_EVEN)))

Martin Půda17:04:27

Did you try (set! *math-context* <some-desired-value>) ?

Miguel17:04:56

Yes I did, it works on the repl but not on the built app. Can't change/establish root binding of: *math-context* with set

Miguel17:04:07

> Currently, it is an error to attempt to set the root binding of a var using set!, i.e. var assignments are thread-local. In all cases the value of expr is returned. https://clojure.org/reference/vars

didibus02:04:03

If you don't start your app using clojure.main, the bindings won't be setup. Are you starting the app using a gen-class AOT?

didibus02:04:44

In that case, you need to bind them in your main function:

(defn -main [& args]
  (binding [*math-context* *math-context*]
    ;; Now using (set! *math-context* ...) will work
    ...))

didibus02:04:51

You can also just use this: https://clojuredocs.org/clojure.main/with-bindings So wrap your -main body in that macro

(ns foo
  (:require [clojure.main :as m]))

(defn -main [& args]
  (m/with-bindings
    ;; Now using (set! *math-context* ...) will work
    ...))

didibus03:04:34

That said, normally those bindings are compile time options. I'm not sure about *math-context*, but my guess is it is as well. That means, if you want to set those globally, you can do it in the compilation itself. For example if you use tools.build: https://clojure.github.io/tools.build/clojure.tools.build.api.html#var-compile-clj You can use :bindings on the compile-clj step.

lepistane18:04:51

How do i connect to clj repl. I started clj repl with clj -A:dev (dev alias has only extra path for /dev folder that has user.clj file) but when i try to cider-connect to localhost i get a message

nrepl Dirrect connection to localhost:36603 failed
Does anyone know why is that? I can cider connect to repl when i start it with lein repl

phronmophobic18:04:36

Usually, the easiest way to start a repl and connect with cider is cider-jack-in

phronmophobic18:04:59

clj -A:dev starts a repl, but cider-connect expects an nrepl

phronmophobic18:04:52

If you'd like to start the nrepl yourself, https://nrepl.org/nrepl/usage/server.html

lepistane18:04:42

oohhh ok that's great information. Maybe i am approaching this in a wrong way then. So i would like to cider-jack-in and i would like to do (start) which is in dev/user.clj At the moment when i cider jack in i have to evaluate user namespace and then i can (start)

phronmophobic18:04:47

What does (start) do?

lepistane18:04:22

it's just a wrapper for mount.core/start

(defn start []
  (mount/start)) 

phronmophobic19:04:14

Unfortunately, I'm not sure what the best way to run a command on cider-jack-in in emacs. You could also ask in #emacs.

phronmophobic19:04:22

I'm probably running an old version of cider and emacs. I set a the variable cider-clojure-cli-parameters to "-M:dev:project -m nrepl.cmdline --middleware '%s'" so that I can add extra aliases.

phronmophobic19:04:13

I also don't really use user.clj

lepistane19:04:46

i will ask there as well. Oh? Why not? How do you start your dev environment

phronmophobic19:04:30

I would usually just run it manually I guess

phronmophobic19:04:23

You could also try adding a defadvice to your cider-jack-in command to run it, but I'm not really sure what the best way is since that's not something I usually do

👍 1
César Olea19:04:09

in your deps.edn dev alias you could add something like this:

{:extra-deps {cider/cider-nrepl {:mvn/version "0.28.3"}}
         :main-opts ["-m" "nrepl.cmdline" "--middleware"
                     "[cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor]"
                     "-e" "\"(mount.core/start)\""]}

César Olea19:04:39

that way running clj -A:dev will run an nrepl and execute mount.core/start, then you can cider-connect-clj to it.

lepistane19:04:19

This works, this is basically the comment cider jack in uses to initiate te repl. When i cider-connect i can use (start) right away without the need to evaluate the namespace. So what exactly prevents plain cider jack in on loading namespace ? this is my dev alias now

{:extra-paths ["dev"]
                 :extra-deps {cider/cider-nrepl {:mvn/version "0.25.1"}}
                 :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}

phronmophobic19:04:27

I think cider-jack-in might override some of that

phronmophobic19:04:00

If you do C-u M-x cider-jack-in it should show you the full command it uses (and allow you to edit it)

phronmophobic19:04:17

I think there are some variables you can customize to tweak the command it uses though

lepistane19:04:54

I thought i shared it...

/usr/local/bin/clojure -Sdeps '{:deps {nrepl {:mvn/version "0.7.0"} cider/cider-nrepl {:mvn/version "0.25.1"}}}' -m nrepl.cmdline --middleware '["cider.nrepl/cider-middleware"]'
It's 1:1

phronmophobic19:04:14

It doesn't look like it's using the dev alias

lepistane19:04:52

true it seems that this might help https://docs.cider.mx/cider/basics/up_and_running.html#clojure-cli-options I will check it out and report back Thank you both for your time

👍 1
phronmophobic19:04:01

You can do also do M-x customize-group and check all of the groups that start with cider

practicalli-johnny03:04:57

A .dir-locals.el file that defines the Clojure CLI aliases to include when running a REPL is a simple approach https://clojurians.slack.com/archives/C0617A8PQ/p1650665847187939?thread_ts=1650655838.638489&amp;cid=C0617A8PQ

👍 1
mister_m19:04:52

If I'm in a namespace and define a spec with s/def - can I import that spec from a different namespace? I don't actually see the spec when I'm browsing my namespace in cider

Alex Miller (Clojure team)19:04:27

specs are just named with their keyword, so you can just use it from another namespace

Alex Miller (Clojure team)19:04:50

you do need to ensure the spec has been loaded into the registry though, so you'll need to ensure the namespace has been required before use

mister_m19:04:11

I see, thanks

Mobe22:04:38

VS-Code/Calva: I just figured out (well almost) how to run jetty inside the repl. But now I want to stop jetty and continue using the repl. So I tried ctrl-c, ctrl-d, ctrl-c twice, ctrl-c ctrl-q at the repl but to no avail. ps: (jetty/run-jetty #'app {:port 4000}) is how I’m running jetty.

practicalli-johnny03:04:15

Add :join false to the hash-map that is passed to the jetty/run-jetty function to return control to the REPL prompt after jetty starts https://practical.li/clojure-web-services/app-servers/atom-based-restart.html

Mobe09:04:21

It wasn’t about the :join flag. I thought the jetty process was running as a REPL child process. So I tried to CTRL-C the jetty process at the REPL 🙂 now I understand that it’s running in the vs code terminal.

hiredman23:04:15

Look at the docs for run-jetty

1
hiredman23:04:52

Be default it causes the current thread to wait for jetty to stop

hiredman23:04:06

But there is a flag you can pass that makes it not do that

Mobe23:04:57

ah that must be the “join” flag. I remember seeing it on some blog. but anyway how do get it to shutdown without having to restart vscode? I guess that’s more of a vscode / calva problem….

bringe23:04:06

You need to stop your repl from the terminal it’s running in, which is not the Calva repl window in the right pane in your screenshot. In the terminal where the repl is running, hit ctrl+c.

bringe23:04:47

But as was stated already, to prevent needing to do that again you’ll need to use the join flag.

bringe23:04:10

So your running server isn’t coupled to your running repl, essentially.

Mobe23:04:02

now I got it running 🙂 Thank you.

bringe23:04:32

No problem! simple_smile

Jon Olick23:04:17

Still trying to fully grok lazy-seq here. Something I'm confused about is in the docs (https://clojuredocs.org/clojure.core/lazy-seq) they commonly have (lazy-seq (cons a (lazy-seq etc))), the question is, the documentation for cons is not lazy (https://clojuredocs.org/clojure.core/cons), thus cons would attempt to resolve the lazy-seq for the first time, causing infinite loops and what-not.

Jon Olick23:04:42

according to https://stackoverflow.com/questions/12389303/clojure-cons-vs-conj-with-lazy-seq it sounds like cons is actually lazy even though the docs don't mention it.

Jon Olick23:04:33

I would suggest fixing the docs for cons then 🙂 kind of confusing

hiredman23:04:13

No, cons is not lazy

hiredman23:04:49

A call to the lazy-seq macro suspends everything inside it until the seq is being realized

Jon Olick23:04:22

Yes, but cons also doesn't evaluate the elements

Jon Olick23:04:37

Cons is itself a list type which just kind of cats lists together

hiredman23:04:43

But lazy seq doesn't

Jon Olick23:04:50

It doesn't care what they are made of

Jon Olick23:04:00

It doesn't evaluate them

hiredman23:04:17

cons builds non lazy seqs

Jon Olick23:04:26

No it doesn't :) otherwise you would get infinite loops with even trivial uses

hiredman23:04:00

Usually calls to cons are wrapped in calls to lazy-seq which is what makes a lazy seq

Jon Olick23:04:19

I think you are wrong

hiredman23:04:52

Which is why the docs for cons don't say it is lazy, it isn't

Jon Olick23:04:09

check that stackoverflow where that guy has basically the same question as me

Jon Olick23:04:17

the answer is, it doesn't evaluate and return a new list

Jon Olick23:04:33

it is its own structure which lazy cats things to the beginning of another list

Jon Olick23:04:55

because seq isn't evaluated, its only evaluated when a seq gets to it

hiredman23:04:55

I feel pretty confident I know more about clojure internals than the majority of people answering clojure stackoverflow questions

Jon Olick23:04:57

thus, its lazy

hiredman23:04:30

You are misreading the answer

Jon Olick23:04:52

lazy-seq doesn't modify the functionality of other unrelated functions

Jon Olick23:04:00

which is what you are suggesting

hiredman23:04:01

The answer says when you cons a value on to another seq S, it does not force S

hiredman23:04:11

Which is correct

hiredman23:04:26

But does not mean cons is not lazy

Jon Olick23:04:57

lazy seq is a lazy seq, list is a list, you can't make a real value and pre-pend it to a lazy seq

hiredman23:04:17

cons does exactly that

Jon Olick23:04:40

but if you read how its implemented, or at least his description of it, that is not what it does?

Jon Olick23:04:11

I mean like what would you do, in the case of a cons being somewhere inside of a lazy-seq, selectively create another lazy-seq which then transfers to another lazy-seq

Jon Olick23:04:13

thats bonkers

Jon Olick23:04:30

(clearly I'm confused 🙂 )

Jon Olick23:04:50

I'm not arguing to argue, I'm trying to correct myself by explaining it the way I understand it

hiredman23:04:11

Like, you can see that cons is not lazy, if you type (do (cons (println 1) (println 2)) nil)

hiredman23:04:22

Into the repl, both printlns run

hiredman23:04:46

But there is nothing that would force them if it was lazy

Jon Olick23:04:23

lemme try, 1 sec

Jon Olick23:04:56

no thats not what I'm talking about

hiredman23:04:26

And you won't get an error because println returns nil, so you are effectively calling (cons nil nil) where nil is taken to mean the empty seq, so that cons call makes a seq which is not lazy, that is has a single value, nil

Jon Olick23:04:00

in that case that you describe, (println 2) is evaluated of course, but if it was a (lazy-seq) it evaluates into lazy evaluated body etc...

Jon Olick23:04:45

I suppose what the disconnect is, is that lazy-seq can have both lazy and real elements in it

hiredman23:04:27

Seq is just a Java interface right, it can have many different implementations that do whatever

Jon Olick23:04:06

so clojure calls cons on the lazy-seq member function or something

hiredman23:04:43

cons returns a clojure.lang.Cons which is an implementation of Seq, and is not lazy

hiredman23:04:23

lazy-seq produces an implementation of Seq that is basically a delay

hiredman23:04:48

So the lazy-seq seq forces its delay(more or less) when you call first or rest on it, which produces the non-lazy seq made via cons, and the lazy-seq forwards the first or rest call to the non-lazy seq

hiredman23:04:33

The laziness of a seq is not really a property of the "whole" seq

hiredman23:04:05

It is a property of each node/cell/link that makes up the seq

seancorfield23:04:38

Perhaps this is more compelling evidence?

dev=> (take 1 (cons (println 1) (lazy-seq (cons (println 2) (println 3)))))
1
(nil)
dev=> (take 2 (cons (println 1) (lazy-seq (cons (println 2) (println 3)))))
1
(2
3
nil nil)

seancorfield23:04:29

And, in modified form, if you extend it:

dev=> (take 2 (cons (println 1) (lazy-seq (cons (println 2) (lazy-seq (cons (println 3) []))))))
1
(2
nil nil)
dev=> (take 3 (cons (println 1) (lazy-seq (cons (println 2) (lazy-seq (cons (println 3) []))))))
1
(2
nil 3
nil nil)

seancorfield23:04:55

(in particular, compare the two different take 2 versions)

hiredman23:04:13

Seqs are really linked lists, but just lifted to an interface instead of a concrete cons cell representation as in older lisps

seancorfield23:04:09

It can be particularly confusing when you interact with other "lazy" functions that operate on chunked sequences because you'll get blocks of 32 items at a time, even if you only ask for a dozen items...

hiredman23:04:20

And the interface allows you to do things like lazy seqs (delays) or custom seqs for iterating over collection types (like most clojure collections have)

seancorfield23:04:38

(but that's just an efficiency artifact of "lazy sequences" rather than lazy-seq)

Jon Olick23:04:41

alright, lemme mull over this a bit

hiredman23:04:44

But it is still fundamentally a linked node kind of thing

Jon Olick23:04:46

more questions later perhaps

Jon Olick23:04:57

this was a great and helpful thread though, thank you guys so much!

seancorfield23:04:19

And, yeah, @U0NCTKEV8 is a pretty definitive source of correctness about Clojure internals 🙂

dpsutton23:04:24

two examples that i think can help explain: (def l (cons :a (cons :b (cons :c (cons :d (range 1e6)))))) is l completely lazy or completely realized? it wouldn't make sense to make the cons lazy when we have concrete values a, b, c, d, etc. Cons isn't lazy there

Jon Olick23:04:37

oh and I'm definitely not, I'm just trying to get a more thorough understanding of it all

dpsutton23:04:54

and (def l (concat (range 1e6) [:a :b :c :d] (range 1e6))) a "lazy" portion with a million elements, then a clearly realized portion with 4 elements, and then a lazy million element portion again

Jon Olick00:04:13

concat I get, yes

Jon Olick00:04:20

I was thinking cons is just like concat

Jon Olick00:04:40

or would have to be

Jon Olick00:04:29

but each individual linked list node itself being a totally arbitrary way to point to the next element in a linked list cell is so very much OOP that its probably right

Jon Olick00:04:57

that seems slightly inefficient

Jon Olick00:04:08

but whatever 🙂 not my circus

seancorfield00:04:05

It's why we use vectors in Clojure a lot. They can behave like sequences but are much more efficient in a lot of situations.

seancorfield00:04:20

While all three of these behave like sequences of 1, 2, 3, they are different types:

dev=> (type (list 1 2 3))
clojure.lang.PersistentList
dev=> (type (cons 1 (cons 2 (cons 3 ()))))
clojure.lang.Cons
dev=> (type [1 2 3])
clojure.lang.PersistentVector
dev=> (type (range 1 4))
clojure.lang.LongRange

seancorfield00:04:01

As in:

dev=> (range 1 4)
(1 2 3)
dev=> (seq [1 2 3])
(1 2 3)
dev=> (cons 1 (cons 2 (cons 3 ())))
(1 2 3)
dev=> (list 1 2 3)
(1 2 3)

andy.fingerhut03:04:19

I have no idea if boxes-for-Java-objects-in-memory and arrows-for-pointers/references pictures of Clojure lazy sequences being realized one element at a time would be at all useful to you, but there is a library I wrote called cljol with a gallery of images I produced using that library, showing such pictures, with some associated text attempting to describe what is happening: https://github.com/jafingerhut/cljol/blob/master/doc/README-gallery.md