Fork me on GitHub
#beginners
<
2023-10-08
>
James Amberger00:10:59

Regarding the clojure cli: I’m working on a namespace that I intend to invoke via -T; is there a way to get an nREPL running for it?

James Amberger00:10:09

Things I can think of: 1. just add it to a src dir 2. start the nREPL from inside the namespace 3. some combination of cli tricks I don’t know that will load the file and run a REPL. I tried some things using -i that didn’t pan out

seancorfield00:10:08

Is the namespace not already in the src directory of the project? I'm not understanding your project structure based on this. How would you intend to use -T for code that isn't on a classpath?

James Amberger00:10:30

the namespace is in the project root; my understanding was that -T will ignore src and look in .

seancorfield00:10:49

If you're using -T:some-alias that alias can specify any :paths you want. Is this just for build.clj or are you developing something you expect other folks to install via -Ttools install?

seancorfield00:10:18

For example, for our build.clj at work, I start an interactive REPL with:

clj -M:build -i build.clj -e "(in-ns 'build)" -r
but when working on the build.clj in an editor, I jack-in with :build as one of the aliases -- and our :build alias uses :extra-deps specifically so it combines when using -M (but will still work as expected standalone with -T:build).

James Amberger02:10:04

Thanks @U04V70XH6, not intended for distribution, it’s just a simple github webhook handler

seancorfield04:10:59

Hmm, you might be better off just having it as part of your codebase, under an alias to control the classpath, and using -X instead -- either way, I think aliases are your friend here.

Nik15:10:41

Following code works

(-> member
                  :id
                  (amount-owed-for-expense expense)
                  (+ amount)
                  round-to-two)
but following doesn't
(-> member
                  :id
                  (amount-owed-for-expense expense)
                  #(+ % (if (pos? %) 0 amount))
                  round-to-two)
Any idea what am I doing wrong? The error is ; IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol Edit - Refactoring it to a defn also worked
defn- offset-by-payment [total-owed transaction-amount]
  (+ total-owed (if (pos? total-owed) 0 transaction-amount))) 

Bob B16:10:53

if we macroexpand a simpler example:

(macroexpand '(-> 7
                #(+ % 2)))
=> (fn* 7 [p1__2521#] (+ p1__2521# 2))
we can see that the macro is a re-write, and when re-writing in this way, it creates a function call that isn't syntactically valid

gratitude-thank-you 1
Ingy döt Net17:10:28

I think you need

(#(+ % (if (pos? %) 0 amount)))

Ingy döt Net17:10:52

lambda functions need to be in parens in a threaded context.

Bob B17:10:27

this "wrapping lambdas" is sometimes considered bad form/confusing: <https://stuartsierra.com/2018/07/06/threading-with-style> see the "Don’t use anonymous functions to change argument position" heading (and judge how you feel about it, of course)

Ingy döt Net17:10:10

Interesting article but to me the lambda-in-parens seems cleanest.

seancorfield17:10:51

as-> is designed for this situation:

(-> member
    :id
    (amount-owed-for-expense expense)
    (as-> x (+ x (if (pos? x) 0 amount)))
    round-to-two)

1
🙌 1
Ingy döt Net17:10:13

One thing I was wondering is if there is a core function that takes a 2-arity function and returns a function with its args reversed?

seancorfield17:10:35

That would be like flip in Haskell. No, Clojure does not have that.

seancorfield17:10:00

(funnily enough, that was being discussed in another thread in the past couple of days)

Ingy döt Net17:10:13

The article doesn't like the inline as-> and I think it's slightly less readable than the lambda-in-parens 🙂

seancorfield17:10:47

We have a little "Clojure extensions" library at work and I added flip ages ago but we just stopped using it -- in favor of "plain" idiomatic Clojure 🙂

Ingy döt Net17:10:48

flip seems like it could be nice here

Ingy döt Net17:10:03

that also makes sense I guess

Ingy döt Net17:10:36

flip should be core and idiomatic!

seancorfield17:10:04

In the follow-up article, Sierra says of: > That’s a good use case for as->, but I would tread cautiously even here. as-> subverts the usual pattern of -> and, as such, should be used only in exceptional situations where the alternative is worse.

1
Ingy döt Net17:10:40

funny I was thinking about this and googled for "clojure flip" (just guessing it would be called flip)

seancorfield17:10:34

So even there, Sierra thinks there are good uses of as-> We don't use it a lot at work but we do use it. We used to have maybe a dozen instances of flip in our codebase but over time we just refactored them away as we simplified code -- as part of breaking up long chains of threaded calls.

Bob B17:10:22

sort of tangential, but flip also gets interesting when you commonly have vararg functions and what not

Ingy döt Net17:10:30

was flip a macro?

seancorfield18:10:02

I think as-> is "OK" if you have a long chain of calls in -> and need something special for one in the middle. If the odd-one-out is at the end, use let to name the result of the -> chain and then just write an expression using that name. Same with cond-> or ->> in the middle of a long -> chain. But also maybe think about not having that long chain in the first place since it macroexpands to a deeply nested set of calls -- which you'd probably consider a bit of a "smell" and you would break it up and name the pieces. -> lulls us into a bit of a sense of false security since it "hides" the deep nesting and so we are less encouraged to break our code into smaller pieces.

Ingy döt Net18:10:10

What's wrong with a lambda in the middle?

seancorfield18:10:20

(#(..) ..) you mean? I think you're the first person I've run into who thinks that is readable 🙂

seancorfield18:10:13

Avoiding that sort of code was the whole reason Rich added as-> (and, boy, were there some long arguments about the naming of that macro!).

seancorfield18:10:22

People had been clamoring for something like as-> for ages back then...

seancorfield18:10:48

(if I recall correctly, it was talked about as let-> for quite a while before it became as->)

Ingy döt Net18:10:34

(as-> _ (foo _ bar))
(#(foo % bar))
lambda wins for me 🙂

seancorfield18:10:07

You don't need it in that simple case. Just use ->

seancorfield18:10:36

You don't need it in the #(foo bar %) case either -- use ->>

Ingy döt Net18:10:50

I was just comparing the 2 forms on their own

Ingy döt Net18:10:17

the context of ->> was assumed 😛

seancorfield18:10:54

But it's a false comparison. The OP's example was better, since it used the threaded expression twice. And you can't nest these things in ->> -- you can't use as-> or -> or cond-> etc inside ->>.

Ingy döt Net18:10:23

sigh. let's drop it here

seancorfield18:10:23

I can see a temptation to use (#(foo % bar)) inside ->> but I'd do what Sierra recommends in that case and write a local adapter fn in let above:

(let [foo-bar (fn [x] (foo x bar))]
  (->> ...
       (foo-bar)
       ...))

Nik15:10:28

So my error was that I just created the lambda and didn't evaluate it. Got to learn lot of other good stuff also. Thanks everyone!

Matthew Twomey23:10:27

It’s interesting to read. I’m a relative newcomer, with just over a year of using clojure for personal tooling. I learned from this slack that it’s considered “smell” to use lambdas in that way and I just use the as-> approach that’s mentioned here. I have no strong preference, so I just went with it. However, I also can be counted among those that find the lambda approach just as readable and comfortable as the alternatives. No idea why.

😎 2