Fork me on GitHub
#beginners
<
2021-02-25
>
West02:02:24

So I’m looking for a GUI framework to use on desktop, something with minimal boilerplate. I think I can store my state in flat edn files.

az02:02:08

@U01KQ9EGU79 - This one looks promising

West02:02:54

I think a reagent based tookit will be good as I’m quite comfortable with hiccup.

West02:02:14

Was also checking out electron.

Thomas Tay02:02:45

I'm confused - if you're looking for desktop gui, why not use Swing

phronmophobic02:02:52

Some of the options on desktop are: • https://github.com/JetBrains/skija/ (no event handling afaik) • https://github.com/cljfx/cljfx clojure library on top of JavaFX • https://github.com/clj-commons/seesaw clojure library on top of swing • any cljs lib + electron • https://github.com/phronmophobic/membrane Obviously, I'm biased towards the last one (membrane)

👍 3
West02:02:27

Ok, maybe I don’t care about boilerplate too much. I could go with electron because it’s what I know and I have access to node for more flexibility.

👍 3
phronmophobic02:02:20

I've used electron before and it works well

West03:02:34

Anyone try using proton native with cljs?

West04:02:24

Alright, so I got Electron working. I have no idea how to make tabs in clojurescript or reagent. What are some general concepts I should think about when making tabs?

West11:02:43

Nevermind. Got tabs working just fine.

West11:02:55

On the topic of databases and persistent storage. What's the preferred way to store data? I was planning on straight up using edn files, but given that my app is going to be electron and cljs based, and that I also don't want to use any SQL, which solution do you guys recommend? Another possibility is I could stick with flat edn and somehow use clojure as my backend instead of nodejs.

borkdude11:02:49

I usually end up with a DB anyway even if I start with flat files. You can try datalevin if you want to use datalog. Else an embedded db like sqlite or hsqldb might also work well.

West21:03:36

How exactly does something like Datalevin work? Does it create a file? If so, what file? What format? Is there a default location?

borkdude21:03:44

I think you should compare it to sqlite: do you care in which format sqlite stores its db?

West21:03:32

I more so want to be able to find it. Like I care if it’s a dotfile or stored in /tmp/ or something like that.

borkdude21:03:24

I just took a look at the datalevin binary.

borkdude21:03:33

$ dtlv --help

  Datalevin (version: 0.4.9)

Usage: dtlv [options] [command] [arguments]

Commands:
  exec  Execute database transactions or queries
  copy  Copy a database, regardless of whether it is now in use
  drop  Drop or clear a database
  dump  Dump the content of a database to standard output
  load  Load data from standard input into a database
  stat  Display statistics of database

Options:
  -a, --all        Include all of the sub-databases
  -c, --compact    Compact while copying.
  -d, --dir PATH   Path to the database directory
  -D, --delete     Delete the sub-database, not just empty it
  -f, --file PATH  Path to the specified file
  -g, --datalog    Dump/load as a Datalog database
  -h, --help       Show usage
  -l, --list       List the names of sub-databases instead of the content
  -V, --version    Show Datalevin version and exit

Omit the command to enter the interactive shell.
See 'dtlv help <command>' to read about a specific command.

borkdude21:03:19

In the code you will refer to the db file as (def conn (d/get-conn "/var/datalevin/mydb" schema))

borkdude21:03:25

so it's pretty clear where it stores the data

West21:03:00

Ah I see. Thank you for your help. Imma try understanding and implementing it now.

Old account10:02:11

how would you merge list of maps into a map? :thinking_face:

Umar Daraz10:02:07

(apply merge [{:a 1} {:b 2}])  => {:a 1 :b 2}

💪 3
Umar Daraz10:02:38

@audrius are you looking for something like this

audrius11:02:21

You could replay in a thread. That would remove a need to tag specific person and prevent from common mistake - tagging wrong person 😉

😄 3
🍻 3
Umar Daraz11:02:49

Sorry audrius, keep that in mind 💡

adam-james12:02:50

Hi everyone. I'm working on an SVG library and am wondering about idiomatic use of spec. I found myself creating a few predicate functions from my specs using s/valid? but wonder if this is actually a good way of using spec?

(defn pt2d? [a] (s/valid? ::pt2d a)) 
(defn pts? [s] (s/valid? ::pts s)) 
(defn element? 
   "Checks if `elem` is an SVG element." 
   [elem] 
   (s/valid? ::svg-element elem))
