Fork me on GitHub
#beginners
<
2021-08-13
>
j01:08:44

For Clojure CLI, what's the difference between clj and clojure? The help text for clj says the following:

'clj' and 'clojure' differ in that 'clj' has extra support for use as a REPL in a terminal, and should be preferred unless you don't want that support, then use 'clojure'.
But I get a REPL I run both clj and clojure. Are they interchangable?

dpsutton02:08:20

Press keys like the up key after evaluating a few forms. Also things like control e for end of line and control a for beginning of line

dpsutton02:08:55

Clj has all the goodies of rlwrap. Clojure will print those key codes as if they are normal input. (Which they are)

j14:08:12

Ohh! I'm usually on windows and clj and clojure seemed to be identical on powershell. I just tested that on Linux and you're right! Thanks for the explanation!

dpsutton14:08:20

ah i don't know what the state of the cli tools is on windows. there is a channel #clj-on-windows that can provide more tailored answers for you there

👍 3
noisesmith16:08:12

another bonus is that if you don't like emacs keybindings you can set up .inputrc to edit your repl input in vi mode

olaf10:08:34

(defmacro with-context
  [ctx & forms]
  (doall
   (for [form forms]
     (if (seq? form)
       (with-meta `(~(first form) ~ctx ~@(next form)) (meta form))
       (list form ctx))))) 
What's wrong with this macro? I simply modified the threading -> one, to not use recursion
user=> (macroexpand-1 '(with-context 2 (+ 3) (+ 5)))
((+ 2 3) (+ 2 5))
user=> (with-context 2 (+ 3) (+ 5))
Execution error (ClassCastException) at user/eval2042 (REPL:1).
class java.lang.Long cannot be cast to class clojure.lang.IFn (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'bootstrap')

indy12:08:29

I guess you know what the error means, which is, that (+ 2 3) which becomes 5 cannot be invoked as a function. But if you're asking why it doesn't work, if you intend it to work like ->, it's because you're taking the first arg and returning a "flat" list that just inserts the first arg in this (fn _ arg2 arg3) position. While the thread first macro nests the forms by collecting the results as the first arg.

🙌 3
indy12:08:57

You can try reduce, if the intention is to have the progression seem "linear" than "recursive" or "circular" . Although loop recur is the construct closest to for/while in other langs.

indy12:08:16

I wrote one quickly

(defmacro my-> 
  [x & forms]
  (reduce (fn [expanded form]
            (if (seq? form)
              `(~(first form) ~expanded ~@(next form))
              (list form expanded)))
          x
          forms))

indy12:08:35

(macroexpand-1 '(my-> {:a 1} (assoc :b 2) (update :a inc)))
=> (update (assoc {:a 1} :b 2) :a inc)

olaf13:08:43

Hi @UL33K1E8K, thanks for the response. Unfortunately I did paid that much attention to the meaning of the error 😞 . But you hint put me on the right path and I solved the macro.

(defmacro with-context [x & forms]
  (list* `do
    (doall
      (for [form forms]
        (if (seq? form)
          (with-meta `(~(first form) ~x ~@(next form)) (meta form))
          (list form x))))))
