beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Jim Newton 2020-10-13T07:35:31.244400Z

I always forget where the documentation for the parameter list of functions, (fn, defn, defn-, etc) is. Its not in the https://clojuredocs.org/clojure.core/fn of fn ๐Ÿ˜ž

2020-10-13T07:36:41.244500Z

it is in documentation

(fn name? [params*] exprs*) (fn name? ([params*] exprs*) +)

Jim Newton 2020-10-13T07:38:07.244700Z

This doesn't explain keyword arguments.

Jim Newton 2020-10-13T07:38:24.244900Z

neither https://clojure.org/guides/learn/functions.

2020-10-13T07:38:57.245100Z

keyword arguments?

Jim Newton 2020-10-13T07:39:34.245300Z

yes some functions can be called like (f a :x 100 :y 200)

Jim Newton 2020-10-13T07:39:57.245500Z

there's a way to define these giving default values of x and y

2020-10-13T07:40:01.245700Z

it is a sequence of args

2020-10-13T07:40:10.245900Z

https://clojure.org/guides/destructuring

Jim Newton 2020-10-13T07:41:06.246100Z

the descructuring rules for let are different from those of fn . Is this really not explained anywhere?

2020-10-13T07:41:17.246300Z

they are the same

Jim Newton 2020-10-13T07:42:00.246500Z

no, I cannot define a function like this, for example. (fn [a & others :as all] ...)

Jim Newton 2020-10-13T07:42:24.246700Z

nor can I define a function like this (fn [[[x]]] x)

Jim Newton 2020-10-13T07:42:44.246900Z

right?

Jim Newton 2020-10-13T07:42:56.247100Z

they are very similar but not the same.

2020-10-13T07:43:10.247300Z

2020-10-13T07:43:14.247700Z

you can

1
Jim Newton 2020-10-13T07:43:29.248Z

wow. I didn't know that one. cool.

Jim Newton 2020-10-13T07:43:37.248200Z

but :as doesn't work as far as I know

2020-10-13T07:44:19.248500Z

(fn [& [a & other :as all]])

2020-10-13T07:44:45.248700Z

Jim Newton 2020-10-13T07:45:26.249100Z

but not (fn [a & other :as all] (list a other all))

clojure-rte.rte-core> ((fn [a & other :as all] (list a other all)) 3)
Syntax error macroexpanding clojure.core/fn at (clojure-rte:localhost:59787(clj)*:17647:24).
(:as all) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
a - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list
clojure-rte.rte-core>  

2020-10-13T07:46:14.249300Z

because this form is violating a spec for arglist

Jim Newton 2020-10-13T07:47:14.249500Z

exactly, the format for arglist is different than the format for destructuring. But I don't find an explanation of this difference in the documentation anywhere

2020-10-13T07:47:18.249700Z

note that [ and ] are not a part of arglist, it is a form that is inside of brackets

Jim Newton 2020-10-13T07:47:58.249900Z

BTW, thanks for the link. I found this useful example>

Jim Newton 2020-10-13T07:48:26.250300Z

exactly, and what is inside the brackets is different than destructuring rules.

2020-10-13T07:48:52.250500Z

(fn name? [params*] exprs*)
here params* is a list of destructuring forms representing arglist [ and ] are not included into destructuring forms

2020-10-13T07:48:59.250700Z

no

Jim Newton 2020-10-13T07:49:58.250900Z

Let me try again. I can destructure with [a], [a & others] , and [a & others :as all]

Jim Newton 2020-10-13T07:50:10.251100Z

but I cannot define functions those three ways.

Jim Newton 2020-10-13T07:50:49.251300Z

I see what you're saying though.

2020-10-13T07:51:06.251500Z

Jim Newton 2020-10-13T07:51:11.251900Z

you're claiming that the arglist is not destructuring, but each variable in the argument list can be replaced by a destructuring form.

Jim Newton 2020-10-13T07:52:06.252100Z

and you cannot use :as to get the entire value of the argument list, only the entire value of any element of the argument list.

2020-10-13T07:53:24.252300Z

right

Jim Newton 2020-10-13T07:53:51.252500Z

I suspect this is really a bug in clojure core which has simply become a feature. Imagine writing a function which allows :as in destructuring forms, but explicitly disallows it in argument lists. My vague suspicion is that this is really just an overzealous syntax checker on fn.

