Fork me on GitHub
#beginners
<
2020-06-03
>
Michael W04:06:15

Can anyone tell me why this macro is working? From http://braveclojure.com and my understanding of macros this should not be working but it does. https://github.com/michaelwhitford/clojure-swapi/blob/master/src/swapi/core.clj#L48

didibus09:06:31

That should not work

didibus09:06:41

You need to quote

didibus09:06:22

(defmacro create-queries!
  "Create 3 new functions for one action of the api
  `action` required: config set"
  [action]
  `(do (def ~(symbol (str action "!")) (partial query! 'action))
      (def ~(symbol (str action "+")) (partial query+ pool 'action))
      (def ~(symbol (str action "-schema!")) (partial (str 'action "!") 'action "schema"))))

didibus09:06:56

So, you can execute code at compile time without quoting. But in your case, def is a special form, and it takes a symbol, which is why you need to return a quoted version of the code where def is then given a symbol which is resolved at compile time

Michael W12:06:50

Thank you for that but it still works but I cannot expand the macro, and it has a bug in that species does not get "-schema!" appended, only "-schema" and since I cannot expand it at the repl I can't see what it is doing...

noisesmith13:06:41

what does your attempt to expand the macro look like?

noisesmith13:06:48

this is what I see locally

user=> (pprint (macroexpand '(create-queries! foo)))
(do
 (def foo! (clojure.core/partial user/query! 'user/action))
 (def foo+ (clojure.core/partial user/query+ user/pool 'user/action))
 (def
  foo-schema!
  (clojure.core/partial
   (clojure.core/str 'user/action "!")
   'user/action
   "schema")))

noisesmith13:06:33

the nice thing about macro development is I don't even need query+ or pool or action to exist

noisesmith13:06:32

that macro-expand exposes at least one bug in the macro - user/action! is under-evaluated, probably missing a ~

didibus01:06:27

Is that you trying the macro I posted or the one from OP's link?

didibus01:06:58

Ya, I don't know why your macro works in your code. When I copy/paste your macro as is, I get a compile error

Michael W01:06:16

Yeah me either and I can't figure out how to get macroexpand to work, it never shows me an expanded macro, just the call.

Michael W01:06:07

swapi.core=> (macroexpand '(create-queries! "planets"))
(create-queries! "planets")

Michael W01:06:29

But if a macroexpand a when call I get the expanded macro

Michael W01:06:59

swapi.core=> (ns-interns *ns*)
{films-schema! #'swapi.core/films-schema!, planets! #'swapi.core/planets!, options #'swapi.core/options, species! #'swapi.core/species!, people! #'swapi.core/people!, vehicles-schema! #'swapi.core/vehicles-schema!, p #'swapi.core/p, a #'swapi.core/a, planets-schema! #'swapi.core/planets-schema!, species-schema #'swapi.core/species-schema, pool #'swapi.core/pool, people+ #'swapi.core/people+, reset #'swapi.core/reset, starships! #'swapi.core/starships!, starships-schema! #'swapi.core/starships-schema!, people-schema! #'swapi.core/people-schema!, vehicles! #'swapi.core/vehicles!, species+ #'swapi.core/species+, films+ #'swapi.core/films+, films! #'swapi.core/films!, planets+ #'swapi.core/planets+, vehicles+ #'swapi.core/vehicles+, url #'swapi.core/url, query! #'swapi.core/query!, query+ #'swapi.core/query+, starships+ #'swapi.core/starships+}

Michael W01:06:53

It is for sure creating the functions, except species drops the ! for some reason. It's just weird.

Michael W01:06:30

Thanks for looking at it anyways, I think for now I will go back to a function that generates the functions, that worked fine I just wanted to learn macros

didibus01:06:26

Oh, you changed your macro since I last checked at it?

didibus01:06:45

I can show you how to make it work. But I do not know why the previous macro you had, where you were just doing inline (def (str ...)) without a quote or unquote would have not thrown an error for you

Daniel Östling06:06:38

In trying to get better at well written Clojure, does anyone have some projects to point to for study?

hindol06:06:55

You can look at projects under the org.clojure namespace. Plus, there are a few developers who have reached celebrity status, like weavejester. You can check out those repos as well.

dharrigan07:06:05

Sean Corfield's repos, like next.jdbc are great too!

Daniel Östling09:06:53

Thanks guys, I appreciate it 🙂 And sorry for late reply, meetings ...

tio08:06:22

What is wrong with this line?

(api/mutes-users-create
 creds :params {:screen_name "jack"})

tio08:06:53

This is the error I get:

Syntax error (NullPointerException) compiling at (src/..../core.clj:7:1).
null

didibus09:06:39

Most likely creds is nil

tio18:06:42

Creds is not nil

didibus00:06:33

Well then its probably something inside that function that would be nil

didibus01:06:04

It helps if you look at the full stacktrace, it could show you the exact line where the exception is first thrown

didibus01:06:37

Also because it says compiling at... I think it might be from inside a macro, not sure

tio05:06:36

Yep; creds was nil face palm

tf11:06:10

Hey everyone 👋 . Quick question. What resource (I would prefer a full course) would you suggest to someone who hasn’t done any Clojure before and wants to get up to speed? I’m not new to functional programming, so I’m looking for a course that is a bit more advanced than “what are variables, what are functions …“. Preferably, the resource would be a full (web) application created in clojure + clojurescript, with practical examples of dependency management, project setup, deploying, the language specifics, such as the macro system, etc. Does something like this exist? I prefer to learn by doing (building) but I do appreciate the theory behind topics as well.

❤️ 4
Daniel Östling12:06:15

I'm a bit in the same situation as you describe, having experience from elsewhere. Not really a course, but I've been working through https://www.braveclojure.com/clojure-for-the-brave-and-true/

👍 12
Joshua12:06:58

I was in the exact same position. I found most of my pain was learning new tooling (emacs for the first time) and learning how things are done. I used Clojure for the Brave and True to launch me into actually writing Clojure https://www.braveclojure.com/

👍 4
Daniel Östling12:06:29

Same here about tooling. I ended up picking Emacs/Cider and Boot, for example.

👍 4
Joshua12:06:04

At the end of one of the earlier chapters it says "now go and write some Clojure" which I did and haven't actually returned back to the book (but will do at some point)

tf12:06:07

> which I did and haven’t actually returned back to the book Hahah, developers and taking things literally. Name a more iconic duo! I love it 😄

tf12:06:12

Thanks so much for the suggestions. So emacs is the weapon of choice when writing Clojure? How does VSCode stack up? I don’t like VSCode, but if it allows me to keep my processes unified I’d prefer to stay…

Joshua12:06:15

You know when books say "We really want you to do try our examples?" and you never do? First time I've ever listened 😆

😂 4
Joshua12:06:01

I used to use Sublime, but it doesn't cut it for REPL work, and that's a big deal for quick iterative development so I went exploring

Joshua12:06:48

I first tried VSCode with it's emacs plugin but then I realised the ctrl+casdf shortcut to use it was a pain and I might as well be learning emacs if I want to learn a new big tool

😬 4
Joshua12:06:02

So I'd say, do whatever is closest what you know and minimises the learning time to get you going

👌 4
Daniel Östling12:06:10

Emacs has a learning curve. But once I learned Emacs a few years back, there's not really anything like it. But, I'm a Emacs/Slime/Common Lisp guy too 🙂

👌 4
Daniel Östling12:06:30

Mastering Emacs is a great book, if that's the route you want.

👌 4
noisesmith13:06:22

> Preferably, the resource would be a full (web) application created in clojure + clojurescript this book is the gold standard https://pragprog.com/book/dswdcloj3/web-development-with-clojure-third-edition

👌 4
tf13:06:52

Thanks again for all the resources, guys! The Web development with Clojure book and Brave clojure both look really nice!

Kazuki Yokoyama14:06:36

An alternative is Intellij IDEA + Cursive plugin. It works very well.

👌 8
Kazuki Yokoyama14:06:23

You can also look for libraries to use here: https://www.clojure-toolbox.com/

👌 8
fabrao13:06:45

Hello all, I know my way is the hard way to join, but is there any easy way to [{:dia "2020-05-18" :total 0} {:dia "2020-05-19" :total 0} {:dia "2020-05-20" :total 0} {:dia "2020-05-21" :total 0} {:dia "2020-05-22" :total 0} {:dia "2020-05-23" :total 0}] and {:dia "2020-05-20" :total 1} to be [{:dia "2020-05-18" :total 0} {:dia "2020-05-19" :total 0} {:dia "2020-05-20" :total 0} {:dia "2020-05-21" :total 1} {:dia "2020-05-22" :total 0} {:dia "2020-05-23" :total 0}] ?

Darin Douglass13:06:55

IMO this data structure works much better as a map:

{"2020-05-18 0
 "2020-05-19" 0
 "2020-05-20" 0
 ...}
then you can just do (assoc my-map "2020-05-20" 1)

noisesmith13:06:44

or even (update my-map "2020-05-20" inc)

Kazuki Yokoyama13:06:33

Is this example right? The second argument is about day 20, and the result gets day 21 incremented. If it is right, I don't get what you're trying to do. Do you mind explaining, please?

Kazuki Yokoyama13:06:09

Anyway I seconded @UGRJKK74Y. That could be a much better data structure (except if you've simplified the actual map for the sake of the example).

noisesmith14:06:35

even if it's simplified for the example, you can use reduce or group-by to turn the example into what @UGRJKK74Y suggests, and the change back would be simple as well

noisesmith14:06:09

the right way to solve a clojure problem is usually to turn the data into the shape where the problem is easily solved

Darin Douglass14:06:04

exactly. if this is the end format, i'd recommend a different format for processing then a transformation at the very end

Kazuki Yokoyama14:06:15

Yes, nice tips. Thank you

noisesmith14:06:50

user=> (pprint (denormalize (update (normalize account-vec)
 "2020-05-21" inc)))
({:dia "2020-05-18", :total 0}
 {:dia "2020-05-19", :total 0}
 {:dia "2020-05-20", :total 0}
 {:dia "2020-05-21", :total 1}
 {:dia "2020-05-22", :total 0}
 {:dia "2020-05-23", :total 0})

noisesmith14:06:09

(defn denormalize
  [account-map]
  (sort-by :dia
           (into []
                 (map (fn [[k v]]
                        {:dia k :total v}))
                 account-map)))

(defn normalize
  [account-vec]
  (into {}
        (map (fn [{:keys [dia total]}]
               [dia total]))
        account-vec))

noisesmith14:06:20

also, the sanity test:

user=> (= account-vec (denormalize (normalize account-vec)))
true

Kazuki Yokoyama14:06:48

Is it ok to do all this transformation to update one field from performance perspective?

Darin Douglass14:06:32

if you're gonna do somethingl ike that the the normalize step would happen once when you first get the data and the denormalize step would happen right before you send it off to wherever needs it

Kazuki Yokoyama14:06:03

And if this increment is just a small step inside a bigger processing (using other fields of the original map) that must be performed many times?

Kazuki Yokoyama14:06:11

Sorry for playing the devil's advocate here, I really would like to fully understand it

fabrao14:06:14

Thank you for your help, I´ll try using each

noisesmith14:06:47

@UNZQ84WJV I would typically try to arrange things so those conversions happen as few times as possible, but even for a single changed row, the amount of work done isn't so much more than what it would take to filter / replace / reassemble the vector as is

noisesmith14:06:12

(where there are variables like the size of the input of course...)

noisesmith14:06:02

the ideal case of course is changing the canonical record so you don't ever have to convert

Kazuki Yokoyama14:06:25

I see. Yes, I agree that changing the original data structure would be the ideal solution. Thank you, @noisesmith and @UGRJKK74Y for the insights.

dev.4openID14:06:38

Learning how to spec (I have looked at just about many vids on youtube on spec and with my limited knowledge the examples are simple (to teach an understanding as I appreciate), however as soon as the structures are more complex there are no techniques/approaches provided - especially some of real data structures I am sure a pretty complex) I ultimately want to check that if the JSON conforms to the spec. In addition, it would be great to exercise the spec and generate data that is conformant following an API call the returned JSON looks like the following {:abc [{:loca {:addr {:country "USA"}} :dets {:some{:timestamp "2020-04-09T15:27:00" :Url "https://mywebpage.com/answer" :prod {:name "this product"}}} :totalNumber 399 :pieceID ["ABC1" "DEF2" "GHI3"]}]} ;; so for all the leaves I can create as below; (s/def ::country string?) (s/def ::addr (s/keys :req [::country])) (s/def ::loca (s/keys :req {::addr [::country]})) (s/def ::abc (s/keys :req {::loca{::addr [::country]}})) (s/conform ::country "USA") ;; => "USA" (s/conform ::addr {::country "USA"}) ;; => #:myproj.trial.spec{:country "USA"} (s/conform ::loca {::addr {::country "USA"}}) ;; => #:myproj.trial.spec{:addr #:myproj.trial.spec{:country "USA"}} (s/conform ::abc {::loca {::addr {::country "USA"}}}) ;; => #:myproj.trial.spec{:addr #:myproj.trial.spec{:country "USA"}} However this cannot be right as loca is in a vector in the JSON?? The s/def s that have been defined have no relation to the JSON yet ;; (etc ...............) (s/def ::totalNumber (and pos? int?)) (s/conform ::totalNumber 399) ;; => 399 (s/def ::pieceID vector?) (s/conform ::pieceID ["ABC1" "DEF2"]) ;; => ["ABC1" "DEF2"] Q1: Considering the JSON at :abc has a vector to be considered what is the sane approach? Do I s/def ::abc including a map function over all the vector elements and ensure each vector element is considered? Q2: In the example, :pieceID is a vector; therefore do I include the vector in the s/def (analogous to Q1) for :pieceID?

Kazuki Yokoyama14:06:13

@UEGT2541J, it is very hard to understand code without proper formatting. Could you put it in a gist, please?

dev.4openID14:06:23

I deleted the message and will put up again with gist

dev.4openID14:06:21

Resubmitted to slack

dev.4openID14:06:26

Learning how to spec (I have looked at just about many vids on youtube on spec and with my limited knowledge the examples are simple (to teach an understanding as I appreciate), however as soon as the structures are more complex there are no techniques/approaches provided - especially some of real data structures I am sure a pretty complex) I ultimately want to check that if the JSON conforms to the spec. In addition, it would be great to exercise the spec and generate data that is conformant following an API call the returned JSON looks like the following <script src="https://gist.github.com/dev4openid/1becc5be61b764e330edc82d16f99eb6.js"></script> Q1: Considering the JSON at :abc has a vector to be considered what is the sane approach? Do I s/def ::abc including a map function over all the vector elements and ensure each vector element is considered? Q2: In the example, :pieceID is a vector; therefore do I include the vector in the s/def (analogous to Q1) for :pieceID?

Joshua15:06:34

I see references to :abc and :pieceID so probably would output it

dev.4openID15:06:19

yes - it was a JSON before I transformed it into a map 😉

Joshua15:06:32

Oh I see. Here's the URL I think you mean, now I can see the gist: https://gist.github.com/dev4openid/1becc5be61b764e330edc82d16f99eb6

dev.4openID15:06:54

so do i s/def a structure that has :loca :dets : totalNumber : pieceID and then map it over the vector while I am in the s/def of the :abc? Similarly do the same for the PieceID vector?

dev.4openID15:06:44

Is that the appropriate appoach?

Joshua15:06:09

Can you describe to me what should :abc be? The spec is different from the data structure at the top of the gist

Joshua15:06:03

What should each vector contain? A map of loca+dets+totalNumber+pieceID and nothing else? That's simple

dev.4openID15:06:05

:abc is just a keyword that is returned - nothing special about it

dev.4openID15:06:42

The :abc has a vector and the vector has only one intsance of data in it for now

Joshua15:06:25

(s/def ::abc (s/coll-of (s/map-of :req [::loca ::dets ::totalNumber ::pieceID])))

Joshua15:06:01

req-un probably makes more sense: (s/def ::abc (s/coll-of (s/map-of :req-un [::loca ::dets ::totalNumber ::pieceID])))

Joshua15:06:50

Your (s/conform ::abc ...) example will stop working because you're passing it a map, not a vector

dev.4openID15:06:05

OK so by defining all the leaves as I sis - I think that is correct? Then I formulate the appropriate collections? (I refer to the PieceID.)

Joshua15:06:18

"defining all the leaves as I sis" I'm not sure what you mean by "as I sis"?

dev.4openID15:06:28

In this manner I build up the spec. Correct?

dev.4openID15:06:07

Ooops! "leaves as I spec" Fat fingers

Joshua15:06:02

I'm not sure. Bottom up from the leaves could work. I've been working top-down and putting in "any?" where needed

dev.4openID15:06:03

To conclude I build the specs for the lkeaves. For all vectors one would use s/coll-of for each vector until the top level. That way each element has a spec

dev.4openID15:06:48

To ensure coverage. I have been watching https://www.youtube.com/watch?v=fOv_z6E30l0 for example

Joshua15:06:13

Yes that would work

dev.4openID15:06:36

Interesting he does it from the bottom up

Joshua15:06:47

To get familiar with the spec functions use https://clojure.org/api/cheatsheet and see the box where it says "Spec (https://clojure.org/about/spec, https://clojure.org/guides/spec)"

Joshua15:06:05

You can see all the spec functions that work on a collection at a glance: coll-of map-of every every-kv keys merge

Joshua15:06:05

(def ex
  {:abc [{:loca {:addr {:country "USA"}}
          :dets {:some{:timestamp "2020-04-09T15:27:00"
                       :Url ""
                       :prod {:name "this product"}}}
          :totalNumber 399
          :pieceID ["ABC1" "DEF2" "GHI3"]}]})
(s/def ::country string?)
(s/def ::addr (s/keys :req [::country]))
(s/def ::loca (s/keys :req {::addr [::country]}))
(s/def ::dets any?)
(s/def ::totalNumber int?)
(s/def ::pieceID (s/coll-of string?))
(s/def ::abc (s/coll-of (s/keys :req-un [::loca ::dets ::totalNumber ::pieceID])))
(s/def ::root (s/keys :req-un [::abc]))

(s/conform ::root ex)

Joshua15:06:10

That conforms

Joshua15:06:40

See the any? for ::dets

dev.4openID15:06:09

OK, thank for your help. I will have to absorb and get on with it 😉 Cheers

Alex Miller (Clojure team)15:06:30

btw for future questions, #clojure-spec is probably better

Joshua15:06:41

No problem 🙂 The REPL is your friend here for quick experimentation "oh that's how it works". I learnt all of this a week ago -- I'm sure you'll catch up quick

mafcocinco17:06:50

I have a bunch of *.edn files in a resource directory who’s specific names I do not know (or rather don’t want to have to know). What is the Clojure idiomatic way to list all of the files in a resource directory? I have tried (file-seq (io/file (io/resource "path/to/edn/resource/directory"))) . That works during local development but fails to load properly once the project has been compiled to an uberjar.

noisesmith17:06:37

right, resources are not files, and file apis don't work on them except accidentally

noisesmith17:06:19

I do wish we had resource-seq - it wouldn't be hard to write, I might be able to pull up a snippet later if no-one beats me to it

Alex Miller (Clojure team)17:06:57

I think it actually is hard to write in a totally generic way

noisesmith17:06:11

fair - I think the common case is looking for a directory under the startup classpath (where resources ends up), I hope that simplifies it enough

noisesmith17:06:26

even narrowing it to directories and jar files would suffice

Alex Miller (Clojure team)17:06:34

I think it’s worth considering why this is hard and maybe why you shouldn’t do it

Alex Miller (Clojure team)17:06:15

Certainly the op’s use case seems like a bad idea and it being hard is a signal worth listening to

mafcocinco18:06:28

That is wise advice. In the end, I created a single resource file that listed all of the resource files I wanted to load. This imposes the extra step on our dev team of having to update this file whenever a new .edn resource is added (this is a GraphQL server for context) but the upside is that the list of files that is used to construct the schema is explicit. Additionally, the chance of forgetting to add the file is pretty low, assuming the server is simply run during local dev.

mafcocinco17:06:33

yeah, that is what I was looking for but could not find it. My experience matches exactly what you said. If I’m loading a specific file, everything works fine but it chokes on directories as they are not files

amirali18:06:30

Hi guys. i want to add a value by one when ever it meet a condition inside for-loop. its directive way is something like:

for(i=0;i<5:i++){
if(some conditions){
count++}}
But as data is immutable in clojure i cant do count++. How can i apply that? i think i must use recur. But i cant figure out how. thanks in advance

manutter5118:06:44

You can use reduce for things like that.

(reduce (fn [counted i]
          (if some-condition
            (inc counted)
            counted))
        0
        (range 5))

🙏 4
amirali20:06:25

Thank you sir But what if i wanted to do it in nested loop? fn would only take 2 parameters and we need to add second loop param as well. imagine we want to count number of similar members of two [3*3] vectors like:

(defn reduce-test [x y]
  (for [i (range 3)
        j (range 3)]
    (reduce (fn [counted i]
              (if (= (get-in x [i j]) (get-in y [i j]))
                (inc counted)
                counted))
            0
            (range 9))))
this will not count properly cause we only evaluate i inside fn

manutter5121:06:40

You can use pairs of integers plus a little destructuring, like this:

(reduce
 (fn [counted [i j]]
   (let [in-x (get-in x [i j])
         in-y (get-in y [i j])]
     (if (= in-x in-y)
       (inc counted)
       counted)))
 0
 ;; generate a list of pairs of integers
 (for [i (range 3), j (range 3)]
   [i j]))

🙏 4
manutter5121:06:41

This makes a nice toy example, but honestly I’d be surprised to see code like this in code I actually wrote. Clojure (and functional programming) are different enough from imperative programming that you can end up with weird looking code if you describe your problem in terms of “this is what I would do if I were writing a ‘for’ loop in python” (or whatever). If you describe the underlying problem you’re trying to solve, you might see a very different approach from experienced Clojure programmers.

👍 4
amirali21:06:18

sure.Thank u for valuable advice. Actually what i really wanted to do was to check number of same arguments inside nested vectors. So i used my imperative mind-set to settle it down. could u be kind enough to explain functional way of thinking through this problem?

manutter5121:06:19

Can you give me an example of some nested vectors you want to check?

amirali21:06:16

[[1 2 3][4 6 5][7 8 9]] [[2 8 7][4 6 3][9 1 5]] so counted is 2 as we have 2 same args at same [i j]

manutter5121:06:04

Ok, let me think a minute…

👍 4
manutter5121:06:49

Ok, straight matrix manipulations like this do lend themselves to a more imperative approach, but if I wanted to demonstrate a more functional approach, I might try something like this:

(def first-nested-vector [[1 2 3] [4 6 5] [7 8 9]])
(def second-nested-vector [[2 8 7] [4 6 3] [9 1 5]])

(defn count-matching-elements
  [v1 v2]
  (mapv (fn [a b]
         (if (= a b) 1 0))
       v1 v2))

(reduce + (mapcat count-matching-elements first-nested-vector second-nested-vector))

manutter5121:06:19

Basically, count-matching-elements just loops over the elements in each of the vectors and returns a series of 1 and 0's, depending on whether the corresponding elements match or not, and then the reduce just adds them all up.

manutter5121:06:22

But now that I think about this some more, I might be tempted to use a loop/recur.

(defn count-matching-elements-alt
  [v1 v2]
  (loop [total-matches 0
         [next-1 & more-1] v1
         [next-2 & more-2] v2]
    (if-not (and next-1 next-2)
      total-matches
      (if (= next-1 next-2)
        (recur (inc total-matches) more-1 more-2)
        (recur total-matches more-1 more-2)))))

(reduce + (map count-matching-elements-alt first-nested-vector second-nested-vector))

amirali21:06:59

genius! Thank u so much!

👍 4
manutter5121:06:01

It’s longer, but I think it’s also much easier to come back later, read the code, and understand what’s going on, and that’s pretty important if you ever need to fix bugs.

jkrasnay18:06:29

Or (count (filter condition (range 5)))

🎯 12
Alex Miller (Clojure team)19:06:44

^^ try to think in collections, not in elements

🎯 8
Daniel Östling20:06:38

What would be the most efficient way to match up two large-ish vectors of maps like this

[{:key1 someval1, :key2 someval2}, ...] and [{:key1 someval1, :key3 someval4}, ...] such that the matchup would be on key1; [{:key2 someval2, :key3 someval4}, ...]
that is, using key1 to match up key2/3 as the result?

noisesmith20:06:48

I'd start with (group-by :key1 coll)

noisesmith20:06:17

you'd need some further edits to make it match your example (it leaves :key1 in each map), but does most of what you want but it does a reasonable first step for what you want

noisesmith20:06:28

and you probably don't even need to remove :key1

hiredman20:06:23

there is also clojure.set/join

👍 8
noisesmith20:06:54

on closer reading, if you used group-by you'd also want merge-with

Daniel Östling20:06:29

Hm, interesting. Could be set/join solves most of the task. Looking at group-by now, thanks guys 🙂

noisesmith20:06:25

nice, I'll need to remember set/join

user=> (clojure.set/join #{{:x 1 :y 2} {:x 2 :y 3}} #{{:x 1 :z 5} {:x 2 :z 6}})
#{{:x 2, :y 3, :z 6} {:x 1, :y 2, :z 5}}

👍 4
dharrigan20:06:34

Say you have a string like this "aaabcdddefffff", is there a way to split apart into continuous groups, i.e., ["aaa" "b" "c" "ddd" "e" "fffff"]?

hiredman20:06:39

which is to say, I dunno how to do it with a regex, but that is a regular language which is exactly the sort of thing you should be able to do with a regex

ghadi20:06:25

@dharrigan (partition-by identity "aaabcdddefffff")

ghadi20:06:11

user=> (doc partition-by)
-------------------------
clojure.core/partition-by
([f] [f coll])
  Applies f to each value in coll, splitting it each time f returns a
   new value.  Returns a lazy seq of partitions.  Returns a stateful
   transducer when no collection is provided.

dharrigan20:06:29

that's great, gets closer, but the output is like this:

phronmophobic20:06:35

(->> (partition-by identity "aaabcdddefffff")
     (mapv (partial mapv str)))

phronmophobic20:06:10

oh whoops

👍 4
phronmophobic20:06:25

(->> (partition-by identity "aaabcdddefffff")
     (mapv (partial apply str)))

dharrigan20:06:07

wunderbar! thank you @ghadi @sfyire and @hiredman 🙂

dharrigan20:06:34

so elegant 🙂

seancorfield21:06:54

or, if seeing partial apply str makes you shudder

(->> (partition-by identity "aaabcdddefffff")
     (mapv clojure.string/join))

👍 8
seancorfield21:06:03

I think a lot of people forget/don't know that str/join has a 1-arity that joins with an empty string... I've used apply str several times and then gone "Oh, yeah, str/join!" 🙂

😁 4
👍 8
raspasov21:06:48

@dharrigan one last one using transducers:

(transduce
  (comp
    (partition-by identity)
    (map clojure.string/join))
  conj
  "aaabcdddefffff")

noisesmith21:06:02

that makes me almost think for a moment that (into "" ... ...) would be useful

raspasov22:06:01

@noisesmith I guess that works as well:

(into
  []
  (comp
    (partition-by identity)
    (map clojure.string/join))
  "aaabcdddefffff")

noisesmith22:06:08

Oh - I meant the string combine after transduce part, but yeah that too

ghadi22:06:14

(defn into-str
  "reduce coll into a String, given a transducer"
  [xf coll]
  (transduce (comp xf (map str))
             (fn
               ([] (StringBuilder.))
               ([^StringBuilder sb] (.toString sb))
               ([^StringBuilder sb s] (.append sb ^String s)))
             coll))

💯 8
ghadi22:06:31

I copypaste that into a lot of projects @noisesmith

ghadi22:06:46

not #beginners material, but essentially what you're asking about