Fork me on GitHub
#beginners
<
2021-07-27
>
zackteo05:07:39

I have an essay I want to write about aesthetics/beauty of Clojure/Lisp and wanted to ask if there are any resources I might want to look at. Was not really sure which channel to post it in Slack. So I decided to post it as a clojureverse question instead - https://clojureverse.org/t/aesthetics-of-clojure-lisp-resources-to-look-at/7932

solf05:07:06

(and an article about the video, I haven’t read it though: https://www.lvguowei.me/post/the-most-beautiful-program-ever-written/)

seancorfield05:07:20

I posted a link to Gene Kim's "Love Letter to Clojure" on ClojureVerse

Jovanni09:07:30

Hi I have been having a problem with configuring the clojure project. Anyone who help me would be appreciated. Thanks!

seancorfield16:07:44

You'll need to explain in more detail what you mean by that, and what you've tried and what errors you got and/or what unexpected behavior you got.

dgb2309:07:47

I’m slowly becoming comfortable with recursion again after having spent years in imperative C-Style languages (and still). However I suspect I’m reaching for recur too quickly now. How would a experienced Clojurian write this function?

(defn first-distinct
  [c1 c2]
  (loop [c1 c1, c2 c2
         i 0]
    (let [x1 (first c1)
          x2 (first c2)]
      (if (not= x1 x2)
        [x1 x2 i]
        (recur (rest c1) (rest c2)
               (inc i))))))

solf10:07:25

Something like this

(defn first-distinct [c1 c2]
  (->> (map vector c1 c2)
       (map-indexed
        (fn [i [x y]]
          (when (not= x y)
            [x y i])))
       (filter some?)
       first))

(first-distinct [0 1 2 3 4] [0 1 2 30 4]) ;; [3 30 3]

solf10:07:22

It’s rather annoying to write code that deals with indexes in clojure, it goes against “the functional way”. And it’s often because of people trying to translate non-functional code into clojure. If you don’t need the index, you could write the function like this:

(defn first-distinct-2 [c1 c2]
  (->> (map vector c1 c2)
       (filter (fn [[x y]] (not= x y)))
       first))

(first-distinct-2 [0 1 2 3 4] [0 1 2 30 4]) ;; [3 30 3]

solf10:07:58

In general, if you find yourself using indexes in Clojure, it’s worth stopping and asking yourself if you really need to

solf10:07:54

Some benefits of writing code this way is that a lot of edge cases are handled kinda by default, for example:

(first-distinct [] []) ;; => nil
(first-distinct [1 2 3] []) ;; => nil
(first-distinct [1] [1]) ;; => nil
Without having to write terminating condition

solf10:07:51

Oh I like the range trick @U0178V2SLAY

dgb2310:07:04

Thank you for the answers

dgb2310:07:31

I have to think more deeply about why I use an index here. I’m building something bottom up

dgb2310:07:42

so I thought I need it but maybe I don’t

dgb2310:07:39

I’ts not quite doing what I need though, because I want to know both distinct elements and where it occurs, which is why I return a tuple in my original function.

pavlosmelissinos10:07:12

@U0178V2SLAY’s solution (as well as @U7S5E44DB 's original one) also give you the index, so either should be what you need

dgb2310:07:17

(defn first-distinct-2
  [c1 c2]
  (->> (map vector c1 c2 (range))
       (drop-while (fn [[a b _]] (= a b)))
       (first)))

(first-distinct-2 [:a :b] [:a :b :c]) => nil

dgb2310:07:48

(first-distinct [:a :b] [:a :b :c]) => [nil :c 2]

lassemaatta10:07:16

I was just about to mention that my solution assumes the inputs c1 and c2 are of equal length 🙂 (because map ignores any extra elements if the given collections are not of equal length)

👍 2
pavlosmelissinos10:07:46

you could use cycle on the shortest collection edit: actually scratch that, filling it with nils is probably better

dgb2310:07:27

or maybe it is a candidate for for ?

Ed10:07:08

(defn first-distinct-2
    [c1 c2]
    (let [len (max (count c1) (count c2))]
      (->> (map vector (concat c1 (repeat nil)) (concat c2 (repeat nil)) (range len))
           (drop-while (fn [[a b _]] (= a b)))
           (first))))
... or make the range the shortest collection?

dgb2310:07:21

I’m now getting the feeling that I’m trying to make things more complicated than they need to be and should just use recur 😄

Ed10:07:23

I tend to only use recur in the last resort 😉

dgb2310:07:46

Something to take away: its often useful to think of collections as infinite lazy sequences

👍 4
dgb2310:07:44

Ah I like this! it solves the problem of having to determine if I encounter a nil or the actual end/empty list

dgb2310:07:53

feels a bit like mini-parsing or something like that. I’ve seen this pattern of signifying something meaningful with keywords in other functions as well and I like this.

tws12:07:52

alternatively:

user=> (def a [1 2 3 4 9])
#'user/a
user=> (def b [1 2 3 5 8])
#'user/b
user=> (first (for [c (range) :let [x (get a c), y (get b c)] :when (not= x y)] [x y c]))
[4 5 3]

👍 2
tws13:07:27

or

user=> (first (keep-indexed #(when (not= (first %2) (second %2)) [%2 %]) (partition 2 (interleave a b))))
[(4 5) 3]

dgb2313:07:03

(range) is interesting! It is implemented in terms of (iterate inc' 0) which is an object that holds the value of first and possibly next (cached) with the provided “seed” as 0 . whenever you call first or next you get the cached value and with rest you get a new Iterate with inc invoked for next as the “seed”. I think I’m starting to get how lazyness works. It is just a simple interface on a chain of objects that may or may not be cached and evaluate on demand.

dgb2313:07:02

I also just broke my REPL by calling (last (repeat))

😄 2
lassemaatta14:07:03

Be sure to check out articles such as https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects when learning lazyness

🙏 2
noisesmith19:07:01

my personal rule is not to use recur for code that walks a collection in order. reduce if I need side effects or to propagate state or early exit, map/filter/etc. otherwise

👍 8
dgb2309:07:57

(just realized that it needs a terminating condition, but you get the drift)

dgb2309:07:59

(defn first-distinct
  [c1 c2]
  (loop [c1 c1, c2 c2
         i 0]
    (let [x1 (first c1)
          x2 (first c2)]
      (if (and (empty? c2) (empty? c1))
        nil
        (if (not= x1 x2)
          [x1 x2 i]
          (recur (rest c1) (rest c2)
                 (inc i)))))))

Jelle Licht11:07:58

Does the recently released tools.build also allow one to AOT compile certain namespaces, similar to lein’s :aot?

Jelle Licht15:07:46

That makes sense, thank you for the info. It works really well, but is there some way I can get it to run my aot before spawning e.g a repl?

fogus15:07:27

How are you trying to run the task?

Jelle Licht15:07:01

Currently, I manually run clj -T:build aot and then I simply cider-jack-in from Emacs; ideally, I’d somehow skip this manual step.

fogus15:07:52

Ah you mean you want the AOT to happen before the jack-in happens?

Jelle Licht15:07:36

At least before the jack-in is done and my repl connected 🙂

fogus15:07:27

I'm certain that's possible but may require an Emacs config thingie which I'm pretty sketchy on myself.

Jelle Licht15:07:37

perhaps I can make it work with tools.build + :deps/prep-lib

Jakub Šťastný15:07:37

Shouldn't (#(%) "7s") just return "7s"? It's giving me java.lang.String cannot be cast to clojure.lang.IFn and I don't understand why. I only want a fn that returns whatever has been passed in.

dpsutton15:07:30

identity fits your needs. type '#(%) at the repl to see the expansion of that form

👍 2
sheluchin17:07:17

Could someone explain what the Spec Guide means by a regex op? For example, s/keys* is described as returning a regex op. https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html#clojure.spec.alpha/keys*

seancorfield17:07:57

They're like regular expressions but for sequences. Does that help?

sheluchin18:07:36

Not really. I still don't understand how keys differs from keys*. It says the latter can be embedded inside a sequential regex structure. They both describe req/optional keys and validate all registered keys in the map. What is it that keys cannot do that keys* can? Is this a good demo of the difference? I'm presuming this is meant by "embedded in a sequential regex structure":

(s/def ::player (s/keys* :req [::name ::score]))
(s/def ::match (s/tuple ::player ::player))

seancorfield18:07:29

keys is like {:keys [...]} in a function argument list, i.e., a hash map. keys* is like & {:keys [...]}, i.e., named arguments.

sheluchin19:07:36

Ah, so if I change the keys* example in the guide to conform a map instead of a vector, I get:

[:my.config/id :s1 :my.config/host "" :my.config/port 5555] - failed: map? spec: :my.config/server

sheluchin19:07:51

That makes sense now. Basically keys is strictly for maps, while the star version is meant for sequential structures:

{:my.config/id :s1, :my.config/host "", :my.config/port 5555} - failed: (or (nil? %) (sequential? %)) spec: :my.config/server

sheluchin19:07:10

Not sure why this one took me a minute to grok. Thanks @U04V70XH6!

seancorfield19:07:11

Yup, you got it!

seancorfield17:07:50

s/cat in particular, since it can describe a sequence that, say, starts with one or more ints, followed by a string, an optional keyword, and then zero or more ints.

David Campbell17:07:26

Hey guys, I'm running into some issues responding a post request with form-data. Does anybody mind helping me out? lmao

sova-soars-the-sora17:07:51

@dcsoups make sure you have type= da-type set on each form input element.

sova-soars-the-sora18:07:34

@dcsoups do you have any more info like what errors your getting or what the server is receiving?

BillyBadBoy19:07:54

I was playing with the REPL a couple of days back and wanted to map a collection such that each item in the original list is repeated 3 times in the result. So, for example, 1 2 3 would become 1 1 1 2 2 2 3 3 3 . My first attempt didn't compile:

(mapcat #([% % %]) [1 2 3])
After some head scratching, I found my way to the clojure docs, which mention this very problem. They recommend:
(mapcat #(vector % % %) [1 2 3])
But, I like using the literal vector syntax, which I can still use if I do this:
(mapcat #(do [% % %]) [1 2 3])
Now, this certainly works, but is it acceptable Clojure code?

noisesmith19:07:30

there's also repeat

user=> (mapcat (partial repeat 3) (range 5))
(0 0 0 1 1 1 2 2 2 3 3 3 4 4 4)

👋 2
noisesmith19:07:10

(partial repeat 3) could be replaced by #(repeat 3 %) if you prefer

seancorfield19:07:43

I would say it isn't idiomatic. Folks would either use vector or the (fn [x] [x x x]) style.

seancorfield19:07:33

In general, the set of places where #(..) function literals are "acceptable" is pretty small. Stuart Sierra talks about this in his Do's and Don't series on his blog.

BillyBadBoy19:07:31

thanks for the links - i'll add them to my bedtime reading

David Campbell19:07:47

@sova my bad for the late response. I've been using ajax core to post form data here :

David Campbell19:07:47

The format of the form-data is something like :

David Campbell19:07:54

{:tpm/name "David Campbell", :tpm/email "<mailto:[email protected]|[email protected]>", :tpm/jira "nil", :tpm/productGroup "Placeholder", :tpm/pods #{"Placeholder"}}

David Campbell19:07:33

And this is how I'm attempting to respond server side:

David Campbell19:07:34

On the server side I'm using retit.swagger to respond to the post

leif20:07:41

If I write the following code: ^int (+ 1 2), it seems like the reader grabs the value of int at read time and attaches it as metadata to (+ 1 2) . So with that in mind: Is there any way to add new values at read time?

leif20:07:33

Perhaps something like:

(defreadtime annotate ....)
^annotate (+ 1 2)
?

leif20:07:08

Or are you limited to the values built into the reader?

Russell Mull20:07:01

You can put anything you like in there, as a map

emccue20:07:15

^{:some/key "abc"} (+ 1 2)

emccue20:07:11

usually you can read ^thing (...form...) as ^{:tag thing} (...form...)

emccue20:07:03

the exception being ^:dynamic , which turns into ^{:dynamic true} (...form...)

emccue20:07:23

you are limited in what can be in that shorthand list by the reader though

Russell Mull20:07:50

That's the case for any ^:keyword form. See the bottom of the docs page, it has all the variations. (I THINK anyway... going to check)

Russell Mull20:07:50

Yeah, that's how it works.

> (def ^:foobar testing 42)
#'user/testing
> (meta #'testing)
{:foobar true, :line 1, :column 1, :file "NO_SOURCE_PATH", :name testing, :ns #object[clojure.lang.Namespace 0x17ae98d7 "user"]}

leif20:07:02

@rsprangemeijer & @emccue of course, but in the docs it indicates that identifiers evaluate to their value, rather than the name itself.

ghadi20:07:53

@leif what are you trying to do at a high level?

leif20:07:17

@ghadi Add an extendable protocol that can be recognized by the IDE when reading code.

emccue20:07:46

so even when the symbol evals to a keyword, if its not a literal keyword it is put into the tag

leif20:07:01

@emccue Wait, so clojure read time is pulls from the same environment as run time?

Russell Mull20:07:28

it depends how you are running your program

emccue20:07:31

that is different in clojurescript

Russell Mull20:07:32

if you AOT compile, no.

leif20:07:49

Okay, in this case I'm interested in clojurescript.

leif20:07:02

(with self-hosting)

leif20:07:13

(or bootstrapping, whichever term you prefer.)

emccue20:07:31

what about data_readers? would that be good for your use case?

emccue20:07:02

you can make a custom #annotate{:a 1}

leif20:07:21

Can you have code (not just literals) in data_readers?

leif20:07:50

(for reference, this is the page for meta I was looking at: https://cljs.github.io/api/syntax/meta)

emccue20:07:06

like #{:a 1} "abc" ? I don't think so

leif20:07:48

(ya, basically like that.)

leif20:07:12

Like, I would like to be able to place them anywhere I could normally place an arbitrary clojurescript form.

emccue20:07:18

can you write what your ideal syntax is for closer to your usecase?

emccue20:07:29

and a bit of what you would want the semantics to be

leif20:07:31

(Which is why meta seems to suffice, if I can just figure out where its environment comes from.)

leif20:07:44

Absolutely.

leif20:07:57

Here's an example:

(ns my.namespace)
(defvisr component
  (render [this updater] ...)
  (elaborate [this] ...))

...

^{:editor :true
  :construct my.namespace/component} (component+elaborate state)

leif20:07:02

Something like this would be nice.

emccue20:07:42

is component+elaborate a macro?

leif20:07:52

Here (defvisr ...) can expand to whatever is needed to make my.namespace/component in the reader environment.

leif20:07:19

Yes component+elaborate is a macro, at least ideally.

leif20:07:46

But it's also introduced by defvisr

emccue20:07:02

One thing you can do is look at the outer metadata in component+elaborate

emccue20:07:04

(defmacro f []
  (meta &form))
=> #'user/f
^{:a 1} (f)
=> {:line 1, :column 1, :a 1}

leif20:07:18

Right now, I have the defvisr expand to:

(defvisr NAME
  (render ...)
  (elaborate ...))

; =>

(defn NAME+render ...)
(defmacro NAME+elaborate ...)
But again, that part is a little more flexible.

emccue20:07:28

and then have component+elaborate handle it

emccue20:07:44

(component+elaborate 
  {:editor :true
   :construct my.namespace/component}
  state)

emccue20:07:57

but also just passing it would be an option, if not a syntactically pleasing one

leif20:07:36

Oh sure, 'getting' at the metadata is not the problem.

leif20:07:39

The IDE's reader is using an indexing-push-back-reader to get to the metadata.

leif20:07:15

The macro component+elaborate doesn't really need to know about the read-time information of component.

emccue20:07:16

i'm not sure the issue here then

emccue20:07:24

is it because macros aren't "real" in cljs?

emccue20:07:38

and protocols are macros and thats the issue

leif20:07:50

The problem is, going back to the sample:

(ns my.namespace)
(defvisr component
  (render [this updater] ...)
  (elaborate [this] ...))
...
^{:editor :true
  :construct my.namespace/component} (component+elaborate state)
Where does the binding for my.namespace/component come from? It looks like in clojure it can come from the defvisr, since the reader and runtime share the same environment. But in clojurescript (even self-hosted?) that doesn't seem to be the case?

leif20:07:02

So then, where would it come from.

emccue20:07:16

so if defvisir expands to defprotocol and that protocol is bound to component, it doesn't exist anywhere

leif20:07:28

(BTW, the puppy in your image is super adorable. ❤️ )

emccue20:07:02

if defvisir expands to something "real" like def, defn or defrecord then those can exist later

leif20:07:30

It doesn't expand to defprotool, it expands to a defn an a defmacro.

emccue20:07:34

component+elaborate is expanded at compile time so it should have a "real" thing for the protocol

emccue20:07:11

but at this point i'm bottomed out on my macro ability

leif20:07:54

That's alright, thanks anyway. 🙂

sova-soars-the-sora21:07:54

@dcsoups I think :handler ought to be a function that will do something with the data you are providing to the post endpoint. You probably also want to use :body instead of :description

sova-soars-the-sora21:07:27

Most HTTP responses need something that is a :status and a :body ... your :handler ought to be something like a function that prints out the data. Like in this image, the keys on the :body that's :posted, they are destructured via :keys [x y] so you could do something similar with all the variables you mentioned.

sova-soars-the-sora21:07:59

["/slack-modify"
{:post {:summary "Update ze slack channels."
        :responses {200 {:body "Success!"}
                    400 {:body "Oops"}}
        :handler (fn [{{{:keys [your keys here]} :body} :parameters}] (println your keys here))
        :accept :text
        :debug? true
        :throw-entire-message? true}}]
I think that ought to work...

wcf21:07:33

Hi, Anyone has experience using MongoDB with Clojure? What is the best way to connect to MongoDB using Clojure ? I was looking at Monger. But it seems that its not very updated as Java MongoDB driver. I was wondering if maybe the best way is to do some Java interop.

alpox22:07:38

@wcarvalho.ferreira i dont know the case of monger but from my observation clojure libraries have often much less reason to change - are usually very stable and thus dont see as many commits/updates as comparable libraries in languages like java

😎 2
wcf22:07:43

Oh Ok, I just thought that the Clojure lib should have the same updates as the Java driver. But thanks anyway

fubar22:07:11

Hi just got the new pragprog Clojure book and want to give clj another shot. But I’m a vim user and that conflicts with Calva. Anyone know a way to disable the VS Code vim extension when a Clojure buffer is active? Or another good workaround? I don’t want to tool around with Neovim for a long time setting up a new Clojure env in that, if possible

Darrell22:07:43

Have you considered using the vim-fireplace plugin for Vim instead of using VS Code?

alpox22:07:01

I had pretty good success with using the https://github.com/asvetliakov/vscode-neovim extension for vim in vscode together with calva

dpsutton22:07:31

i think there's a #calva channel that would have some people that can help you get your environment configured

alpox22:07:31

I did rebind some paredit stuff to fit better with my vim-knowing fingers though

fubar22:07:02

@U6JS7B99S thanks, vscode-neovim was pretty buggy for me when I tried it a few weeks ago but maybe I’ll give it another shot

alpox22:07:09

It was a bit buggy for me lately before I properly updated neovim, calva and the plugin but you may be using festures I dont

fubar22:07:23

Oh nice are you on 0.5 stable of neovim? I was using an RC of 0.5 head, using a clean init.vim

fubar22:07:59

I just updated to stable and applied the maps on https://calva.io/vim/, will give a try thanks!

alpox23:07:51

@US948FXDL yes, im on 0.5.0 stable

fubar15:07:21

@U6JS7B99S What kind of rebinding did you do? I’m having trouble getting paredit mode to work for me with the Vim cursor. Either it’s buggy or I am not understanding paredit mode

alpox15:07:23

@US948FXDL there is a specific paredit mode? Im using mostly the basic slurp/barf sexp stuff from calva. No special mode for it. That said I can dm you my bindings

seancorfield22:07:44

@wcarvalho.ferreira I think Monger is the most up-to-date MongoDB wrapper at this point. I thought it had all been updated for the 3.0 drivers but it's been a while since I last looked. We were very heavily into MongoDB for years but we abandoned it completely and migrated back to MySQL... I used to maintain CongoMongo but I would pretty much always recommend folks use Monger instead (once it dropped its dynamic var API).

wcf22:07:59

Thanks @U04V70XH6 I will take a careful look at Monger and see if I can use in my project.

alpox22:07:41

Looks like the java drivers are already on 4.x though and monger still on 3.x :thinking_face:

wcf22:07:55

Yes ... that's my concern

seancorfield22:07:15

It certainly hasn't helped the 2.x -> 3.x was a load of breaking changes and I wouldn't be surprised if 3.x -> 4.x also has breaking changes. But as long as the 3.x drivers work with whatever actual server version of MongoDB you're using, you should be OK. We ran with the 2.x drivers against the 3.x server for a while I think...? It was years ago...

wcf23:07:23

Ok ... thanks again @U04V70XH6

Mauro23:07:39

Hi. I'm learning clojure. It's being fun. How can I have a better REPL? Currently my default clojure -r REPL doesn't support arrow keys to navigate, which is pretty limiting.

Russell Mull23:07:56

https://github.com/bhauman/rebel-readline is a pretty nice enhancement to that workflow

5
Russell Mull23:07:07

There may be something newer/fancier though

dpsutton23:07:17

clj will be an immediate improvement for you

🙌 5
dpsutton23:07:35

it's clojure but with readline support which was your chief complaint here

dpsutton23:07:51

just type clj (no need for the -r on either one of these for this purpose)

seancorfield23:07:14

A bit +1 for Rebel Readline -- that's what I use for all my console REPLs. I have this in :aliases in my ~/.clojure/deps.edn file:

:rebel {:extra-deps {com.bhauman/rebel-readline {:mvn/version "RELEASE"}}
          :main-opts ["-m" "rebel-readline.main"]}
So I can do clojure -M:rebel and get a really nice syntax-colored REPL with auto-complete, inline help, and full arrow key/edit functionality. It's very nice.

👍 6
3
Mauro23:07:45

Thanks all of you. clj works exactly as I was expecting clojure to work. rebel-readline is even better because it has syntax highlighting and probably other cool stuff

dpsutton00:07:14

at some point you will want to figure out how to get your text editor to interact with your repl. being able to evaluate forms in your source files. There should be integrations for your editor of choice

💯 18