Fork me on GitHub
#code-reviews
<
2022-01-14
>
Drew Verlee04:01:42

Is this lisp written in lisp?

Drew Verlee16:01:26

☯️

❤️ 1
vemv14:01:33

Another topic, I found myself writing this to sort by multiple criteria at once: (emphasis on str + a number that will force a certain priority in the sorting)

(sort-by (fn [[entry {:keys [lib-name path-key]}]]
           (cond
             (and path-key (original-paths-set entry))
             (str "0" entry)

             path-key
             (str "1" entry)

             (original-deps-set lib-name)
             (str "2" lib-name)

             (not (-> lib-name str (.contains "$")))
             (str "3" lib-name)

             true
             (str "4" lib-name))))
It works, but I wonder if there's something more efficient than creating strings, I don't like much the idea of creating garbage :)

Ben Sless15:01:45

Write a custom comparator?

magnars15:01:21

(sort-by (juxt :a :b) [{:a 3 :b 1}
                       {:a 1 :b 1}
                       {:a 1 :b 2}])

=> ({:a 1, :b 1} {:a 1, :b 2} {:a 3, :b 1})

Ben Sless15:01:49

Juxt allocates a tuple for the result

👀 1
vemv15:01:17

yeah was wondering how convoluted the comparator would look like. I haven't dived into it but perhaps the more criteria you add, the more convoluted it would become

Ben Sless16:01:53

You could write a function which takes a hierarchy of predicates a d returns a comparator, or something similar

👀 1
noisesmith17:01:56

you can also just construct a vector instead of a string [0 entry] [1 entry] [2 lib-name] etc as long as your algorithm ensures an entry and lib-name are never compared against one another

noisesmith17:01:37

my custom comparators often end up creating vectors for the comparison (not free, but cheaper than those strings allocation wise)

👀 1
noisesmith17:01:04

this is clojure, every function call represents multiple allocations

😱 1
vemv17:01:54

> this is clojure, every function call represents multiple allocations I wouldn't have imagined that. Even invoking an empty or near-empty defn (`(defn foo [] 42)`) represents an alloc?

noisesmith17:01:25

the arg list is an alloc, for example (perhaps there's an optimization if the list is empty)

noisesmith17:01:16

remember that the jvm doesn't store any data structures on the stack, if it's not an immediate primitive, it's a heap allocation

noisesmith17:01:18

I'd have to go read code to get the specifics, but even for just entering the scope of the function, I'd assume there's an alloc or two...

noisesmith17:01:42

unless foo is a local that was captured (in which case we need to analyze the parent that captured it...) even just calling foo will cause a var lookup from the ns at the very least, and I don't think that's garbage free in clojure

vemv17:01:27

Great insights thanks!

noisesmith17:01:39

to get the real details, check out the stack traces you see with simple function calls, and ideally find a good disassembler (|'ve been meaning to check what the state of the art is these days... no.disassemble was nice but inconvenient)

noisesmith17:01:52

(ins)user=> (decompile (fn [] (foo)))

// Decompiling class: user$fn_line_1__212
import clojure.lang.*;

public final class user$fn_line_1__212 extends AFunction
{
    public static final Var const__0;
    
    public static Object invokeStatic() {
        return ((IFn)user$fn_line_1__212.const__0.getRawRoot()).invoke();
    }
    
    @Override
    public Object invoke() {
        return invokeStatic();
    }
    
    static {
        const__0 = RT.var("user", "foo");
    }
}

nil

noisesmith17:01:26

so the allocation and var lookup is being done statically - better perf wise than I first imagined

Ben Sless20:01:27

(fn [a b c] ,,,) is compiled to a method invoke(Object a, Object b, Object c) (fn [& args]) will alloc an ArraySeq and call a different method

Ben Sless20:01:07

the var lookup via .getRawRoot depends on direct linking, which is off by default

✔️ 1
stopa23:01:43

What an interesting thread. Thanks for that @U051SS2EU !