Fork me on GitHub
#beginners
<
2022-11-14
>
mandudeman02:11:31

I defined two functions. One does the creation of an empty list and the other adds items to that empty list. My intention for the 2nd function was to produce a list that contains the added items to it. When I invoked the 2nd function with the added items, it returned a list of items within a list. How can I have it returned the list with the added items without being nested in another list? Hereunder are the functions I had defined or created:

mandudeman02:11:53

I tried to use it but it threw an error whenever I invoked the function.

skylize03:11:38

Clojure is designed around immutability. So you don't generally want try to changing things in place like that. Beyond that, it's hard to point you in the right direction from the info provided, because: My first step would be to change add-num to take in a collection and return an updated collection...

(defn add-nums [nums & more-nums]
  (into nums more-nums))
... but that makes add-nums just an alias for conj, which isn't very helpful.

skylize03:11:08

What are you trying to accomplish with this list of numbers?

mandudeman03:11:20

I just want to access the items in the list for another function to make use of it. Since it is nested within another list, I am having problem with fetching items from the list. Let us say, I want to retrieve the first item with peek or first built-in function, it will return a different result than my desired outcome when the said list is not nested within another list.

skylize03:11:19

A lot of different ways to traverse nested data, depending on the data, the context, and your personal taste. Can you share some code?

mandudeman03:11:04

I am actually trying to follow some tutorials. Let me show you the code first.

mandudeman03:11:18

(defn new-list "Creates an empty list of languages to practice." [] (def lang-list (list )) lang-list) (new-list) (defn add-language "Adds a language to the list." [& lang] (new-list) (conj lang-list lang)) (add-language "Python" "PHP") (defn first-language "Returns the first language on the list." [lang-list] (peek '(lang-list))) (defn remove-language "Removes the first language added to the list." [lang-list] (pop '(lang-list))) (defn count-languages "Returns the total number of languages on the list." [lang-list] (count '(lang-list))) (defn learning-list "Creates an empty list, adds Clojure and Lisp, removes Lisp, adds Java and JavaScript, then finally returns a count of the total number of languages." [] (new-list) (add-language '(lang-list) "Lisp" "Clojure") (remove-language '(lang-list)) (add-language "Java" "JavaScript") (count '(lang-list)))

mandudeman03:11:24

If you notice, in the learning-list function, I have to invoke several functions that were defined previously.

mandudeman03:11:53

For me to be able to invoke the first-language function or the remove-language function, I must be able first to produce a list. However, since the list is nested to another list, I cannot directly fetch the item as it has to go through the main list upon which the lang-list is nested on.

skylize03:11:01

Hold, on. I'm trying to work out what your goal is from the code. Using def inside a function is a very bad code smell. There are good uses for it, but they are (mostly) limited to "programs writing programs" types of situation. As a rule of thumb def and defn should only occur at the top level.

mandudeman03:11:25

How can I pass an empty list for another function to make us of it?

skylize03:11:51

Not sure what you mean. You can pass an empty list to any function that accepts lists.