Jim Newton 2020-10-13T07:59:06.254900Z

question about destructuring:

(defmethod rte-match :sequential
  [pattern items & {:keys [promise-disjoint]}]
  (rte-match (rte-compile pattern) items))
what is the correct way to make the recursive call to rte-match passing the given keys? I.e., I want to pass :promise-disjoint nil if :promise-disjoint nil was specifically given at the call site, but not if the value is nil simply because it's missing from the map. given that I cannot use :as all in the arglist of rte-match

Jim Newton 2020-10-13T08:02:53.255600Z

I think this works.

(defmethod rte-match :sequential
  [pattern items & {:keys [promise-disjoint] :as keys}]
  (apply rte-match (rte-compile pattern) items keys))

2020-10-13T08:03:24.255800Z

(defmethod rte-match :sequential
  [pattern items & {:keys [promise-disjoint] :as opts}]
  (apply (partial rte-match (rte-compile pattern) items) opts))

Jim Newton 2020-10-13T08:04:44.256Z

I didn't use partial , is it necessary?

2020-10-13T08:06:04.256200Z

no

1
2020-10-13T08:06:51.256400Z

bold assumption

Jim Newton 2020-10-13T08:29:40.256700Z

I added these examples to https://clojuredocs.org/clojure.core/fn

2020-10-13T10:03:38.258300Z

i want to test if the length of a lazy-seq is larger than some value, without exhausting the seq by using count

2020-10-13T10:03:46.258600Z

whatโ€™s the proper approach ?

2020-10-13T10:04:20.258900Z

(take n) and (count) ?

2020-10-13T10:07:44.259300Z

(seq (drop n l-coll))

yuhan 2020-10-13T10:12:36.260100Z

maybe (bounded-count n coll)

Jim Newton 2020-10-13T10:29:59.263700Z

I'm looking to name a parameter to one of my functions, and the semantics are potentially difficult for the caller to understand, but could nevertheless have a big impact on the performance of the function. The function has two modes, 1) it can internally lazily-compile the user's data structure into a form which is very fast. Unfortunately the compilation is slow. 2) it can use a semantically equivalent interpreter which is slow. #2 is better if the user intents to only do it once. But if the user intends to call the function multiple times with different arguments, it may be better to use option #1. What could I call the parameter to make this more apparent to the caller?

Jim Newton 2020-10-13T10:31:41.264200Z

I was thinking of calling it :hot-spot , implying that the more you run it the faster it will become.

Jim Newton 2020-10-13T10:33:54.264600Z

otherwise what is the motivation for intentionally making a function's argument list processor, simply a destructuror?

Jim Newton 2020-10-13T10:34:19.264900Z

I agree, it is a very bold assumption on my part

2020-10-13T10:39:01.265100Z

> otherwise what is the motivation for intentionally making a functionโ€™s argument list processor, simply a destructuror? I guess to simplify reading and compiling processes. As an example: (fn ([x]) ([x :as y]) โ€ฆ) not valid because those forms of the same arity but to throw there compiler need to know about destructuring

2020-10-13T10:39:51.265300Z

compare with [param*] where each param can be a simple symbol or destructuring form

2020-10-13T10:50:10.266300Z

split into two functions with proper documentation for each โ€œmodeโ€

Jim Newton 2020-10-13T10:53:01.269Z

Another question about Destructuring and keyword arguments. My experimentation shows that Destructuring works a particular way, which I like, but I don't know whether this is accidental or by design. If a function is defined as follows:

(defn foo [& {:keys [x y]
              :or {x 12 y 13}}]
  (list x y ))
then I can call the function using apply like this
(apply foo some-map}
However if I want to override some value in the map, I can insert them into the apply call site.
(apply foo :x 300 some-map)
this calls foo with x=300, rather than whatever is in some-map . This is what I'd expect as a Common Lisp user. Is it dependable?

2020-10-13T10:56:45.269700Z

some-map can not be a map in that case

2020-10-13T10:57:43.269900Z

(apply foo :x 300 {:x 200})
Execution error (IllegalArgumentException) at user/foo (REPL:8).
No value supplied for key: [:x 200]
map in clojure is a sequence of key-value pairs

