Fork me on GitHub
#clojure
<
2020-08-12
>
wombawomba10:08:07

Is there a (fast) way to apply but with the second to last argument expanded instead of the last?

michael.e.loughlin10:08:58

Arguments after the first (the function) are added to the list of args anyway? Do you have an example snippet?

wombawomba12:08:12

@: I want to do something like

(defn foo [& xs] (apply' str \< xs \>))
such that (= (foo 1 2 3) "<123>")

wombawomba12:08:56

I could do something like

(defn foo [& xs] (str (apply str \< xs) \>))
or
(defn foo [& xs] (str \< (clojure.string/join xs) \>)
…but I don’t want to allocate any temporary strings

wombawomba12:08:10

Likewise I could do something like

(defn foo [& xs] (apply str (concat [\<] xs [\>])))
but that seems like it’d be inefficient as well

michael.e.loughlin12:08:22

have you checked out format or cl-format?

michael.e.loughlin12:08:35

you can also use backtick-quoting and un-splice

michael.e.loughlin12:08:03

(defn foo [& xs]
  (apply str `(\< [email protected] \>)))

(foo 1 2 3)
;; => "<123>"

michael.e.loughlin12:08:00

cl-format takes a stream argument which it can output to, which I'd have to check, but might let you avoid unnecessary string creation

wombawomba13:08:36

I’ve looked at format but not cl-format

wombawomba13:08:57

Format doesn’t work AFAICT since I’d have to generate the format string

wombawomba13:08:41

…but the backtick solution seems like it should work (as long as it’s performant enough)! thx

quoll15:08:40

I’d use your early suggestion, but just passing the opening char (not that this is a big deal):

(defn foo [& xs] (apply str \< (concat xs [\>])))
Why? Because the apply function just uses clojure.lang.AFn/applyTo and that is going to do the work of getting through the final seqable argument no matter how you handle it. Splicing doesn’t change that. The str function then does a recur to run down that seq and append to a string buffer.

quoll16:08:04

The setup of creating a lazy concat to pick up the final element to go on the end is relatively small, and not going to be a noticeable cost.

quoll16:08:07

If you have a performance issue with that (and I’d be surprised if you did!) then you can just pull apart the str implementation and append to the StringBuilder yourself, but I’d be really surprised if you gained any benefit from doing that

wombawomba19:08:58

cool, thanks for the insight @!

quoll19:08:35

When you’re looking to minimize work like that, then the results can be counter-intuitive. It’s worth trying a few million executions of the code in question and timing it. At that point, if you’re unhappy, then it’s worth looking into how it works and see if there is anything redundant happening. (I like to look anyway, just to find out how it works)

quoll20:08:42

If you try (source apply) you’ll see how it uses the IFn method applyTo. That’s at https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFn.java#L143 You’ll see that for anything more than 20 values, it creates an array of the data to pass in, and the function should be handling an array parameter by that point anyway. So what you’re passing will go to an array. Doesn’t seem too bad. Hows does str manage all these parameters? (source str) does this in its final arity:

((fn [^StringBuilder sb more]
          (if more
            (recur (. sb  (append (str (first more)))) (next more))
            (str sb)))
      (new StringBuilder (str x)) ys)
So it just iterates along and uses java.lang.StringBuilder.append(String s) on every element

quoll20:08:19

This is one of the things I like about Clojure. It’s very easy to see what’s actually going on

michael.e.loughlin08:08:04

Inspired by quoll, I looked up what the backtick version expands to:

(apply str (clojure.core/seq (clojure.core/concat (clojure.core/list \<) xs (clojure.core/list \>))))

zilti11:08:42

I have a problem with next-jdbc... It suddenly refuses to work properly. No code changes have been made. Reading from the database works fine. But as soon as I use with-transaction or call a hugsql generated function that would end in a write, I get an

Execution error (ConnectException) at java.net.PlainSocketImpl/socketConnect (PlainSocketImpl.java:-2).
Connection refused (Connection refused)
exception. The role definitely does have write rights from my machine, it is the role I use daily to make changes to the database. Anyone knows what that could be?

nbtheduke13:08:24

might want to ask in #sql

wilkerlucio15:08:08

hello, in CLJS there is the IFind protocol to define how a type should respond to find, is there an analogous one in CLJ?

alexmiller16:08:13

not really. probably the closest is Associative's entryAt() method

alexmiller16:08:00

if a type implements Associative, that method will be used

alexmiller16:08:54

(but there are also special cases in RT.find() for Map and ITransientAssociative2)

lilactown17:08:09

I remember some advice that if app.foo.bar is a namespace, then app.foo should not be a namespace. does anyone know where I might have read this?

alexmiller18:08:12

not an issue at all in clojure (and pretty common) but I think there is some issue with this in cljs?

thheller18:08:12

pretty sure thats only CLJS-only advice since app.foo/bar can clash with app.foo.bar?

noisesmith18:08:38

so namespaces are reified into the same "namespace" as the definitions in them? that's weird

noisesmith18:08:10

I guess that's using js globals, and that's simply the semantics offered?

lilactown18:08:10

yeah clojurescript reifies namespaces as objects & properties on objects

thheller18:08:27

too late to change it to something else 😉

lilactown18:08:24

yeah, plus it has some nice things about it. using closures as namespaces has its own problems…

lilactown18:08:42

that solves my mystery! thank you

thheller18:08:48

nah I'd still have used the same system. just probably would have been better not nested. eg. (ns app.foo) (defn bar ...) becomes ns$app$foo.bar or so

lilactown18:08:02

ah yes that would be better