I use these functions (and specs) for :pre validation, but also inside conditionals from time to time.
(defn translate
  {:pre [(s/valid?
          (s/or :one (s/coll-of :svg-clj.specs/svg-element)
                :many (s/cat :elems (s/coll-of :svg-clj.specs/svg-element))) elems)
         (s/valid? :svg-clj.specs/pt2d [x y])]}
  (let [elem (first elems)
        elems (rest elems)]
    (when elem
      (cond
        (and (specs/element? elem) (= 0 (count elems)))
        (translate-element [x y] elem)
        (and (specs/element? elem) (< 0 (count elems)))
        (concat
         [(translate-element [x y] elem)]
         [(translate [x y] elems)])
        :else
        (recur [x y] (concat elem elems))))))
I have a hard dependency on spec in my library, and I don't know if that's a good idea, or if it's advisable to re-work things and make it unnecessary. I have an open issue on the project related to this question. https://github.com/adam-james-v/svg-clj/issues/2 Any advice is greatly appreciated :)

borkdude12:02:28

I don't see a problem with your usage of spec right now. Except that you maybe might want to add some explain output if things are not valid

👍 3
adam-james12:02:56

Ya, I noticed while I was using it that I still asked "but why did that fail?" s/explain should help with that at least.

borkdude12:02:04

@U01LR7M2B7A It might also be interesting to look at malli, which is a more data driven approach to data validation. I am considering adding that to babashka if people are in favor of this. It's not an either/or between spec, but spec is still in development, whereas malli is already out of the alpha stage.

adam-james12:02:47

Hmmm... I'll give that a look/experiment a bit. The original thinking for spec was to 'use the official tools'. It's also why I stick primarily with deps.edn vs lein. But it's not a strongly held opinion of mine, so I'm willing to change. Thanks for the tip!

borkdude12:02:22

The official things aren't always better than community things, but it is a benefit that something is built-in.

robertfw18:02:26

My thought when seeing spec wrapped up in :pre/:post is that you may want to look at using s/fdef in concert with either clojure.spec.test.alpha/instrument (which will verify that that function arguments match your spec) or the orchestra library which does similar but also checks that function outputs are what you specify

robertfw18:02:59

You may want to drop into #clojure-spec if you want to discuss in more detail

adam-james00:02:38

I was looking at s/fdef a bit, and as you say using instrument will essentially eliminate any need for :pre conditions. Orchestra I haven't heard of and that looks nice for the function output validation too, which would certainly help in my case. Thanks for the feedback

Jim Newton12:02:03

What is the correct way to control the clojure random number generator. I see the functions https://clojuredocs.org/clojure.core/rand-int and https://clojuredocs.org/clojure.core/rand ? But I don't see the discussion of how to get and set the random seed, how to reset the generator to a previous state (e.g., for reproducing errors in functions which use random numbers), and how to assure (or whether it is assured) that new processes get their seed set the same way.

borkdude12:02:31

@jimka.issy It's probably better to use a library like clojure.data.generators or clojure.test.check for this

borkdude12:02:46

or raw Java interop

andy.fingerhut13:02:19

In particular, class java.util.Random provides an explicit method to set the seed, and a variety of methods to get different distributions/types of randomly generated values from such a generator.

borkdude13:02:15

There is also SecureRandom

Jim Newton13:02:17

Sorry, to help me understand. are you suggesting I should avoid rand, rand-int and rand-nth, and in stead use the java Random interface? Or are you suggesting that rand, rand-int, and rand-nth promise to use the java Random interface, so I can mix and match them freely?

borkdude13:02:53

the first, if you need seed

3
andy.fingerhut13:02:35

I believe that the JVM methods used by the Clojure function rand has no way of setting a seed explicitly.

andy.fingerhut13:02:50

That is the reason for suggesting the former, if you want to explicitly assign seed values.

3
Noah Bogart16:02:43

sometimes i run a lein run command and after completion, the process doesn't exit, so I have to CTRL-C to exit

Noah Bogart16:02:52

anyone know why this happens or how I can prevent it?

blak3mill3r16:02:54

if the code ever uses pmap for example, there are going to be non-daemon threads running (which prevents the JVM from shutting down)

Noah Bogart16:02:36

Thanks! I'm not using pmap anywhere, but I do call require instead of using :require in the ns macro. i'll poke around to see what i'm missing