Jim Newton 2020-10-13T11:12:58.270100Z

Here's the function. I don't see how splitting it into two functions solves the problem. Since the function is pretty large, the common code would be factored anyway into a common function, and there'd still need to be a hot-spot argument. Right?

Jim Newton 2020-10-13T11:12:58.270300Z

(defmethod rte-match :Dfa
  [dfa items & {:keys [
                       ;; if the caller promises that never are two transitions in the Dfa
                       ;;   labeled with intersecting types, the use
                       ;;   :promise-disjoint true, in this case rte-match
                       ;;   can be more efficient and can assume that the
                       ;;   clauses can be tested in any order.  If the transitions
                       ;;   are not guaranteed disjoint, then rte-match must
                       ;;   build new type designators each one containing an and-not
                       ;;   of the previously seen types. 
                       promise-disjoint
                       ;; hot-spot = true -- lazily compile the type checks into Bdds
                       ;;    which is slow-going but becomes faster the more often you
                       ;;    re-use the same pattern, either because of loops in the
                       ;;    Dfa, or when the same Dfa is used to match different
                       ;;    input sequences.
                       ;;    
                       ;; hot-spot = false -- always interpret the type checks rather
                       ;;    than converting them to Bdd.  This option is probably faster
                       ;;    if there are few loops in the Dfa, or if you only use the
                       ;;    pattern once to check a single input sequence.
                       hot-spot
                       ]}]
  (let [state-vec (:states dfa)
        sink-states (set (dfa/find-sink-states dfa))]
    (if (empty? sink-states)
      (rte-match (dfa/extend-with-sink-state dfa) items :promise-disjoint promise-disjoint)
      (let [sink-state-id (:index (first sink-states))]
        ;; There are two possible transition functions
        ;;   slow-transition-function -- this is faster if the caller intends to match
        ;;       the pattern only once.   The pattern is matched by an interpreter,
        ;;       and it is possible that the same type predicate will be tested multiple
        ;;       times on the same candidate objects.  If one of the type predicates
        ;;       is (satisfies slow-predicate) then that slow-predicate may be called
        ;;       multiple times, resulting in poor performance, especially if the
        ;;       pattern is used to test multiple sequences.
        ;;   fast-transition-function -- this is faster if the caller intends to match
        ;;       the pattern multiple times with different input sequences.  The
        ;;       pattern is *compiled* into a form where type-designators are converted
        ;;       to Bdds thus each type check guarantees to never check the same
        ;;       type predicate multiple times, and sometimes not at all.
        (letfn [(slow-transition-function [transitions]
                  (fn [candidate sink-state-id]
                    (some (fn [[type next-state-index]]
                            (if (gns/typep candidate type)
                              next-state-index
                              false))
                          transitions)))
                (fast-transition-function [transitions]
                  (dfa/optimized-transition-function transitions promise-disjoint sink-state-id))
                (transition-function [transitions]
                  (if hot-spot
                    (fast-transition-function transitions)
                    (slow-transition-function transitions)))
                (consume [state-index item]
                  (let [state-obj (state-vec state-index)]
                    (cl/cl-cond
                     ((member state-obj sink-states)
                      (reduced false))
                     (((transition-function (:transitions state-obj)) item sink-state-id))
                     (:else (reduced false)))))]
          (let [final-state (reduce consume 0 items)]
            ;; final-state may be integer desgnating the state which was
            ;;  reached on iterating successfully through the input
            ;;  sequence, items.  Or final-state may false, if the
            ;;  iteration finished without iterating through the entire
            ;;  sequence, either because we found ourselves in a
            ;;  sink-state, or we encountered a item for which no transition
            ;;  was possible.
            (cond
              (= false final-state) false
              (:accepting (state-vec final-state)) ((:exit-map dfa) final-state)
              :else false)))))))

2020-10-13T11:20:41.270900Z

iโ€™m not suggesting duplication of code but making two โ€œpublic/interfaceโ€ function for each corresponding mode (rte-match-simple, rte-match-full as an example) each should prepare and set as a default value for that argument

Daniel Stephens 2020-10-13T11:20:45.271100Z

it doesn't error if there are an even number of keys in the some-map, but it's not doing what you want

(do (defn foo [& {:keys [x y]
                  :or {x 12 y 13} :as m}]
      (println x y m))
    (apply foo :x 1 {:x 2 :y 3}))