(def result (my-list-using-fn '()))

mandudeman03:11:34

Initially, the code was like this: (defn new-list [] '()) or this: (defn new-list [] (list ))

mandudeman04:11:40

The tutorial says that first, you have to define a function that creates an empty list.

skylize04:11:52

What tutorial is that? You don't need to define a function to create an empty list. Even if you need a function for that, list with no arguments can already do that.

(list) ;; => ()

mandudeman04:11:31

It was in Exercism. Though I know what you mean but with the instruction from the tutorial, I just try to make way to follow what it instructs in the hope that I may learn something from what it tries to make us do.

skylize04:11:03

(def practice-list '()) (defn add-language "Adds a language to the list." [m & lang] (apply conj m lang)) (def with-ps (add-language practice-list "Python" "PHP")) practice-list ;; => () with-ps ;; => ("PHP" "Python")

skylize04:11:44

Maybe it is an exercise designed for another language that translates poorly to Clojure.

skylize04:11:45

If you really must have a list to edit in place, use an atom. https://clojuredocs.org/clojure.core/atom . Not totally sure how Exercism works. But if the process allows it, I suggest only using the instructions as a rough guide, and focus instead on using functions over immutable data structures to reach the end goal. You can come back to atoms after you have a strong foundation in functional design.

mandudeman04:11:10

Initially, that was what I had in my mind as it would be simple for me to do. However, in the codes given to us, we only need to supply the missing algorithm. Thus, I followed by just supplying the codes that I suppose to think that makes it work.

mandudeman04:11:27

Anyway, thanks a lot for the response.

skylize04:11:46

What is the target of the exercise? Even though I think this exercise is terribly misguided for Clojure, I still might be able to point you in the right direction.

mandudeman04:11:24

The aim was to supply some codes to pre-defined functions and on the last function, you supply all these predefined functions to the learning-list function.

pavlosmelissinos06:11:15

The problem is that you're trying to apply what you know from imperative languages to Clojure. The exercise doesn't seem to be restricting you to use the functions as steps of an algorithm independently from each other (which requires you to have side effects - if you don't know what that is look it up). In Clojure the proper way would be to use the output of the previous steps as input to the next ones. On my phone right now but give me a moment and I'll come up with something.

mandudeman06:11:13

This is what I am trying to complete.

mandudeman06:11:54

(defn learning-list
  "Creates an empty list, adds Clojure and Lisp, removes Lisp, adds
  Java and JavaScript, then finally returns a count of the total number
  of languages."
  []
  (new-list)
  (add-language '(lang-list) "Lisp" "Clojure")
  (remove-language '(lang-list))
  (add-language "Java" "JavaScript")
  (count '(lang-list)))

mandudeman06:11:23

First, I try to supply the codes for every function that I would eventually need for the learning-list function. Once those functions are ok, I will then invoke them in a particular sequence where the output of the previous function will serve as the input for succeeding function.

pavlosmelissinos07:11:16

Alright, forget about bringing them all together, you should start with new-list:

(defn new-list []
      (list))

    (new-list)
    ;; => ()
This works. Like @U90R0EPHA said, def in a function is bad code smell. Moving on to add-language:
(defn add-language [l lang]
      (conj l lang))

    (add-language '("Clojurescript") "JavaScript")
    ;; => '("JavaScript" "Clojurescript")
Can you work out first-language, remove-language and count-language?

mandudeman07:11:53

Yes, I can. If I have to do the codes like your example, then it would be easy.

mandudeman07:11:49

Give me some time to edit my codes and I will send the codes to you for you to evaluate them.

mandudeman07:11:42

(defn add-language
  "Adds a language to the list."
  [lang-list lang]
  (conj lang-list lang))

(add-language '("ClojureScript") "JavaScript") ;; returns ((("ClojureScript") "JavaScript"))

mandudeman07:11:06

My function returns

mandudeman07:11:15

=> ((("ClojureScript") "JavaScript"))

pavlosmelissinos07:11:10

What's in lang-list?

mandudeman07:11:50

("ClojureScript")

pavlosmelissinos07:11:43

Something else is happening because what you wrote above is correct.

pavlosmelissinos07:11:26

Maybe you're using an older version of add-language?

pavlosmelissinos07:11:44

Make sure you re-evaluate the functions as you change them

mandudeman07:11:34

I check your code sample, it also returns this: ;=> ((("Clojurescript") "JavaScript"))

(defn add-language [l lang]
  (conj l lang))

(add-language '("Clojurescript") "JavaScript")

mandudeman07:11:24

Does it have something to do with Calva or VS code editor?

pavlosmelissinos07:11:57

I'm not sure but the problem is definitely on your side.

pavlosmelissinos07:11:47

Did you override conj by any chance?

mandudeman07:11:56

I will try to install another editor and from there, I will check again. I will let you know once I have tried again in another editor.

pavlosmelissinos07:11:41

No need to install another editor just for this, VS Code & Calva is a quite decent combination. Restarting the REPL should suffice.

pavlosmelissinos07:11:16

what does conj evaluate to?

mandudeman07:11:46

I have been changing the codes to find which one will return a list. But I suppose that repl will only read and evaluate the forms that I have written.

mandudeman07:11:37

It throws an error. I will disconnect and reconnect my repl again.

pavlosmelissinos07:11:38

> I have been changing the codes to find which one will return a list. But I suppose that repl will only read and evaluate the forms that I have written. That's correct but you've been doing lots of side-effects so you might have changed something inadvertently

mandudeman07:11:44

It is now ok.

👌 1
mandudeman07:11:04

It returns this: => ("JavaScript" "ClojureScript")

🙂 1
mandudeman07:11:36

I will get back to you as I want to complete this particular tutorial. I will just have to try a different editor as I am getting seemingly conflicting evaluation from VS Code editor.

mandudeman07:11:10

Thanks a lot for the response and assistance.

pavlosmelissinos07:11:06

If you're having issues with your editor you can ask at #calva I wouldn't change editors if I were you, the other decent ones I know is emacs (and vim) and they're not very beginner-friendly... 🙂

mandudeman10:11:23

I have tried emacs with vim on it. This time around, I would try intellij. I am having problem evaluating forms with Calva.

kennytilton10:11:35

I use IntelliJ with Cursive, as do many. If you know the beast 🙂 that might be best.

👍 1
mandudeman10:11:49

I have not tried it yet but I want to know if it is better than vs code + calva.

pavlosmelissinos11:11:22

Oooh, totally forgot about Cursive! Yes, it's nice. 🙂 > I am having problem evaluating forms with Calva. @U03JN22B596 I'm sure @U0ETXRFEW and the rest of the folks over at #CBE668G4R would be more than happy to help you.

pez11:11:02

@U03JN22B596 Cursive is great. Though I am pretty sure this is not about Calva. Calva is just sending the forms on to the REPL to be evaluated. Do the same things in some other editor (or even a bare REPL prompt) and you will get the same results. This will rather be about your expectations and your (lacking) experience with the Clojure REPL. Let's see if we can help you with those, regardless of which editor you use.

pez12:11:35

Can you share the steps you take to evaluate things in your file, @U03JN22B596?

kennytilton12:11:56

Feel free to DM me if you need help with IntelliJ/Cursive @U03JN22B596. btw, @U03JN22B596, I understand your desire to stick closely to the tutorial. That is wise, because once we deviate from the instructions all bets are off. I agree with @UEQPKG7HQ that this is not a great tutorial, but agree also we can still help you with it. The problem is that the instructions say only "Define a learning-list function, within which you will use...the functions you've defined above." and then describes the steps in natural language. And it leaves out a huge, unstated requirement: how to combine the results of two or more functions. The examples it provides all cheat in this regard:

(remove-language '("Common Lisp" "Racket" "Scheme"))
;; => '("Racket" "Scheme")
Oh, sure. Pass a literal list to remove-language. Easy! But how do we do that with a list build in a preceding step?! And the learning-list exercise requires us to make a list then change "the list" three times and then count it. The instructions each refer to "the list", but they do not say how to continue working on the same list!! In your brave early attempt we saw:
(defn learning-list
  "Creates an empty list, adds Clojure and Lisp, removes Lisp, adds
  Java and JavaScript, then finally returns a count of the total number
  of languages."
  []
  (new-list)
  (add-language '(lang-list) "Lisp" "Clojure")
  (remove-language '(lang-list))
  (add-language "Java" "JavaScript")
  (count '(lang-list)))
Not sure where that stands now. The second call to add-language does not pass a list as the first argument. Typo, probably. Anyway, once the individual functions are sorted out we still have the problem of getting one step to work on the result of the preceding step. Importantly, nowhere in that exercise instructions do I see them pointing that out, let alone helping with it. Here's some baby step ideas: • new-list returns an empty list. Super! • now how can you get the second step add-language to work on that same empty list returned in the first step, instead of the literal '(lang-list)? btw, that lang-list looks suspiciously like a variable name. If so, have you covered let in these tutorials? (hint)

👍 1
mandudeman12:11:58

Thanks a lot. Actually, the codes that I sent was not fully done yet as I was figuring out how to generate a list out from a function. That was the first step. However, since it was pointed out that it does not have to be that way, then I try to follow the inputs given by pavlosmelissinos. As I was doing it, something seemed not properly working with my vs code editor because I have to disconnect the repl and reconnect again before making any evaluation. It is very frustrating. Thus, I want to try using IntelliJ with cursive to see whether repl evaluation with forms will be smooth or without problems.

kennytilton12:11:31

Well, if you get frustrated installing Cursive I am sure the #CBE668G4R crowd can get you sorted out. See you back in our DM....

skylize13:11:30

If you open a bare repl in the terminal, it starts you off in the user namespace, which already has clojure.core required in for you. But in Calva you typically evaluate directly from a file, where Calva evaluates in that file's namespace, which does not have clojure.core functions available until you evaluate`(ns my.namespace)`. So after a repl restart, you need scroll to the top and evaluate (ns my.namespace) before trying to eval any other code in a file. If your code is in good enough state to not throw any exceptions, then you can alternatively use the command Calva: Load/Evaluate Current File, which will evaluate the whole file at once and make all your defs, defns, etc. available in one go.

skylize14:11:55

After seeing the actual exercise, I still say this is very poorly written for Clojure; though not for the reason I thought it was. If you look closely, it does mostly follow the expectation that functions will take a list as input.

;; Takes a list and an element as args and returns a new list.
;; Here '("Clojurescript") is a stand-in for an existing list.
(add-language '("Clojurescript") "JavaScript")
The problem is it tries to confuse the reader by making you think you need to "write" functions to do a thing, when all the things are already done by core functions.
(def new-list list)
(def add-language cons) ;; or `conj`
(def first-language first)
(def remove-language rest)
(def count-languages count)
Only the final learning-list function does anything more than aliasing a core function. In learning-list, you can use let to store partial results...
(defn learning-list []
  (let [first-langs (cons (cons (list) "Clojurescript")) "JavaScript")
        ...]
    (count final-langs)))
...or stuff the whole thing through a pipeline with the thread first macro.
(defn learning-list []
  (-> (list)
      ;; `->` passes the list as first arg of each fn
      (cons "Clojurescript") ;; so this line is actually `(cons '() "Clojurescript")`
      (cons "JavaScript")
      ...
      count))

👍 1
pez18:11:44

Exercism does not try to confuse the reader. That’s very unfair to say about an honest attempt to help with learning to code.

pavlosmelissinos18:11:51

I agree, it's not intentional, but it is a bit confusing. Other languages don't have the constructs Clojure does so some of the exercises don't translate very well.

skylize19:11:48

I was personifying the list of instructions, not intending to accuse the writer of being nefarious. Sorry if that didn't come through. I definitely blame this on the totally understandable need to recycle exercises across different languages.

👍 1
kennytilton19:11:48

I did not take "try to confuse" literally. 🙂 I took it as, "Man this exercise is poorly crafted!" Anyway... Who knows what was intended, or even what had been covered previously, hence been assumed by the author of the exercise? For one thing, having the tutee do things in functions that are part of Clojure, eg. implementing first, is standard pedagogy. Hell, I was asked to implement mod in a tech interview. I laughed and let them know my earliest languages did not have mod, here is how we did it in 1982. They were stunned by our boomer code, btw. More concerning was leaving out the need to repeatedly operate on the same result, but I wondered if the author thought it would be neat to let the student crash into the need, effectively, to use a threading macro or an atom or a sequence of let bindings. ie, discover immutability on their own. But then I would expect a warning, such as "Do not be surprised if your naive solution does not work -- combining even two of these functions will be a challenge!" But teaching is hard, and good exercise design harder, so...I suggested Winston try Brave Clojure. (Wait, did that have exercises?) OT: Paul Graham is famous for On Lisp, but I recall the exercises in his other title, ANSI Common Lisp, were great.

pez20:11:32

The author/translator might be able to answer about intents in #C010UTRT0BZ. All I know that an enormous amount of work and energy has been put into making the Clojure track possible and enjoyable. It is also open source.

pez20:11:17

I think that the ”recycling” of exercises is a good thing. Some exercises are easier in some languages than in others. With a a rich standard library like Clojure's, along with effective core abstractions, Clojure comes out strong, even OP in many of the earlier steps. But there are always things to learn. Reimplementing core functions is certainly worthy efforts.

skylize20:11:14

> For one thing, having the tutee do things in functions that are part of Clojure, eg. implementing first, is standard pedagogy. True. But not usually 5 reimplentations in a row without any preface of "let's pretend x-functions do not already exist". This seems pretty clear to me as an exercise designed for a C-like language, where nobody has found sufficient time for more than a surface level translation to Clojure. If it was reworked properly, it would probably include relevant discussion about combining functions over immutable data. That does not mean Exercism is bad. It just means that this particular exercise is in bad shape.

mandudeman02:11:36

(defn num-list [] (def num-list (list )) num-list) (defn add-num [& num] (num-list) (conj num-list num)) (add-num 1 2 3) ;; returns ((1 2 3))

Amogh Rajapure11:11:28

Hello, Can someone help me in understanding Why does group-by, reduce, map or into functions maintain order/sequence upto only 8 items? If the input data has more than 8 items, then the output will be unordered (compared to original input)? Is there a way to merge/assoc data without altering the order of the input into a single map/hash-map? Input: '({["a"] [{:type "chart" :val "a"}]} {["b"] [{:type "chart" :val "b"}]} {["c"] [{:type "chart" :val "c"}]} ....... ..{["j"] [{:type "chart" :val "j"}]}) I have tried with (reduce), or used (into {}), (hash-map), (assoc) inside a for loop. But not getting the output in the same order.

pavlosmelissinos11:11:21

maps change their type once they become larger than 8 items

(def m (zipmap (range 8) (range 8)))
  (type m)
  ;; => clojure.lang.PersistentArrayMap

  (-> (assoc m :foo :bar)
      type)
  ;; => clojure.lang.PersistentHashMap
The bottom-line is that you shouldn't depend on the order of maps. If you need order you should use another data structure (e.g. vector)

pavlosmelissinos11:11:55

In other words it doesn't have to do with group-by/reduce/map/into, it's about the data structure you're using

pavlosmelissinos11:11:07

There's also https://clojure.org/reference/data_structures#ArrayMaps, but: > it has linear lookup performance, and is only suitable for very small maps

Amogh Rajapure11:11:38

@UEQPKG7HQ Thanks for responding. But in my case the order is important becuase this unordered data is displayed on the UI.

Amogh Rajapure11:11:56

Hence I am trying to find a way to return ordered data.

pavlosmelissinos11:11:33

You have to choose another data structure then. This is Clojure being honest with you. It can either give you ordered data or O(1) lookup

pavlosmelissinos11:11:00

Do you need O(1) lookup?

pavlosmelissinos11:11:01

If not, just replace the map with a vector. If you need it, you can keep the map and have a vector to keep track of the order of your keys.

Amogh Rajapure11:11:27

I am open to both the approaches.

pavlosmelissinos11:11:53

Alright, there you go, choose one and go ahead. Or do you have another question?

Amogh Rajapure11:11:21

If I have to get the desired output like this which way do I have to follow ? {["a"] [{:type "chart" :val "a"}] ["b"] [{:type "chart" :val "b"}] ["c"] [{:type "chart" :val "c"}] ....... ..["j"] [{:type "chart" :val "j"}]}

pavlosmelissinos11:11:00

That's an unusual way to represent data

pavlosmelissinos11:11:15

Do the keys have to be vectors?

pavlosmelissinos11:11:34

Also when you say "ordered" do you mean "sorted alphabetically"?

Amogh Rajapure11:11:37

Yes, the keys should be in vector

Amogh Rajapure11:11:33

No, not in alphabetical order. For understanding purpose i have used abcdj.

👍 1
pavlosmelissinos12:11:30

> If I have to get the desired output like this which way do I have to follow ? It doesn't matter what the desired output will look like. First of all you need to decide if you want fast lookup and this isn't Clojure-related. • Will the data be big? (hundreds/thousands of keys) • Will you need to get the value of an arbitrary key often? If you answer yes to both of the above, then you need O(1) and it's probably better to use a map (to store the data) and a vector (to keep track of the key order)...

pavlosmelissinos12:11:20

data:

{["a"] [{:type "chart" :val "a"}]
["b"] [{:type "chart" :val "b"}]
["c"] [{:type "chart" :val "c"}] .......
..["j"] [{:type "chart" :val "j"}]}
key order:
[["a"] ["b"] ["c"] ... ["j"] ...]]

👍 1
Amogh Rajapure12:11:48

I have input like this: (def data [{:type "graph" :id "memo"} {:type "graph" :id "load"} ... {:type "graph" :id "usage"}]) After doing (group-by :id data) (first time order is getting changed here) and some business logic the output looks like this. {["load"] [load data] ["memo" [memo data] ["usage"] [usage-data]}

Amogh Rajapure12:11:33

Using the original order of the data I need to sort it and return it to the UI.

Amogh Rajapure12:11:10

I am able to sort the data using sort-by with .indexOf [origonal order] function.

Amogh Rajapure12:11:27

But the output is not the desired one.

pavlosmelissinos12:11:45

How do you produce the keys?

pavlosmelissinos12:11:47

If you map the function that computes the keys over the original data you'll get the keys in a seq (but with duplicates). Once you have that, you can use distinct to remove the duplicates.

pavlosmelissinos12:11:26

That will give you the key order, so all you'll have to do is ((apply juxt key-order-vector) unordered-map) and you'll get the values in order

👍 1
Amogh Rajapure12:11:05

Yes, I did use map function to get keys from the input data to store the original data.

pavlosmelissinos12:11:20

Alright, so you're good to go or do you have another question?

Amogh Rajapure12:11:21

((apply juxt ordered-keys) intermediate-output) I tried your solution, but it thrown me an exception, string cannot be cast to clojure.lang.ifn

pavlosmelissinos12:11:17

should be a vector, not a string

Amogh Rajapure12:11:10

Do have to convert ordered-key/s to vector?

pavlosmelissinos12:11:12

Your ordered-keys should look like this, it's all of the "keys" of the original data, ordered: [["a"] ["b"] ["c"] ... ["j"] ...]] If it does look like that, then this should give you the values of the intermediate output, also in order: (map #(get intermediate-output %) ordered-keys) edit: (you're right, the juxt thing would only work if the keys of the map were keywords)

Amogh Rajapure12:11:11

Let me try this, will let you know the results. Thanks 😊

🙂 1
Amogh Rajapure13:11:21

@UEQPKG7HQ Observations from your approaches: 1. ((apply juxt .....) Throws an exception like key must be an integer 2. (Map #(get ....) This returned only values in the sorted order based on the ordered-key vector. Something like this ([{:type "graph", :id "memo"}] ..... [{:type "graph", :id "usage"}]) Type: lazySeq and output is missing the keyword

pavlosmelissinos13:11:12

Alright, so what's your question?

Amogh Rajapure13:11:39

My question here is, how to get the output in a map with key value

pavlosmelissinos13:11:58

Why do you need the output as a map?

pavlosmelissinos13:11:04

Maps are not meant to be ordered

Amogh Rajapure13:11:40

Output Like this: {["memo" [memo data] ["load"] [load data] ["usage"] [usage-data]}

Amogh Rajapure13:11:33

Agree, maps will not be in order, but is there way to create dat structure like that ?

pavlosmelissinos13:11:17

Why do you need that data structure? This sounds like an XY problem.

pavlosmelissinos13:11:08

What actual goal would having that serve?

Amogh Rajapure13:11:28

In my case, this map item will be processed down further to functions, which is expecting a map of that data structure

pavlosmelissinos13:11:29

And those functions further down expect an ordered map?

Amogh Rajapure13:11:32

Sorry for asking too many questions.

Amogh Rajapure13:11:10

Yes, they require it to be in ordered map.

pavlosmelissinos13:11:31

That's the problem. Maps are associative data structures, their contract is to do fast lookups, not keep the insertion order.

👍 1
pavlosmelissinos13:11:04

You could create a new type that satisfies both constraints or add a second "key-order" argument to those functions that expect an ordered map. > Sorry for asking too many questions. You're not asking too many questions but I think they're a bit misguided. You should take a step back and rethink how the data flows through your functions.

pavlosmelissinos13:11:58

A third option would be to replace the maps with another data structure that is ordered but then you lose the ability to do fast lookups.

Amogh Rajapure13:11:45

Yes, currently i am kind of stuck third option. Will try to pass on the ordered-key vector to the downstream functions. Hope it works there.v

valerauko14:11:25

is there any protocol/interface i could implement to make my thing invokable as a function other than IFn? hacking together 21 arities of invoke is a motivation drainer

delaguardo14:11:33

you don’t need all of them 🙂

delaguardo14:11:40

IFn should be enough but don’t forget that IFn extends Callable and Runnable — to cover those you will need run and call methods

valerauko14:11:25

ooh okay that makes things easier

delimber14:11:48

Hi folks, There is a great https://github.com/funcool/promesa library and it has an fn. It seems like a sane wish to cancel all other promises but successful (binded) one after this successful one is determined. But there in no such functionality in this fn. So, two questions are rissen (in a thread)

delimber14:11:25

Q1: Am I understand correctly. There is no such behavior (mainly?) because of promesa is targeting both clj and cljs and there is no such thing as cancellable promise in js?

delimber14:11:09

Q2: Is there a better way to get this behaviour other than copy any from https://github.com/funcool/promesa/blob/master/src/promesa/core.cljc#L297 with one extra form?

delimber14:11:43

(defn any
  "Given an array of promises, return a promise that is fulfilled when
  first one item in the array is fulfilled."
  ([promises]
   (any promises ::p/default))
  ([promises default]
   (let [state (atom {:resolved false
                      :counter (count promises)
                      :rejections []})]
     (p/create
      (fn [resolve reject]
        (doseq [p promises]
          (-> (p/promise p)
              (p/then (fn [v]
                        (when-not (:resolved @state)
                          (swap! state (fn [state]
                                         (-> state
                                             (assoc :resolved true)
                                             (update :counter dec))))
                          #?(:clj (doseq [p promises] (p/cancel! p))) ;; <-- This extra form
                          (resolve v))))
              (p/catch (fn [e]
                         (swap! state (fn [state]
                                        (-> state
                                            (update  :counter dec)
                                            (update :rejections conj e))))
                         (let [{:keys [resolved counter rejections]} @state]
                           (when (and (not resolved) (= counter 0))
                             (if (= default ::p/default)
                               (reject (ex-info "No promises resolved"
                                                    {:rejections rejections}))
                               (resolve default)))))))))))))

yiorgos15:11:49

Is there a function to pick a random element from a set, something like rand-nth collection ?

Dane Filipczak15:11:58

no, but you can call rand-nth on a set if you cast it to a sequential collection first (with seq or similar)

delaguardo15:11:20

(comp rand-nth seq) should give you that function

👍 2
yiorgos15:11:17

Sweet, thanks!

sam15:11:04

Hm, is (rand-nth (seq my-set)) O(n)? I think it probably is. May not matter much for your case, but just throwing it out there.

sam15:11:17

If performance is any concern.

sam15:11:53

It might be worth converting the set to a vector up front if it is more than a few elements and you plan to sample many random values. Some discussion here: https://groups.google.com/g/clojure/c/dMFZhEXE88s

yiorgos15:11:53

no performance is not an issue, only a couple of elements in the set. Thanks btw

👍 1
valerauko16:11:02

is there a way to name the class generated by reify?

Alex Miller (Clojure team)16:11:25

no, it's an anonymous instance

Alex Miller (Clojure team)16:11:46

if you want something stable, defrecord may be a better match

valerauko16:11:08

it doesn't have to be stable, but it'd be nice to give a hint to the gensym i guess? so it'd be more obvious in the stacktrace and easier to parse in (class my-thing)

Noah17:11:15

Hi everyone. Let's say I need to import a library like data.csv to my project, am I also obligated to add the dependency in project.clj? Clearly, using only :require in my core.clj is not enough.

respatialized17:11:49

Yes. require only imports things that are on your project's classpath; your project.clj defines the classpath.

respatialized17:11:06

This also means that you will need to restart your REPL when you've added a new dependency, as the classpath doesn't change dynamically at runtime.

Noah17:11:52

Then I got a follow up question.

Noah17:11:07

As you see in the above screenshot, the dependency I have has a specific version.

Noah17:11:32

Let's say I want to import clojure.data.csv how would I know the version number?

Eric Scott17:11:46

lein deps :tree will give you the full dependency tree of whatever you explicitly declare as a dependency

respatialized17:11:30

The best place to look to start off is probably Clojars: https://clojars.org/ - you can search there for the latest version.

Noah17:11:10

So each time I need a library I will need to look it up on that website to know what version number I am supposed to import?

respatialized17:11:40

Actually I just tried searching for data.csv on Clojars and the official version didn't seem to turn up - the library's GitHub project has the information as well. Usually Googling "clojure + [library name]" will turn it up. https://github.com/clojure/data.csv

👍 1
respatialized17:11:38

And yes, Clojure requires you to be explicit about which version of a library you're installing. In my experience, the benefits of always knowing what library version I have loaded outweigh the costs of looking up the version number. Recording versions in lockfiles or stuff like requirements.txt is now being recommended as a best practice in languages like Python for similar reasons.

1
dvingo17:11:10

the clojure artifacts live in maven: https://search.maven.org/search?q=g:org.clojure the community ones tend to live in clojars

1
🙏 1
sam17:11:55

You can also do:

lein deps :query org.clojure/data.csv

1
respatialized17:11:21

@UABEM258C that's handy! I didn't realize that was built into lein. Is there a similar tool for deps.edn?

Noah17:11:16

@UABEM258C , sorry for my noobiness, but am I supposed to write that in repl? I tried but it did not work. And it automatically adds a dependency to project.clj?

respatialized17:11:15

No, that's a CLI command that you'd execute from your terminal that uses leiningen to search for versions of a given project. So the workflow would be something like: 1. run lein deps :query from a terminal to get a version number 2. add appropriate entry in project.clj 3. restart REPL 4. require new dependency and go!

❤️ 1
sam17:11:54

@UFTRLDZEW Yes! There is one for deps.edn. Let me find it…

sam17:11:55

clj -X:deps find-versions :lib org.clojure/data.csv 

nice 2
Alex Miller (Clojure team)18:11:28

I'm not sure if it adds anything beyond what people have already helped with here, but the Deps and CLI guide covers this as well... https://clojure.org/guides/deps_and_cli

👍 1
vlad_poh18:11:08

I saw this https://stackoverflow.com/questions/74376824/r-remove-values-that-do-not-fit-into-a-sequence

I have a sequence s where I expect each proceeding value to be either the same as the previous one or +1.
s = c(1,1,1,1,2,2,2,-2,3,3,4,8,8,8,9,5,5,12,6)
What I want:
1,1,1,1,2,2,2,3,3,4,5,5,6
and wondered how would i do this in clojure this is my attempt
(def s '(1,1,1,1,2,2,2,-2,3,3,4,8,8,8,9,5,5,12,6))
(reduce #(if (#{0 -1} (- (last %) %2)) (conj % %2) %) [(first s)] (rest s))
and i feel there is an idiom i’m missing. What is the most idiomatic way to solve this problem?

Bob B18:11:01

maybe something like:

(take 50 (mapcat #(repeat (rand-int 10) %) (range)))
depending on what the parameters are of the expectations

hiredman18:11:22

use partition-all

Bob B18:11:11

oh... I misunderstood the intent... sry

hiredman18:11:22

maybe a combination of partition-by and partition-all

hiredman18:11:12

that would work for single stage of removal, but you may need to switch to iterate or a custom lazy-seq builder when it is more complicated

hiredman18:11:46

iterate would let you do basically the same thing as the reduce, while still being lazy (which the reduce is not)

kennytilton23:11:41

I have:

(let [s [1,1,1,1,2,2,2,-2,3,3,4,8,8,8,9,5,5,12,6]]
    (loop [prior nil
           [next & more] s
           result []]
      (cond
        (nil? next)
        result

        (nil? prior)
        (recur next more [next])

        (or (= next prior)
          (= next (inc prior)))
        (recur next more (conj result next))

        :else (recur prior more result))))
But then I am an old Common Lisper. 🙂

👍 1
mister_m18:11:02

is there a clj comand available to create an empty project? Something like lein new ...

mister_m18:11:33

is this an external tool?

Alex Miller (Clojure team)18:11:26

an empty directory is a valid deps.edn project

Alex Miller (Clojure team)18:11:44

by default it will look for source under src/

Alex Miller (Clojure team)18:11:29

you might find some of what the clj-new template creates for you useful, but you don't need any of it really

👍 2
delaguardo18:11:32

it is external, but requires only Clojure cli

mister_m18:11:02

thank you both

dpsutton18:11:29

i use neil like this

dpsutton18:11:10

neil add build gets me a template very close to what lein new app foo would do

Ben Sless18:11:14

neil is pretty snappy

dpsutton18:11:44

indeed. empty directory, neil add build and you are off to the races

seancorfield19:11:30

@U04V4KLKC I would prefer folks recommend deps-new over clj-new but, as @U064X3EF3 rightly points out, you can have just an empty folder with code in that you can run with clojure script.clj or you can create src and put files in there and use clojure -M -m my.main (to run -main in src/my/main.clj). You only need deps.edn when you want to use additional dependencies @U01188CHUFL

practicalli-johnny21:11:03

It depends on a person's understanding of the definition of a project (or empty project) Some editors will look for a deps.edn file to automatically determine its a Clojure CLI project, so I usually start with a deps.edn file with an empty map {} When running a terminal Repl prompt, then no file is actually needed (as many people have started previously). I'll use clj-new or deps-new to create a project from a template (clj-new can use the same templates as Lein new), especially if there is config and code that is not really useful to remember how to write from scratch or some libraries I don't use very often (e.g Figwheel-main template with reagent saves me a bunch of time when creating a ClojureScript app). One day (given time and health) there are some templates I'd like to write for deps-new. For now I just have a number of common config and code files in a GitHub repository that I pick and choose from when relevant.

ahungry06:11:34

An empty directory may be a valid deps.edn project, but I think most people (and perhaps the OP) relate "empty project" to "project template" or "project with scaffolding" - which lein new ... and clj-new always seemed to do the trick well (I hadn't heard of deps-new) - for the pedantic answer, I think the question might be phrased more similarly to "what is the bare minimum required to be considered a valid deps.edn or clojure project"

ahungry06:11:26

For discoverability, if I search "clojure clj-new" on duckduckgo, the first result is the github project page - if I search "clojure deps-new", the first positive match is the 5th result (a reddit thread asking if anyone uses it) - I wonder why it's DDG result is so poor and the project page on github isn't even listed on page 1 of ddg search results

seancorfield06:11:08

Probably because I wrote clj-new some years back and I wrote deps-new more recently?

seancorfield06:11:17

I haven't put any notices on clj-new to deprecate it or discourage usage in favor of deps-new. I fell the latter is much more in the spirit of the CLI tools: simple, and depends only on official Clojure core team libraries (specifically tools.build), whereas the former includes Leiningen code and Boot code (that I wrote back in 2015 or 2016).

🙏 1
ahungry06:11:57

It's odd, I'm sure it has something to do with the project name - when I've chosen a unique name ("ahflate" being the most recent example) it was listed within an hour on duckduckgo as the top result - I figured since ddg pulls some data from bing, and microsoft controls GH+bing atm

ahungry06:11:31

but I see deps-new (commit wise) looks to be a year+ in age - did it just recently move to GH, with those commits being private previously?

seancorfield06:11:25

As I said above: "Probably because I wrote clj-new some years back and I wrote deps-new more recently?"

seancorfield06:11:46

I'm sure 4+ years of existence vs 1+ year of existence of a project has some impact on how widely-used and therefore how widely-referenced it is, and therefore how high it places in search results... Bing delivered 🙂

skylize20:11:00

Totally confused by a quickcheck failure. If I manually call assoc-from with the inputs of either the failed test or the shrunken test then I get expected results. So I think the problem must be in my property definition? 🧵

skylize20:11:33

(defn assoc-from
  "Call `assoc` on a coll with the matching key value of another coll.
     
  
(assoc-from {:foo :bar} {:foo :baz} :foo) ;; => {:foo :baz}
"
  [target source key & keys]
  (if keys
    (let [keys (cons key keys)
          vals (map source keys)]
      (apply assoc target (interleave keys vals)))
    (assoc target key (key source))))
(defn gen-keys-from [m]
  (-> m keys gen/elements gen/vector-distinct gen/not-empty))

(def gen-2-maps
  (gen/let [ks (gen/not-empty (gen/vector gen/keyword))]
    (gen/tuple
     (gen/map (gen/elements ks) gen/any-equatable)
     (gen/not-empty (gen/map (gen/elements ks) gen/any-equatable)))))

(def gen-2-maps+2nd-keys
  (gen/let [[m1 m2] gen-2-maps]
    (gen/tuple (gen/return m1)
               (gen/return m2)
               (gen-keys-from m2))))

(def result-has-kv-pairs-from-map-2
  (prop/for-all [[m1 m2 ks] gen-2-maps+2nd-keys]
                (let [m3 (apply assoc-from m1 m2 ks)]
                  (doseq [k ks]
                    (= (k m3) (k m2))))))

(tc/quick-check 100 result-has-kv-pairs-from-map-2)
{:fail [[{} {:* {}} [:*]]],
 :failed-after-ms 3,
 :failing-size 0,
 :num-tests 1,
 :pass? false,
 :result false,
 :result-data nil,
 :seed 1668455832429,
 :shrunk {:depth 9,
          :pass? false,
          :result false,
          :result-data nil,
          :smallest [[{} {:A 0N} [:A]]],
          :time-shrinking-ms 7,
          :total-nodes-visited 15}}

skylize20:11:10

gen-2-maps+2nd-keys is intended to create a 3-tuple of 2 maps from the same list of keys, and a list of keys generated from the 2nd map.

skylize20:11:35

Then the property is intended to apply each tuple to assoc-from (which assoc's the value of each listed key from map 2 onto map 1), and test that the values from map 2 show up in the result.

hiredman20:11:49

Doseq returns nil which is false

Arek22:11:32

Hi. I just play a little with XTDB (and Datalog in general) and got some questions. :xt/id - is it supposed to be just internal value to group my datoms into one value/map or I can use it the sam way I’d use “id” column in any SQL database? For example use it in rest api or as a reference in relations? If you use it in your code do you use it in namespaced form? It seems a little dirty to me because it makes whole code depend on database. Are Clojarians ok with it? And the second question is about (pull ?e …) syntax(?). Does it must return set of vectors of maps? I mean if we have query like this:

{:find [(pull ?e [*])]
 :where [[?e :user/age 42]]}
we get result:
#{[{:xt/id 1, :user/name "foo", :user/age 42}]
  [{:xt/id 2, :user/name "bar", :user/age 42}]}
It’s not really convenient to have these maps of users wrapped in vectors. And I know I can map over it with first function but it seems to be so common case I feel there’s a better way/ On the other hand I can use :keys so my query becomes:
{:find [e name]
 :keys [id name]
 :where [[e :user/age 42]
         [e :user/name name]]} => #{{:id 1, :name "foo"}, {:id 2, :name "bar"}}
but now keys becomes another thing to maintain and it can lead to some strange bugs

refset23:11:22

Hey @U044Y4Z2R8D map first is the best solution in XTDB here, for the time being, although the "correct" solution is out there but needs implementation effort, see the find specs https://github.com/xtdb/xtdb/issues/1449 feel free to ask questions over in #xtdb or via the other channels 🙂 (e.g. https://discuss.xtdb.com/ or https://juxt-oss.zulipchat.com/)

refset23:11:40

> keys becomes another thing to maintain and it can lead to some strange bugs also if you've seen something that might be a bug please do flag it 😅

Arek13:11:02

Thanks. It’s not a bug. I mean :keys are based on an order of keys in vector and any mistake in the order can lead to hard to find bugs in code sometimes

🙂 1
mister_m22:11:17

Hi, I have a data structure that I'd like to be able to pass to functions that operate on collections like nth and have nth be able to handle my data structure - is there a mechanism in clojure that allows me to do this? Possibly with deftype and then implementing a protocol? I am trying to find out exactly what documentation I need to read that is relevant to this question. If I was writing java I think the analogue of this is that I would have some object that would maybe extend AbstractCollection which would force the implementation of Iterable and Collection.

Alex Miller (Clojure team)22:11:59

clojure.lang.Indexed is the interface needed for nth

👍 1
Alex Miller (Clojure team)22:11:50

https://insideclojure.org/2016/03/16/collections/ may be useful in getting the lay of the land, and there's some more info in Clojure Applied

mister_m22:11:05

Thanks for the references

skylize02:11:00

If your data happens to fit a map-like representation, you can get a lot collection functionality for free by going the other direction: making a protocol to extend a Record type with a few custom methods.