Noah Bogart17:02:44

turns out I had a call in another file that was doing somthing like that. thanks for the heads-up to both of you!

🎉 3
ivar17:02:35

q: does anyone have a recommendation for clojure friendly jvm instrumentation ? Something like newrelic or dynatrace

blak3mill3r18:02:06

Instrumentation for what purpose? Profiling? Perhaps this can be useful: https://github.com/jgpc42/jmh-clojure (edited)

ivar18:02:55

hmm. I'm looking to get metrics on our app's performance and to capture errors

pyry18:02:55

There are obviously a number of choices; Elastic APM has worked fine for me. Quite easy to write your own simple wrapper around the Java agent API. Or I guess you can also take a wrapper such as https://github.com/Yleisradio/clojure-elastic-apm, provided to you by the Finnish national public broadcasting company.

👍 3
seancorfield18:02:29

@U051Q4BJY We'll been using New Relic in production for years with Clojure and we've been very happy with it. A couple of caveats: if you're using a "non-standard web server", such as http-kit, New Relic doesn't support that very well (we switched back to Jetty to address this); adding Trace metadata is a bit of a pain -- I blogged about that some years ago on https://corfield.org/blog/2013/05/01/instrumenting-clojure-for-new-relic-monitoring/ (and see also https://corfield.org/blog/2016/07/29/clojure-new-relic-slow-startup/ which may have been addressed in a later New Relic release but I think we still have that config in place).

👍 3
ivar18:02:54

ok, I'm familiar with newrelic from my days as a rails dev, so I think I'll start exploring that now that I know it's viable.. Thank you everyone for your feedback - very much appreciated!

seancorfield18:02:14

Feel free to DM me with Qs if you run into issues with New Relic and Clojure @U051Q4BJY

🙏 3
ivar18:02:09

thanks - the project I'm instrumenting uses aleph, so I have a feeling it fits in the 'non-standard' category

seancorfield18:02:07

Definitely. I was able to get some way with http-kit by explicitly telling New Relic to instrument certain classes/methods via configuration. Not sure if I still have that on hand but I can search our repo history for it if needed.

robertfw18:02:27

I just tried out the clojure-metrics library the other day for the first time, it may have some useful tools for you. https://github.com/metrics-clojure/metrics-clojure/

👀 3
seancorfield19:02:09

We just recently migrated from New Relic's old plugin architecture (and their ancient metrics Java lib) to the new Telemetry SDK stuff which provides access to their Metrics API as well as Logging API and a couple of other APIs. Apart from the resolutely Java-focused API design, it was pretty easy to get it all integrated and working. We publish a lot of custom application metrics. (I ranted about this SDK on Twitter recently because of the custom types it uses to try to encapsulate what could just be a regular hash map!)

ivar19:02:06

looks like I have the go-ahead to change our webserver from aleph to jetty, so that's step one. I'm hoping it's as straightforward as it seems 🤞

Eric Ihli18:02:43

Thoughts on this idea? I'm trying to turn a Trie data structure that is represented in Clojure as a HashMap (`{"D" {"O" {"G" {:val "DOG"} "T" {:val "DOT"}} :val "DO"}}`) into a "https://www.aclweb.org/anthology/W09-1505.pdf" which is just a raw array of bytes in a particular order. To work with that trie-as-a-byte-array data structure, I need some help navigating around the byte array. I was thinking some type of "cursor". It has a reference to the byte array, but all the iteration protocols are implemented by adjusting the location of the cursor. Something like this...

(defprotocol IByteArrayCursor
  (loc [_])
  (jump [_ loc] "Moves location of cursor to specified index.")
  (forward [_] [_ n])
  (backward [_] [_ n])
  (slice [_ end])
  (ba= [_ other-ba]))