=> 1 13 {:x 1, [:x 2] [:y 3]}
my understanding is apply treats the last arg as a sequence, and map as a sequence is a sequence of key-value pairs

2020-10-13T11:23:58.271700Z

> map in clojure is a sequence of key-value pairs

2020-10-13T11:24:49.271900Z

the fact that map with even number of keys is not throwing exception doesnโ€™t mean the logic of function is correct)

๐Ÿ‘ 1
Jim Newton 2020-10-13T11:25:42.272200Z

then we have the same question, right? that argument needs a name.

2020-10-13T11:26:51.272400Z

no, you can change the names of functions to describe what they are doing instead

Jim Newton 2020-10-13T11:29:07.272600Z

but the functions call a common function, and pass an argument. that argument needs a name. Or did I misunderstand?

2020-10-13T11:30:57.272800Z

details of implementation doesnโ€™t need to have โ€œabsolutely meaningfulโ€ names as long as they hidden from the caller and properly documented for maintainance reasons

Jim Newton 2020-10-13T11:39:55.273Z

ahh, I'm glad I asked. This means the note that I added to fn documentation some time ago is actually wrong.

(defmethod foo :a [a b & {:keys [x y z] :as all-keys}]
  (apply foo (f a) (f b) all-keys))

Jim Newton 2020-10-13T11:41:08.273200Z

So what's the correct way to call a function, re-passing the keys which were received, or overriding some of them?

2020-10-13T11:42:18.273400Z

bounded-count fits here. but the function itself looks twisted to reason

2020-10-13T11:45:20.274Z

do not use โ€œkeyword argumentsโ€ maybe?

(defmethod foo :a [a b {:keys [x y z] :as all-keys}]
  (foo (f a) (f b) all-keys))

Jim Newton 2020-10-13T11:47:23.274300Z

I'm tempted to write my own version of apply which has more reasonable semantics with optional arguments. couldn't be so hard actually. It's just lisp.

Jim Newton 2020-10-13T11:58:27.274500Z

hmmm it seems clojure doesn't allow me to list the same key multiple times in a call to foo ๐Ÿ˜ž

Jim Newton 2020-10-13T11:59:54.274700Z

I'd expect a call like (foo :x 100 :y 200 :x 300) cause x to be bound to 100 within foo, instead it throws an exception.

Execution error (IllegalArgumentException) at clojure-rte.rte-core/eval8637 (form-init3719502167719786565.clj:93).
Wrong number of args passed to keyword: :x

2020-10-13T12:01:54.274900Z

1. why 100 and not 300? 2. it should working normally

2020-10-13T12:02:10.275100Z

2020-10-13T12:02:23.275500Z

(defn foo [& {:keys [x y]
              :or {x 12 y 13}}]
  (list x y ))

Jim Newton 2020-10-13T12:05:11.275700Z

I'd expect leading values to override later values.

Jim Newton 2020-10-13T12:05:57.275900Z

that way when dealing with argument lists programmatically, you can simply prepend overriding values without having to copy the entire list.

Jim Newton 2020-10-13T12:08:28.276100Z

That means if you have a list of the optional args like my-optional-args, I can call the function as (apply foo my-optional-args) but you would also be able to override some values like this (apply foo :x 200 my-optional-args)

2020-10-13T12:09:47.276400Z

I would rather prepare my arguments before the call

Jim Newton 2020-10-13T12:14:46.276600Z

with my approach no preparation is necessary. The approach is used very often in Common Lisp, just another surprise that something I thought of as fundamental is different in clojure. Not wrong, I suppose, just different.

yuhan 2020-10-13T12:17:51.276800Z

What do you mean by that? the implementation of bounded-count is twisted?

2020-10-13T12:18:48.277Z

I think wrong is thinking that Common List is โ€œmore fundamentalโ€ than clojure ) you could also replace with any other pair of programming languages in the sentence above )

yuhan 2020-10-13T12:19:16.277200Z

(defn count< [n coll]
  (< n (bounded-count n coll)))

yuhan 2020-10-13T12:25:04.277600Z

I think the mismatch here has to do with the fundamental reliance in Common Lisp on the concrete cons cell - that is why prepending arguments seems more "natural"