More than threading I needed to send the first argument x (aka context) as first first argument to each form. But not in a recursive way, so no nested functions.
(macroexpand-1 '(with-context {:a 1} (assoc :b 2) (update :a inc)))
=> (do (assoc {:a 1} :b 2) (update {:a 1} :a inc))

indy13:08:59

Oh alright, if you're looking for something out of the box than a personal exercise, there is doto

👍 3
edo14:08:13

Hey everyone, I’m learning Clojure(Script). Been doing a cljs frontend using jacekschae shadow-cljs-tailwindcss boilerplate https://github.com/jacekschae/shadow-cljs-tailwindcss. I’m confused on how to deploy it to heroku :thinking_face: On one hand package.json already contains the release script and I get everything I need to serve it inside the public folder. But in this case I’d serve a node.js app. On the other hand I think it would be more clean to create a jar and use heroku java buildpack. I can’t find any beginner resource that explains this clearly. Can someone provide some hints? TIA. ❤️

deleted14:08:22

well, you can still use the build steps from there 😄

edo14:08:40

Thank you @deleted-user 🙂 I’d also like to not use docker for now :rolling_on_the_floor_laughing:

edo14:08:24

But I think I can read it and do it without docker 🙂 Going to tinker a bit 😄 I’m loving Clojure but the tooling… kills me softly :rolling_on_the_floor_laughing:

edo15:08:50

Yeah, in my case, it could be a static bundle. :thinking_face:

edo15:08:26

Good, going to post it there. :thumbsup: Thanks!

Dudley Craig14:08:09

coming back to clojure after 3 years absence and trying to figure out how to manage an environment (db, api settings) with cli deps instead of lein and environ? Or is environ also the standard for deps?

emccue14:08:57

nowadays deps.edn is the thing with the built in tooling

Dudley Craig15:08:44

thanx @U3JH98J4R ... so create an external edn file and slurp it via clj tools?

Drew Verlee15:08:13

@U02A7D8LYN9 what are you trying to do?

Dudley Craig15:08:53

add dev/prod settings for db

Dudley Craig15:08:36

@deleted-user read a little on cprop, will have a more aggressive look at it 🙂 ... thanx

Dudley Craig15:08:53

@deleted-user yup, I did a LOT of searching and couldn't find much. Source repos naturally wouldn't contain their environment settings either ... so it's tough to search for

Dudley Craig15:08:15

and the docs on deps tooling is very vague, pointing at custom implementations via System.getProperties and System.getEnv

emccue15:08:05

at work we use outpace config

emccue15:08:36

for personal stuff I'm partial to https://github.com/lightbend/config

emccue15:08:51

I know that makes me a wierdo in the clojure world, but whatever

🙌 3
Dudley Craig15:08:02

lolz ... personally after working with typescript I know there's a point where enough is enough ... and spec is enough 🙂

Dudley Craig15:08:07

@deleted-user admittedly that immediately looks clean and concise

Dudley Craig15:08:46

thanx all (@deleted-user, @U3JH98J4R, and @U0DJ4T5U1), this does give me plenty to research and hopefully find a natural fit for deps and a CI pipeline.

bubblebobble 3
sheluchin15:08:59

If I'm passing around a map argument, should I ever filter the map so that only keys relevant to the function I'm calling are left? For example, if I'm passing around a props map for a person like {:person/id 1 :person/name "bob}, and the delete-person function only cares about :person/id, should the calling code ever remove the extra fields from the map before passing it to delete-person, or should delete-person just deal with it by destructuring the map arg?

Russell Mull15:08:59

It is better to do it the second way.

Russell Mull15:08:50

By having each function look in the map for only the keys they care about, you can add other keys to the map later on, for things you haven't thought of yet.

quoll15:08:38

What Russell said. Also, removing unused fields requires unnecessary code, and creates a new object (that shares structure with the initial object). It’s an expense that gains nothing.

sova-soars-the-sora18:08:42

What's that quote...? "it is better to have many functions act upon a single data source" Data is like a treasure chest And functions are the scalpel and surgeon's tools

sheluchin20:08:37

Are there reasonable limits to that approach? For example, if I need to pass some data about a person from the UI to the backend for deletion, and the backend only needs the :person/id, is it still idiomatic to pass the entire person props when the backend fn only needs one field? What if the person props is a huge map with a large number of entries, often entirely unrelated to backend code?

Russell Mull20:08:30

The size of the map doesn't matter here; everything is passed by reference, and the cost to look up a key in even a very large map is still quite small.

Russell Mull20:08:07

I personally prefer passing the raw value when only a single value is required.

Russell Mull20:08:46

But others advocate that it should be a map with {:person/id 42} (and optionally anything along for the ride. Their rationale is that, absent the :person/id, 42 has no inherent meaning.

Russell Mull20:08:23

While I agree with the reasoning, I think it's a little impractical.

sheluchin20:08:24

I think it also reduces the readability of the client code. There's some intent communicated when the client code picks specific values to pass on instead of passing everything it has. It seems to conflict with the "explicit is better than implicit" approach that's preferred in Python.

sheluchin20:08:04

And I don't think everything is passed by reference in the example that ties UI/backend together. The values have to be serialized to make it into a request. No?

Russell Mull20:08:02

The considerations are very different if you have a network in the middle, yes.

sheluchin13:08:38

Thanks for the input everyone. I understand the general approach that I should take of just letting the data flow through to whatever needs it instead of doing irrelevant filtering, but clearly there are some nuances involved.

sova-soars-the-sora04:08:55

For sending stuff over the wire I usually have users authenticated so the ring-response-map has their identifier or email in a session map... but the actual messages I send are quite brief because you want wire stuff to be minimal and snappy -- typically transaction deltas. recall that if you have a map with lots of unique keys, it's easy to update some info related to that key... if i want to update the person/id i might need their e-mail or phone number and their id, but i don't need their address, or date of birth, or state of residence, or anything else... so yeah, for updating data, i think little snips and slices are preferred.

👍 3
Anders Persson15:08:39

Now at last, my book "Getting Clojure" is on it way, so i can start learning Clojure, my enviroment is Windows 10, and i am planning to use Visual Studio Code, any good adwise for a beginner?

practicalli-johnny17:08:16

Windows Subsystem for Linux seems to be the recommended way to run Clojure. There is also support in VS Code for WSL Otherwise scoop.sh is a very good alternative Some info here https://practical.li/clojure/clojure-tools/install/clojure.html#windows

seancorfield19:08:52

A big +100 for WSL2 on Windows 10 and the VS Code Remote WSL extension. So much Clojure literature assumes macOS/Linux so using WSL just makes consuming that stuff so much easier than trying to deal with the differences in Powershell -- and you'll definitely find libraries and tools that simply don't work on Windows 😐

👍 6
Anders Persson15:08:46

@deleted-user Thanks, vill look at it, I have WSL on my machine but the plan in the beginning at least is to use Windows. I have installed Powershell version of CLJ port but if it is better on WSL och Linux i'am to new to tell.

leif16:08:51

In indexing-push-back-reader , I see get-line-number and get-column-number (and also the end line number and column numbers), is there any similar mechanism for getting a character's index?

leif16:08:07

(Otherwise it seems like I'd have to take the original input to indexing-push-back-reader, walk out get-line-number newlines, and then add get-column-number chars...But it seems like this functionality should should already be there.)

Brice Ruth22:08:22

Is there an FAQ for getting clojure tools running on macOS Monterey yet? macOS 12.0 beta ... I ask because using brew install clojure/tools/clojure throws a weird error that I'm not seeing on other homebrew packages, specifically:

Error: An exception occurred within a child process:
  NoMethodError: undefined method `path' for nil:NilClass
Did you mean?  paths
I've tried with xcode-select set to the stable release Xcode as well as Xcode-beta. Tried hacking brew.sh as well, no dice. As near as I can tell from running --debug and looking at where it's bailing and it's failing on MacOS.sdk being nil, ultimately ... no idea why or why other formulas aren't affected, but there you have it.

Alex Miller (Clojure team)22:08:22

sorry, never seen it, have no idea

José22:08:49

Hey

(conj (reduce #(conj %1 %2) [:start] [:a :b]) :end)

jaihindhreddy07:08:19

#(conj %1 %2) is the same as conj, and reduceing with conj is exactly what into does. So, it becomes (conj (into [:start] [:a :b]) :end). And because you mentioned creating a "nested array structure", I would recommend avoiding concat, because it may lead to stack overflows. Check out https://stuartsierra.com/2015/04/26/clojure-donts-concat for a good explanation of why this is.

José22:08:09

is there a simpler more idiomatic way to write that?

José22:08:26

given an existing vector append an element to the start and other to the end

schmee22:08:51

@pepone.onrez (conj (into [:start] [:a :b]) :end)

clojure-spin 3
👍 2
tschady23:08:26

(concat [:start] [:a :b] [:end])

dpsutton23:08:43

(conj [:start] :a :b :end)

José23:08:16

In my case [:a :b] is a function parameter that is already a vector so @tws suggestions do what I want

schmee23:08:44

heads up though:

=> (type (concat [:start] [:a :b] [:end]))
clojure.lang.LazySeq

schmee23:08:50

which may or may not be what you want

José23:08:44

I think lazy is fine in my case, I'm creating a nested array structure, different functions create pieces of the array, and at the end, I iterate it and emit some output