(deftype ByteArrayCursor [ba loc]
  clojure.lang.Indexed
  (nth [_ i]
    (if (and (>= i 0)
             (< i (count ba)))
      (ByteArrayCursor. ba i)
      (throw (ex-info "Index out of bounds."))))
  (nth [self i not-found]
    (if (and (>= i 0)
             (< i (count ba)))
      (ByteArrayCursor. ba i)
      not-found))

  IByteArrayCursor
  (loc [_] loc)
  (jump [_ loc] (ByteArrayCursor. ba loc))
  (forward [_ n] (ByteArrayCursor. ba (+ loc n)))
  (backward [_ n] (ByteArrayCursor. ba (- loc n)))
  (slice [_ n]
    (loop [i 0 r []]
      (if (or (= i n)
              (>= (+ loc i) (count ba)))
        (ByteArrayCursor. (byte-array r) 0)
        (recur (inc i) (conj r (aget ba (+ i loc)))))))
Is this reasonable? Am I overlooking a simpler alternative? Am I not seeing any particular "gotchas"? Thanks for any thoughts anyone has.

dpsutton18:02:38

i think you're making a zipper. i'd check out that

andy.fingerhut18:02:17

It seems odd that a 'cursor' implements a slice operation like that. If the cursor is really just an index into a byte array ...

Eric Ihli18:02:09

That's a good point. I might not need it. I'm basically doing a lot of packing and unpacking bytes. So I'll read one byte that says "The next 20 bytes make up that value you want to decode.". Then I'll take a 20 byte slice and send it to the decode function. That decode function also makes use of the cursor functionality. But it eventually reads all the way to the end of whatever its given. I guess another type of interface that would give me the functionality of being able to read from the start to the end of some bytes in the middle of a byte array would be a "view" rather than a "slice".

hiredman18:02:11

are you aware of java.nio.ByteBuffer?

hiredman18:02:33

(it is super mutable, which is meh)

hiredman18:02:07

but it basically does all those things

Eric Ihli18:02:06

Oh interesting. That looks like a great idea. I think a wrapper around ByteBuffer will be nice.

Scott Starkey19:02:59

Hi there, folks. I’m trying to sort a map with an initial decreasing numeric sort and a tie-breaker alphabetical sort.

[{:name "Andy", :score 22} {:name "Charlie", :score 25} {:name "Bruce", :score 25} {:name "Xerxes, :score 7}]
This should sort to be:
[{:name "Bruce", :score 25} {:name "Charlie", :score 25} {:name "Andy", :score 22} {:name "Xerxes, :score 7}]
The tie of “:score 25” is resolved alphabetically by the :name field. I’ve looked at sort-by and it shows examples of tie-breaker sorts. I see examples of https://clojuredocs.org/clojure.core/sort-by#example-542692cbc026201cdc326c2c with juxt, and examples of overriding with https://clojuredocs.org/clojure.core/sort-by#example-57ae5e86e4b0bafd3e2a04f1 with >. However, I’m having problems finding examples that combine the two. Can someone help? Thanks in advance!

seancorfield19:02:34

@scotto (juxt (comp - :score) :name) is probably good enough for your needs here?

seancorfield19:02:27

Although your data seems to have a string for the :score for "Andy" -- is that true? (that scores can be strings or numbers)

seancorfield19:02:11

If you have to deal with potential strings sorting as the number they represent, you'll need a helper function in there, something like (fn [s] (if (string? s) (Long/parseLong s) s)) -- which can go in the comp between - and :score

Scott Starkey19:02:12

Oops, I did not mean a string for Andy’s score. 🙂 Thanks for the catch

hiredman19:02:41

speaking of Andy https://clojure.org/guides/comparators is a guide for writing and using comparators in clojure, which is another way to do this

👍 3
Scott Starkey19:02:18

I’ll check it out.

Scott Starkey19:02:49

Wow @hiredman - That page is excellent and thorough! Thanks!

Scott Starkey20:02:23

Also, I had to scratch my head a while about the minus sign in @seancorfield’s example above. I think the minus sign is a negative-making function, so sorting by the scores negatively sorts them descending. That’s a nice little trick! Thanks!

seancorfield20:02:48

(comp - :score) = (fn [data] (- (:score data))) or (fn [data] (- 0 (:score data)))

seancorfield20:02:10

There's also clojure.core/unchecked-negate

dev master22:02:14

is it possible to load a clojure script file from java, and then execute a function from that clojure file?

noisesmith22:02:13

one call to require the ns, another to lookup the function, then you can invoke it

West04:02:24

Alright, so I got Electron working. I have no idea how to make tabs in clojurescript or reagent. What are some general concepts I should think about when making tabs?

West11:02:55

On the topic of databases and persistent storage. What's the preferred way to store data? I was planning on straight up using edn files, but given that my app is going to be electron and cljs based, and that I also don't want to use any SQL, which solution do you guys recommend? Another possibility is I could stick with flat edn and somehow use clojure as my backend instead of nodejs.