1
Jim Newton 2020-10-13T12:25:59.277800Z

probably right. if the cons cell is primary, than adding to the beginning is efficient, and becomes commonplace.

yuhan 2020-10-13T12:26:12.278Z

whereas in Clojure you might use vectors instead to store arguments, and conj to the end

โœ”๏ธ 1
yuhan 2020-10-13T12:27:57.278300Z

(also note that structures in Clojure are persistent and immutable, so the distinction of "copying the entire list" doesn't really apply)

โœ”๏ธ 1
2020-10-13T12:28:59.278500Z

I mean the meaning of the return value of bounded-count.

yuhan 2020-10-13T12:31:36.278700Z

ah, I think of it as a sort of (min n actual-count)

yuhan 2020-10-13T12:36:28.279100Z

The idiomatic way to override a value would be (apply foo (assoc some-map :x 300))

Jim Newton 2020-10-13T12:41:10.280Z

can someone explain what destructure does? Its https://clojuredocs.org/clojure.core/destructure is not so illuminating. neither does there seem to be a useful comment in the code where it's defined.

2020-10-13T12:48:16.282600Z

Destructuring is the process of extracting values from collections. Say you have a vector of two elements, [1 2], you can use a let to destructure that in the first and second value:

(let [[a b] my-vector] ...)
Similar things can be done with maps, say you have {:a 1 :b 2} , you can destructure it like this:
(let [{:keys [a b]} my-map] ...)

2020-10-13T12:49:24.282900Z

Iโ€™m pretty sure there is a better page about it

2020-10-13T12:52:14.283500Z

Ah nevermind, I guess you are specifically asking about the function

Jim Newton 2020-10-13T12:57:40.283700Z

indeed

Jim Newton 2020-10-13T12:58:42.284Z

I didn't see a reference to restructure in that link. Did I miss it?

2020-10-13T12:59:31.284200Z

I misunderstood what you asked

Jim Newton 2020-10-13T13:00:58.284400Z

I was hoping it might be a programmatic interface to destructuring. In the coming weeks, I think I'm going to have to write a lambda-list parser which mimics the semantics of function lambda-lists. I was hoping the destructure function might be of help.

2020-10-13T13:01:39.284700Z

canโ€™t say much why there is no documentation for destructure in clojure.core this is more like internal helper that accidentally become public (this is my opinion, donโ€™t have any proof that this is true)

Jim Newton 2020-10-13T13:02:01.284900Z

yes I think my mac spell corrected destructure -> destructuring in my OP

2020-10-13T13:02:13.285100Z

it is programmatic interface but in the form of helper for macro writers

Flo 2020-10-13T13:02:20.285400Z

https://clojure.org/guides/destructuring#_macros at the very end it's talking about the destructure fn: > However, in rare cases you might want to instead resolve the destructuring yourself in a macro. In this case, use the (undocumented) clojure.core/destructure function[...]

2020-10-13T13:03:06.285800Z

clojure.core/destructure is a function, not macro

Jim Newton 2020-10-13T13:04:22.286100Z

yes, right

Jim Newton 2020-10-13T13:05:37.286300Z

looks like destructure produces the first operand of let. But it also gives me a sequence whose even elements are the names of the variables which the destructing will bind. That could be very useful.

2020-10-13T13:08:51.286500Z

you can think about it as a function to return bindings (destructure '[[x y] z]) => [gen_0 z, x (nth gen_0 0 nil), y (nth gen_0 1 nil)] ^ this is simplified form

2020-10-13T13:09:24.286700Z

so in one sentence it transform destructure syntax into clojure syntax

1
j 2020-10-13T16:41:42.288700Z

Is there a idiomatic/preferred binary tree in Clojure?

ghadi 2020-10-13T16:53:39.290100Z

@watchtheblur sorted-map is a persistent red-black tree

j 2020-10-13T17:07:59.291900Z

Thanks @ghadi. I was looking for something that I could traverse/search children with. I realize I can implement a binary tree using a list and its indices, but I wondered if there was a better way (maybe objects)?

2020-10-13T17:23:03.293500Z

(use a vector and indices, lists are not for looking up by position)

๐Ÿ‘ 1
2020-10-13T17:34:22.294700Z

@watchtheblur on a sorted collection (eg sorted-map) the subseqfunction function can be used for efficient splitting, sadly it doesn't return a pair of new sorted collections though

j 2020-10-13T17:59:52.298900Z

@noisesmith seems like the censuses is to use a map instead of a vector for a binary tree? So if I wanted to grab the 2 children of a node, I would need to give indicies to the map to get the child, or is the map already structured as a tree (i.e. nested maps)?

2020-10-13T18:03:43.299900Z

there's also sorted-set, but neither directly exposes its tree structure https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentTreeSet.java

2020-10-13T18:05:29.300800Z

actually this might be what you want https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentTreeMap.java#L223

2020-10-13T18:06:38.301600Z

it requires interop, but at least it's public

alexmiller 2020-10-13T18:07:51.304Z

I can't imagine that's what OP actually wants, but that raises the question - what do you want? :) are you looking for a "by the book here's how to implement a binary tree" or are you looking to store some data in a tree for a reason, and if so there are probably better default answers

Mario C. 2020-10-13T18:14:04.309300Z

We have an app running in wildfly at work and it started to get unresponsive with users reporting that they are getting "BadGateway" issues. Looking at the logs I noticed quite a few errors. Main ones that stick out were "OutOfMemoryError: Metaspace", "NoClassDefFound", "I/O Exception Broken Pipe (Write failed)" and "Postgres connection attempt failed". Looking at NewRelic, I noticed that the UnloadedClass count started to increase along with a huge spike in GC (ConcurrentMarkSweep), where at the highest point, is where the application went down and users started reporting the errors. Operations restarted the servers and the app was working again. But this had happened last week as well. Currently I am researching but this is honestly a little over my head/experience level. I am thinking there is perhaps a memory leak going on but I am not so sure. Does anyone have any idea what could be causing this or could point me in the right direction?

Mario C. 2020-10-13T18:16:26.310100Z

Oh, another thing, which could be coincidence, but the servers both went down on Monday/afternoonish

2020-10-13T18:16:45.310500Z

sounds like you are calling eval a lot

Mario C. 2020-10-13T18:17:53.310900Z

Hmm, what makes you say that?

2020-10-13T18:18:06.311600Z

I don't have experience doing this myself, but there are several attach-them-to-the-running-JVM-process memory profiling tools that can aid in looking for what classes of objects are using the most memory.

Mario C. 2020-10-13T18:18:14.312Z

That could be it actually, since some new code that went out about two weeks, uses the eval function

2020-10-13T18:18:26.312200Z

eval compiles clojure code to jvm classes, classes are allocated in metaspace

2020-10-13T18:18:58.312700Z

the correct amount to be calling eval is usually 0

๐Ÿ’ฏ 1
2020-10-13T18:24:51.313800Z

If you really need to have some form of eval in your code, thereโ€™s always sci

Mario C. 2020-10-13T18:24:56.313900Z

Actually, the code does not use eval. I thought it did but it does not after second look

2020-10-13T18:25:10.314100Z

https://github.com/borkdude/sci

Mario C. 2020-10-13T18:25:54.314300Z

Yea I was trying to hook up VisualVM but was getting issues with it freezing

2020-10-13T18:31:22.314500Z

hprof is much lighter weight, and visualvm can load the dump file it creates

j 2020-10-13T18:34:11.315900Z

@alexmiller I'm looking to practice leetcode-type questions with clojure. Since leetcode doesn't support clojure, I wanted a simple binary tree data structure to run algorithms on. I wanted to check with the community on a idiomatic approach

seancorfield 2020-10-13T18:34:28.316400Z

@mario.cordova.862 are you trying to limit the metaspace? That seems to be a common mistake for folks coming from earlier JDKs where limiting the non-heap part of memory sometimes made sense (it's been a while so I've forgotten exactly what setting I'm thinking of on earlier JDKs)

seancorfield 2020-10-13T18:35:05.316600Z

(PermGen!)

seancorfield 2020-10-13T18:35:35.317200Z

(it used to be a good idea to limit PermGen -- it is not a good idea to limit Metaspace, as I recall...)

2020-10-13T18:36:43.318200Z

yeah, in my experience setting limits on metaspace is cargo-cult and/or mindless copying of old config from ancient jvms

2020-10-13T18:37:01.318700Z

it's not a useful setting

Mario C. 2020-10-13T18:37:17.318900Z

I am just trying to figure out why this issue occurred. If its a problem with new code changes or something else.

2020-10-13T18:37:44.319400Z

something is allocating memory off heap, that's done by some libraries, it's done when you create new classes

alexmiller 2020-10-13T18:38:16.320Z

in general, the best way to work with Clojure is to lean on the built-in data structures, so I'd probably make each node a map with slots for :left and :right (or whatever). Or a vector of 2 items. not sure which way works out better.

2020-10-13T18:39:29.321100Z

the biggest culprits I know of are using eval to make new functions at runtime, and badly written interop with platform level stuff (eg. a memory leak in initialization of some adaptor to C code)

2020-10-13T18:40:59.322100Z

I guess "at runtime" is when all functions are created, but I mean in a deployed app after startup

Mario C. 2020-10-13T18:43:16.322900Z

I think it might be the eval but it's odd that it just started to happen. Going to check if there is a limit on metaspace

2020-10-13T18:43:59.323600Z

IIRC that's the only way to get that specific error

Mario C. 2020-10-13T18:44:51.323900Z

The metaspace one?

2020-10-13T18:45:18.324400Z

right, without the metaspace limit you get a different flavor of "vm out of memory"

2020-10-13T18:46:13.325400Z

and you'd need to create a lot of functions before that came up (I guess that's one benefit of a metaspace limit, catching metaspace leaks early...)

2020-10-13T18:47:11.326200Z

and now I wonder if there's any such thing as garbage collection of Classes (probably not...)

2020-10-13T18:52:41.327Z

there is, but it is relatively new (I forget how new, or maybe if it is just a jep that hasn't landed yet)

2020-10-13T18:53:24.327300Z

I guess it isn't that new, java 8 or so

2020-10-13T18:53:57.327800Z

so something must be not only creating new functions via eval but also holding onto them I guess

Mario C. 2020-10-13T18:54:36.328300Z

I am seeing this in the standalone.conf -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2G

Mario C. 2020-10-13T18:55:00.328900Z

This is setting a maxsize on metaspace, which should not have a limit?

2020-10-13T18:55:38.329600Z

I've never seen a drawback to just leaving it unlimited, but you probably also want to find out what's filling your metaspace and not getting cleared up

2020-10-13T18:56:55.330Z

visualvm / yourkit / hprof can show you what's filling metaspace

Mario C. 2020-10-13T19:00:03.330700Z

Going to investigate it then, thanks for the help guys! :thumbsup:

2020-10-13T19:00:51.331400Z

luckily anonymous functions / partial / reify / proxy etc. create class names that will lead you to the code creating them

2020-10-13T19:06:48.332300Z

Partial's is just something like clojure.core$partial$fn

Mario C. 2020-10-13T19:08:11.333300Z

So eval is okay to use when "defining" something but during run-time it is an issue?

2020-10-13T19:11:04.336900Z

It is important to understand, and an important design feature, that clojure only creates classes during compilation, running code doesn't create new classes unless you call the compiler (calling eval)

j 2020-10-13T19:11:13.337Z

Got it, thanks @alexmiller!

2020-10-13T19:13:32.340600Z

So using any of the features @noisesmith mentioned will only generate a single class (and partial doesn't even do that, because partial doesn't really belong in that set) at compile time, and at runtime you will just get multiple instances of that class

2020-10-13T19:14:26.341600Z

The only way to generate an unbounded number of classes is by calling eval over and over

2020-10-13T19:15:11.342200Z

right, I shouldn't have included partial, but was thinking that instead of creating fns via eval, they might be creating reify or proxy

2020-10-13T19:15:38.343Z

Reify and proxy will only generate new classes when compiled

2020-10-13T19:15:45.343400Z

right, inside eval

2020-10-13T19:15:53.343700Z

it would be weird but it's possible

Mario C. 2020-10-13T19:19:57.348400Z

So if I do (eval '(+ 1 1)) this creates a class?

2020-10-13T19:20:25.348800Z

I would not try to define cases when eval is ok to use and when it isn't. I would say because clojure provides it neatly wrapped in a single function you don't realize the complexity of it, and whatever you are doing almost certainly doesn't require it

2020-10-13T19:20:57.349500Z

To execute byte code on the jvm it has to live in a class file

2020-10-13T19:21:56.350200Z

Live in a class, the class may just exist in memory

2020-10-13T19:23:52.353Z

Eval has some special cases for very trivial expressions where it just acts as an interpreter and doesn't generate byte code, but in general for anything but the most trivial expressions, you will generate at least one class when calling it

Mario C. 2020-10-13T19:25:31.354600Z

Im not so much trying to define cases when is it okay to use but I am trying to reason about some code. I am trying to verify that a certain portion of the code is the culprit. Since this part of the code will be calling eval during runtime where as the other portion is using eval but not during runtime

2020-10-13T19:26:11.355400Z

Remove the calls to eval

2020-10-13T19:30:03.358Z

When there is a foot gun in your code you remove it or debate where it is pointing, and for #beginners the response has to be removing it. Debating about where it is pointed belongs somewhere else

๐Ÿ‘ 2
alexmiller 2020-10-13T19:30:43.358300Z

sounds like https://clojure.atlassian.net/browse/CLJ-1152

๐Ÿ’ฏ 1
Mario C. 2020-10-13T19:37:11.359400Z

I dont feel comfortable just yet removing it, since it is legacy code and its essentially the heart of the application so a little risky

2020-10-13T19:41:09.359500Z

Hah, I know that guy

2020-10-13T19:48:32.359800Z

If the application is written such that it can be shut down in an orderly fashion, and/or has multiple instances running in parallel that can take over for each other dynamically, e.g. like many highly available web services, you could potentially consider the workaround of monitoring the PermGen space usage, and when it reaches a threshold, force that instance to restart. That would be a workaround, mind you, with its own introduced operational difficulties.

Sergio 2020-10-13T23:11:30.367400Z

hello, I'm writing a little tool to automate some of my daily task on Clojure and Conch (https://github.com/Raynes/conch), however I have a function that I can't make pass a test. Here the code for both:

(defn exec-cmd [expression]
  (let [bin (:bin expression)
        args (:args expression)
        config (:config expression)]
    (sh/with-programs [echo mvn docker-compose]
                      (case bin
                        "echo" (echo args config)
                        "mvn" (mvn args config)
                        "docker-compose" (docker-compose args config)))))
(deftest test-echo-cmd-exec
  (testing "Execution of echo command OK"
    (is (= (exec-cmd {:bin "echo", :args '("hello" "world!"), :config {:seq true}})
           '("hello world!")))))
Any idea how can I make the test pass?, I can make the function to use echo by using apply but I need to pass extra configuration for the programs binded by Conch (that's why in the test case I'm passing a {:seq true})

Sergio 2020-10-13T23:12:07.367800Z

by the way, the test fails with:

FAIL in (test-echo-cmd-exec) (core_test.clj:7)
Execution of echo command OK
expected: (= (exec-cmd {:bin "echo", :args (quote ("hello" "world!")), :config {:seq true}}) (quote ("hello world!")))
  actual: (not (= ("") ("hello world!")))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.

2020-10-13T23:49:21.369100Z

@sdmoralesma ymmv but I find conch to be over engineered and I prefer to use ProcessBuilder / Process directly:

user=> (let [b (ProcessBuilder. ["echo" "hello"])
             p (.start b)
             res (.getInputStream p)]
         (.waitFor p)
         (slurp res))
"hello\n"
*reformatted to be more readalbe

Sergio 2020-10-13T23:53:01.369600Z

thank you for the answer, I'll give it a try, also I noticed that is possible to pass a folder for execution which helps in my case. ๐Ÿ‘

2020-10-13T23:53:33.369800Z

yeah, the API is pretty nice actually, especially if you already know the posix exec api

2020-10-13T23:53:50.370Z

except for the part where it doesn't let you know the PID :/

2020-10-13T23:54:48.370200Z

using the inputStream and outputStream together you can even run an interactive program, which is much harder to do in most languages

Sergio 2020-10-13T23:57:20.370400Z

with the PID at least is not an issue for me... yet. About the streams, that might improve the workflow... after all the idea is to simplify my day, thanks for the hint

alexmiller 2020-10-13T23:59:58.370600Z

In Java 9 they added a .pid() method

๐Ÿ‘ 1
โค๏ธ 1