beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
2021-01-15T00:01:21.083Z

ah, i think its :integratedSecurity

seancorfield 2021-01-15T00:47:46.084Z

@qmstuart You can pass any properties that the JDBC driver understands -- so you won't find them in the docs specifically, but there is an explanation of passing additional properties to the driver as part of the db-spec hash map...

Claudio Ferreira 2021-01-15T01:02:51.085300Z

Clojurians, what are we really doing here when we " { :keys [ minimum maximum ] } " as a parameter inside that function? I became lost here

2021-01-15T01:05:33.087100Z

That is destructuring a map into bindings by specifying the names of the keys in the map. So you function expects a parameter that is a map that contains the keys minimum and maximum After the destructuring you can just use the names minimum and maximum rather than having to do something like (:minimum your-map) e.g. Say you have a map

{:foo 5 :bar 6}
And a function
(defn foo [{:keys [foo bar]}]
   (prn foo bar))
ANd calling it with our map
(foo {:foo 5 :bar 6})
5 6 
=> nil
If foo didn't destructure it's map argument, I would have to do something like this
(defn foo [my-map]
   (prn (my-map :foo) (my-map :bar)))
See here, https://clojure.org/guides/destructuring, you want the section on associative destructuring.

Claudio Ferreira 2021-01-15T15:35:08.165300Z

Thanks all. Finally i've understood that!

Claudio Ferreira 2021-01-15T01:49:10.088100Z

HMMM, thank you @qmstuart! And when we use ":as" after it? Are we binding the keys to next element? e. g. Acc in the next image?

2021-01-15T02:04:02.088600Z

:as allows you to also bind the entire map as well, in addition to the key names specified

2021-01-15T02:06:45.088800Z

That is so you can still reference the entire map. So in that method your map argument is called acc, and its keys are current-bag and bags. The method also has a second parameter called stream. Im assuming this is a method to pass to reduce.

seancorfield 2021-01-15T02:46:10.089Z

Also worth noting: if you have :or to set a default value, it binds the symbol to that value but the :as symbol is bound to the original data.

seancorfield 2021-01-15T02:47:20.089200Z

(let [{:keys [a b] :as data :or {a 1 b 2}} {:a 42 :c "Sea"}] (prn a b data))
42 2 {:a 42, :c "Sea"}

seancorfield 2021-01-15T02:47:44.089400Z

So b is bound to 2 -- the default -- but data still doesn't contain b.

zackteo 2021-01-15T06:23:37.091300Z

Quick (?) question: what idioms should I be using instead if I'm planning to use map inside another map function

seancorfield 2021-01-15T06:26:52.092Z

@zackteo For some problems, that might well be the right solution. Can you provide some context?

zackteo 2021-01-15T06:27:33.092400Z

Give me a moment 🙂

zackteo 2021-01-15T06:35:06.095200Z

So I current have a list of sheets like ( "Sheet1" "Sheet2" ) and a list of lists of java objects (( org.apache.poi.ss.util.CellRangeAddress [A1:C1] org.apache.poi.ss.util.CellRangeAddress [A2:B3] org.apache.poi.ss.util.CellRangeAddress [C2:C3] )( org.apache.poi.ss.util.CellRangeAddress [A1:C1] )) and I want to transform it into a format like so ...

{[0 0 "Sheet1"] {:lrow 0, :lcol 2},
 [1 0 "Sheet1"] {:lrow 2, :lcol 1},
 [1 2 "Sheet1"] {:lrow 2, :lcol 2},
 [0 0 "Sheet2"] {:lrow 2, :lcol 2}}
where I can grab the numbers using .getFirstRow .getFirstColumn .getLastRow .getLastColumn on the java objects.

zackteo 2021-01-15T06:36:32.096500Z

And currently I have

#(hash-map (vector (.getFirstRow %)
                   (.getFirstColumn %))
           (hash-map
                   :lrow (.getLastRow %)
                   :lcol (.getLastColumn %)))
which works on a list of java objects (not a list of list) to produce
{[0 0] {:lrow 0, :lcol 2},
 [1 0] {:lrow 2, :lcol 1},
 [1 2] {:lrow 2, :lcol 2}}

zackteo 2021-01-15T06:37:03.097200Z

Sorry about the mess. I'm trying to figure out how to best approach the problem

seancorfield 2021-01-15T06:37:22.097700Z

Can you explain what the actual problem is that you're trying to solve?

zackteo 2021-01-15T06:44:15.101800Z

Am trying to add merged cells into https://github.com/zero-one-group/fxl/blob/develop/src/zero_one/fxl/read_xlsx.clj#L107 by adding :lrow and :lcol into :coord when it is a merged-cell . Which should seem easy to do. But the only/best access point to find out about merged-cells seems to be using getMergedRegions on the sheet https://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/Sheet.html#getMergedRegions--

seancorfield 2021-01-15T06:47:27.105Z

I think you're struggling because you can't explain the problem very well...

zackteo 2021-01-15T06:47:36.105300Z

So I was thinking the way to approach it is by having a list of merged-regions in the let block of throwable-read-xlsx! https://github.com/zero-one-group/fxl/blob/develop/src/zero_one/fxl/read_xlsx.clj#L116 which I then can feed into poi-cell->fxl-cell to check if the current cell (it is mapping through) is a merged cell and adding the right keys accordingly

seancorfield 2021-01-15T06:47:44.105900Z

You're too deep in the weeds of the implementation.

seancorfield 2021-01-15T06:48:04.106400Z

Take a few steps back, away from the code, and think about the data at a higher level.

zackteo 2021-01-15T06:49:36.106600Z

hmmmm

zackteo 2021-01-15T06:57:03.110Z

I guess I this might still be implementation level but broadly my problem seems to be a 2 step one 1. Get a hash-map which I can use to identify which cells are merged-cells. (which also contains the extra parameters I want, to make a cell a merged-cell) 2. Update the list of cells based on that hash-map

seancorfield 2021-01-15T06:58:11.110900Z

That's still implementation. You haven't actually described the problem yet.

dpsutton 2021-01-15T06:58:42.111300Z

"i have a workbook with two spreadsheets in them. i would like to ..."

seancorfield 2021-01-15T07:00:25.113400Z

Right now I have no idea what you mean by "merged-cells" and what "extra parameters" means, even in that context.

zackteo 2021-01-15T07:03:54.116Z

Hmmmmm :thinking_face: I wondering if this is too high level. I am adding merged-cell reading capabilities to an excel read/write library (that wraps apache poi) Merged-cells are a combination of multiple cells but only display the value of the top leftmost one

zackteo 2021-01-15T07:06:13.118400Z

A merged cell is a range/region of cells. So a cell typically only has 1 pair of coordinates. I will need the other pair to give me the region

zackteo 2021-01-15T07:07:59.119700Z

Like cell A1 (:row 0 :col 0) to merged-cell A1:B2 (:row 0 :col 0 :lrow 1 :lcol 1)

zackteo 2021-01-15T07:16:15.121200Z

Maybe I was trying to jump steps by considering multiple sheets, without successfully handling a single sheet first

Yang Xu 2021-01-15T07:20:15.123300Z

Hi, Who knows the best way to invoke HoneySQL from java? or some suggestions?

2021-01-15T16:59:51.165600Z

you can use the clojure compiler to load and run clojure library code, it will handle all of those things via its own read and eval steps https://clojure.org/reference/java_interop#_calling_clojure_from_java

2021-01-15T17:01:29.165800Z

here's a small example I made, that loads up a namespace and runs it when a java daemon starts: https://github.com/noisesmith/clj-jsvc-adapter/blob/master/src/java/org/noisesmith/Cljsvc.java

2021-01-15T17:03:07.166100Z

clojure.java.api.Clojure handles all the setup, compilation etc. you just need to use the require method and the invoke method on any resulting functions you want to use

Yang Xu 2021-01-18T09:15:18.481100Z

Thank you.

seancorfield 2021-01-15T07:21:46.123700Z

The same way you invoke any Clojure from Java... https://clojure.org/reference/java_interop#_calling_clojure_from_java

dumrat 2021-01-15T07:29:20.131100Z

Hi guys, need some opinion if possible. Not 100% related to clojure. I want to do an excel-like grid at front end, but evaluate it (the formulas) at backend. I'm thinking of casting the formulas into variable assignments like so: `A.1 = 30 + 20` , concatenating them and sending them to the backend when the user clicks a button. So, the request would contain something like this (Assuming a 2x2 grid):

A.1 = nil;
A.2 = 10;
B.1 = A.2;
B.2 = B.1 + 50;
I can parse this using instaparse and have played with it a bit here (Incomplete so far and assignment is not fully handled yet) : https://github.com/nakiya/efev/blob/main/src/duminda/efev.clj I have two general questions: 1. Is this a reasonable way to handle this problem? 2. How should I handle Stuff like this: A.1 = A.2; A.2 = 40; Because at evaluation I need to evaluate the second statement first and then only the first. 3. How should I handle circular references? i.e. A.1 = A.2; A.2 = A.1; It is enough to identify and stop evaluation on encountering circular references.

aratare 2021-01-15T07:35:34.133200Z

@dumrat Can't you send a map of cells to their values instead? So something like {:A.1 nil, :A.2 10, :B.1 :A.2, :B.2 [:B.1 + 50]}?

dumrat 2021-01-15T07:39:03.135100Z

@rextruong Yes I can. Still I'd have to parse the input because the users expect excel-like formulas. But I don't think this would solve #2 and #3 issues if I understand correctly.

aratare 2021-01-15T07:39:23.135300Z

To handle (2) and (3), I suggest building a topology graph to identify the leaves (e.g. A2 = 10) vs the internal nodes (e.g. B1 = A2) so you can start evaluating them in the correct order.

aratare 2021-01-15T07:40:03.135500Z

You can also detect loop while building topology graph iirc.

dumrat 2021-01-15T07:41:54.136300Z

@rextruong Thanks. Do you have any suggested reading for building a topology graph? Not familiar with the concept.

aratare 2021-01-15T07:42:11.136500Z

This may help https://www.geeksforgeeks.org/topological-sorting/

👍 1
dumrat 2021-01-15T07:43:05.136900Z

Great, will check it out. Thanks!

Grigory Shepelev 2021-01-15T07:48:08.140500Z

Hello there. Could you please help with understanding of the async lib ans subscriptions? Suppose I want to send https req to website every second and subscribe to it's result. And do some action each time as I get response. Suppose it's just printing the response's body. So having the following code does not produce desirable effect:

(ns async.core
  (:require [clojure.core.async
             :as async
             :refer [chan <!! <! >! >!! timeout pub sub unsub unsub-all go go-loop]]
            [clj-http.client
             :as h]))

(def updates-channel (chan))

(def publishing
  (pub updates-channel :status))

(defn req [url]
  (h/get url {:async? true}
         (fn [res] 
           (go (>! updates-channel res)))
         (fn [_] nil)))

(defn listen!
  [publisher topic]
  (let [l (chan 1)]
    (sub publisher topic l)
    (go-loop []
      (when-let [{:keys [body]} (<! l)]
        (println (str "got incoming message: " "\n" body))
        (recur)))))

(listen! publishing :200)

(go-loop [seconds 1]
  (req "<https://motherfuckingwebsite.com/>")
  (&lt;! (timeout 1000))
  (recur (inc seconds)))
As far as I undestood that's because of double go-loop.

Grigory Shepelev 2021-01-15T08:42:10.141300Z

I am an idiot. sorry. It should be:

(listen! publishing 200)

Mark 2021-01-15T11:18:49.146100Z

Hi folks, is there a fn to pop a key from a map, similar to dissoc but should also return the value of the key provided. e.g.

(pop-key {:a 1 :b 2 :c 3} :a)
;;=&gt;[{:b 2 :c 3} 1]  

👍 1
bronsa 2021-01-15T11:24:26.146300Z

nothing builtin

bronsa 2021-01-15T11:24:40.146600Z

(juxt dissoc get)

😁 1
🙌 1
Mark 2021-01-15T11:27:50.146900Z

thanks

agata_anastazja (she/her) 2021-01-15T11:58:23.151500Z

Hello, I am trying to find a way to write an acceptance test for my app. I want to verify it prints to the terminal the correct strings, but the last thing that the app needs to do is to exit. So I tried capturing it with with-out-str and leaving the program using System/exit 0, but it seems the program shuts down before any output is produced or captured. I reproduced a simpler version in repl, like so:

(defn print-many []
  (prn "here")
  (prn "there"))
=&gt; #'user/print-many

(let [output (with-out-str (do (print-many)
                               (System/exit 0)))]
  (= output "here"))


Process finished with exit code 0
Any idea how I can test the correct output?

simongray 2021-01-15T12:30:36.152200Z

I think you are capturing the output of (System/exit 0) since that is the last form of your do.

agata_anastazja (she/her) 2021-01-15T13:05:07.153400Z

that sounds correct

agata_anastazja (she/her) 2021-01-15T13:48:33.154200Z

but does it mean that if I want to exit the program there is no way for me to capture what it prints?

agata_anastazja (she/her) 2021-01-18T09:36:49.482300Z

thank you!

Eamonn Sullivan 2021-01-15T13:53:24.154400Z

The way I've handled that in the past is to split out the code that puts the string together into a separate function and unittest that function. So that would mean changing print-many to return a string and not call prn directly. Just a thought. I try to keep (defn -main ...) very tiny, for just this reason.

💜 1
bronsa 2021-01-15T13:54:43.154800Z

well, for one you're not flushing the output stream actually nevermind, prn does autoflush

bronsa 2021-01-15T13:55:03.155Z

invoke (flush) before exiting

bronsa 2021-01-15T13:55:26.155400Z

and invoke System/exit after the =

bronsa 2021-01-15T13:57:34.156200Z

you can't do any computation after the program has quit -- System/exit needs to be the last expression evaluated

👍 1
agata_anastazja (she/her) 2021-01-15T13:58:27.156900Z

I guess the problem is that I use System/exit to close the program

agata_anastazja (she/her) 2021-01-15T13:58:41.157500Z

and I want to run a test on a whole program

bronsa 2021-01-15T13:58:53.157800Z

your program code should not invoke System/exit anywhere but in the top level

👍 1
agata_anastazja (she/her) 2021-01-15T13:59:19.159Z

the above is just a simplified version of how my app behaves

bronsa 2021-01-15T13:59:20.159100Z

if you need to return from a place deep in your stack, consider using an exception

bronsa 2021-01-15T13:59:53.160300Z

e.g in your app instead of having (if my-condition (continue-running) (System/exit 0)) consider (if my-condition (continue-running) (throw ..))

agata_anastazja (she/her) 2021-01-15T13:59:55.160400Z

but the idea is that it prints something, and then when it’s done processing, quits

bronsa 2021-01-15T14:00:08.160800Z

then at the very top level you can catch the exception and invoke System/exit

bronsa 2021-01-15T14:00:32.161500Z

and in your tests you can capture the exception and return nil, so that you can capture the output

agata_anastazja (she/her) 2021-01-15T14:00:54.162Z

thank you!

alexmiller 2021-01-15T14:06:47.163200Z

in general, things like (System/exit) and (shutdown-agents) are world-ending kinds of calls and should be factored to the very outside of an application either by wrapping or via swappable callback so that you can test stuff without actually ending the world

👍 1
roelof 2021-01-15T17:06:32.167100Z

if I want to make this to a back-end of a website (https://gist.github.com/RoelofWobben/98066daa4042ad9e7e5db0b0956c00c3) am I right I only need ring and cheshire so no framework

2021-01-15T17:08:26.167900Z

the bare minimum with ring is a web server backend (which only shows up directly in one line of code), ring core, and a router

2021-01-15T17:08:59.168600Z

the web server does the actual work, ring has an adaptor to that which makes it more clojurey, and the router decides which function to call for each request

2021-01-15T17:09:26.168900Z

the default back end is jetty, the default router is compojure

2021-01-15T17:11:27.169900Z

@roelof the ring wiki has an even simpler example, just ring and backend and no router - it's a "misbehaving" server with no routes, it just uses the same function for every request ignoring the route https://github.com/ring-clojure/ring/wiki/Getting-Started

roelof 2021-01-15T17:12:45.170700Z

I know I read that page several times and did some experiments

roelof 2021-01-15T17:13:17.171500Z

that is why I think things like luminus is a overkill for this back-end

2021-01-15T17:15:36.172600Z

looking at your code again, it looks like you are only interested in doing one thing - parsing a request URL to calculate a result - the issue you'd come up with is your function will get called with a lot of paths that won't make sense to that code

2021-01-15T17:15:47.172900Z

eg. browsers will ask for favicon.ico

2021-01-15T17:16:31.173800Z

with a router, you can have separate 404 response (or even a meaningful one), otherwise your function gets complex as it tries to handle all these unexpected strings

2021-01-15T17:16:46.174200Z

(or just blatantly fails for all unexpected input)

roelof 2021-01-15T17:20:03.174400Z

oke

roelof 2021-01-15T17:20:24.174800Z

so maybe ring and computure ?

2021-01-15T17:21:01.175Z

with compojure, yeah

roelof 2021-01-15T17:21:24.175200Z

oke

roelof 2021-01-15T17:25:03.176Z

first study those two again and try to make it work

2021-01-15T17:26:52.177Z

@roelof this is a minimal compojure router, you can plug in any function that takes a ring request map and returns something ring knows how to use as a response https://github.com/weavejester/compojure-example/blob/master/src/compojure/example/routes.clj

roelof 2021-01-15T17:28:28.177800Z

oke, I have not decided if the front-end will be hiccup or maybe something like reframe

2021-01-15T17:34:26.179400Z

with reframe your back end router will be managing requests from the cljs code for data used in rendering the page, with hiccup your back end router ends up calling code that generates the page, the router will look similar to what's above in both cases

roelof 2021-01-15T17:34:41.179900Z

but I will like i said study ringa nd compujure

2021-01-15T17:34:45.180400Z

using hiccup is easier for a first draft

roelof 2021-01-15T17:35:03.181100Z

before Im going to make my own code work

2021-01-15T17:35:04.181200Z

or even skipping hiccup and just returning some html string

roelof 2021-01-15T17:35:32.181400Z

for example

roelof 2021-01-15T17:35:59.182Z

in the end I hope I can build a front-end like this : https://codepen.io/rwobben/full/xxVepxG

roelof 2021-01-15T17:36:08.182500Z

but then without the movement

roelof 2021-01-15T17:36:50.183400Z

See how long that will costs me . maybe weeks or maybe months but that does not matter

Harley Waagmeester 2021-01-15T18:00:10.185500Z

i can run lein uberjar just fine, but when do java -classpath ... target/snapshot.jar i get the error /tmp/form-init1109030110411510610.clj ( no such file or directory )

Harley Waagmeester 2021-01-15T18:00:20.185800Z

wassup wid dat ?

Harley Waagmeester 2021-01-15T18:01:31.186300Z

i use the correct classpath

2021-01-15T18:02:13.187Z

the uberjar task makes two jars

2021-01-15T18:02:20.187200Z

you are running the wrong one

Harley Waagmeester 2021-01-15T18:02:49.188200Z

java -jar uberjar works fine

2021-01-15T18:02:50.188300Z

(this is because it treats making a jar for your app as a subtask for making the fat jar, and doesn't delete that pre-req)

Harley Waagmeester 2021-01-15T18:03:03.188500Z

oh ?

Harley Waagmeester 2021-01-15T18:03:31.188900Z

ok, i need to do lein jar ?

2021-01-15T18:03:45.189300Z

no, lein uberjar creates two jars, one of which is created by using lein jar

2021-01-15T18:04:03.189800Z

I'm just saying that when you get an error like that, the usual cause is running the regular jar instead of the uberjar

2021-01-15T18:04:32.190200Z

"correct classpath" with an uberjar is just one thing- the jar itself

2021-01-15T18:04:52.190600Z

it contains all other things that it needs

Harley Waagmeester 2021-01-15T18:06:07.190800Z

i want to use the jar because it is smaller

2021-01-15T18:06:51.191400Z

but it's not smaller than all the separate classpath items you need to gather to make it usable

Harley Waagmeester 2021-01-15T18:08:40.193200Z

the file is smaller, the system loaded into ram will be the same i imagine

2021-01-15T18:08:54.193600Z

the convention is to use uberjar for application deployment, and regular jars for libraries

2021-01-15T18:09:24.194800Z

otherwise you need to deploy and run a build tool as part of app deployment, which is not a good idea

dorab 2021-01-15T18:09:26.194900Z

The "skinny" jar is smaller because it only contains the files from your project. You want to use the "uber" jar that contains all the transitive dependencies as well.

Harley Waagmeester 2021-01-15T18:09:39.195Z

i usually use cider

2021-01-15T18:09:54.195600Z

cider is another thing you don't want to use when deploying an app

2021-01-15T18:10:11.196200Z

if you don't deploy, and everything is just cider usage with your editor, you don't need jar or uberjar at all

Harley Waagmeester 2021-01-15T18:10:16.196300Z

but still i wonder why a trasient file in /tmp is required

2021-01-15T18:10:42.197100Z

that's an implementation detail of lein, it creates a bootstrap to load up your project

Harley Waagmeester 2021-01-15T18:10:44.197300Z

yes, tmux, emacs, cider is my stack

Harley Waagmeester 2021-01-15T18:11:31.198900Z

ah well.. i was just wondering

dorab 2021-01-15T18:11:35.199100Z

For development, that is fine. But for deploying, you (likely) just want an uberjar.

2021-01-15T18:11:40.199500Z

OK - it would be foolish to deploy an app for others to use that is run that way, but if you aren't doing that you can ignore jar / uberjar entirely

Harley Waagmeester 2021-01-15T18:14:11.200500Z

this is code for my private web server, deployment from me to others would be source only

2021-01-15T18:15:29.202Z

OK - running cider and emacs as a container for your app in a production environment is considered a problem. It can work but you can save yourself a lot of problems by just not doing that.

Harley Waagmeester 2021-01-15T18:15:40.202300Z

the uber is 27M, that is a lot of man hours of coding

Harley Waagmeester 2021-01-15T18:16:20.203300Z

lately i've been using the uberjar more often

Harley Waagmeester 2021-01-15T18:30:02.204Z

not my coding, other persons, it's really amazing

Harley Waagmeester 2021-01-15T18:34:38.205Z

sure, and now i'm looking at GraalVM, more candy to tempt me

Harley Waagmeester 2021-01-15T18:37:33.206500Z

is it ok to run "lein snapshot.uberjar &", backgrounded is what i call it

roelof 2021-01-15T18:38:36.207400Z

one last question For a new project can I the best use leiningen or switch to the new kid clojure clj ?

dorab 2021-01-15T18:41:11.209500Z

For a new project I'd use clojure CLI over lein. But be aware that you may need other tools (or aliases) to build and run. See https://github.com/clojure/tools.deps.alpha/wiki/Tools

dorab 2021-01-15T18:43:31.211300Z

@codeperfect If you have already created an uberjar that has a main function, then java -jar youruberjar.jar should work.

2021-01-15T18:44:00.212400Z

switching between the two is relatively easy, and most tutorials (especially with ring) specify lein commands for building / running, I'd recommend lein for a beginner

2021-01-15T18:45:06.213Z

(unless you are following a tutorial or lesson that uses clj, then use that of course)

2021-01-15T20:10:15.214200Z

Hey all, I'm having a tough time wrapping my head about how to deploy a Clojure app for production. I'm hoping to explain what I'm trying to do and get pointed in the right direction! So I've got a simple ring-based HTTP server which connects to a database and serves up an index.html file for requests to "/". When I'm working on it locally, I run it with the clj tool, but now the time is coming to put it online. Coming from a Javascript background, the way I'd do that is: - install NGINX on a web server - start up a Node process running my backend server, this runs on port 3000. - the NGINX server reverse proxies requests on port 80 to port 3000, and bam, you're on the internet. This is a Clojure app though, and Clojure compiles to Java, so I need something that can build a JAR, which when invoked will start the server on port 3000. It looks like Boot and/or Leinigen will do this for you out of the box. I'm open to refactoring to use one of those tools, but I'd rather stay away from them if I can so I can stay current with language tooling. When it comes to clj though, it's unclear how you go about this. I've tried: - https://github.com/EwenG/badigeon , and - https://github.com/tonsky/uberdeps But it isn't working out great so far. Do I even need to build a JAR in the first place? Can I just run the app on the production server the same way I do locally?

borkdude 2021-01-15T20:11:11.214900Z

@andyfry01 try depstar for creating uberjars with clj, works wonders

borkdude 2021-01-15T20:11:36.215400Z

You don't have to build a jar though, you can start the app with clojure -M -m myapp.main as well

borkdude 2021-01-15T20:11:43.215600Z

right from the git repo

2021-01-15T20:12:20.216200Z

Right, I figure I can fall back on this if I can't get it to work. I assume the downside is that the app won't be as performant and/or use more memory? Are there other downsides to that approach?

borkdude 2021-01-15T20:12:32.216500Z

I've got an example of building an uberjar here: https://github.com/babashka/pod-babashka-buddy/blob/8ac874b44e3700666ce77aa94fee374a6cfae7df/deps.edn#L6

🙇 1
borkdude 2021-01-15T20:13:36.217900Z

@andyfry01 there is no real difference if your source is loaded from an uberjar or from disk, but people also AOT their code for faster startup during deploys

borkdude 2021-01-15T20:14:12.218600Z

this can be independently done from an uberjar though. uberjars are just easy for copying things around

borkdude 2021-01-15T20:14:48.219Z

but you often see the combination of both. none of this is mandatory

borkdude 2021-01-15T20:15:49.219600Z

so if your old AOT-ed uberjar is still running and you can stop it + swap the uberjars real quickly and then start the other one, you can have less downtime

2021-01-15T20:16:54.220100Z

Ahhh, ok. This is great info.

borkdude 2021-01-15T20:17:09.220500Z

this is actually one of the questions in the Clojure survey: how do you start your production app?

2021-01-15T20:17:28.220800Z

Looks like I'm headed over there next 👀

borkdude 2021-01-15T20:18:04.221500Z

https://www.surveymonkey.com/r/clojure2021

borkdude 2021-01-15T20:19:02.222600Z

question 20

2021-01-15T20:19:23.222900Z

Thank you! This is exactly what I needed. Sounds like my approach is going to change over to installing a process runner which can invoke my app directly with clojure on startup and reboot, and maybe upgrading to building JAR files if/when I need the advantages you mentioned above.

2021-01-15T20:19:43.223400Z

You've saved me hours of googling :man-bowing:

dpsutton 2021-01-15T20:22:07.224400Z

There shouldn’t be any hit to performance. Clojure -M is just a script that builds a class path. So largely saving you the trouble

2021-01-15T21:21:45.225700Z

> Clojure compiles to Java clojure emits bytecode that runs on the jvm

2021-01-15T21:22:53.226Z

the argument against running the server on prod the way you do locally is that clj is a build tool, and it's more reliable to have a standalone thing to run (a fat jar with deps) as opposed to a program that will fetch your deps and run your code

2021-01-15T21:23:02.226200Z

it removes a layer of complexity / source of errors

2021-01-15T21:24:38.226400Z

last I checked / tried, depstar was the best way to make an uberjar with clj https://github.com/seancorfield/depstar

2021-01-15T21:24:52.226700Z

bonus, the author is very active here and responsive to errors or questions

2021-01-15T21:27:28.229200Z

@andyfry01 I'm going to emphatically disagree with the others about the benefit of using an uberjar - the main advantage as I see it is that you have a single artifact to recreate a state of your code, which eliminates many sources of uncertainty (eg. - I've had prod errors caused by the programmer having a "customized" version of a lib locally). when you have an uberjar you can directly find this sort of discrepancy (as of a specific release artifact), while using the build tool in prod means investigating the caches and other server state

2021-01-15T21:29:57.230600Z

Also it separates concerns - if you use a build tool on prod, you now have tooling config / proxys, auth to private repos etc. that leak into multiple environment configs. Ideally you only need these things in the local dev machine and the CI / build server.

2021-01-15T21:32:26.232400Z

(to elaborate more about the specific example: if you are using an uberjar, you can verify the code that runs based on what's present in the jar, to do the same debugging when using a build tool in prod, you need to somehow guarantee that your editor / dev environment sees the exact same cache of packages, and sources all artifacts from the same places that the server did - maven shenanigans are rare, but between local dev mucking and private maven server admin they do happen)

2021-01-18T16:59:17.038500Z

Hey, thank you so much for the very detailed writeup. I was doing some more thinking over the weekend, and I think that just running things with CLJ both locally and in production is going to be the way to go. Coming from a JS perspective, I was very fixated on the concept of needing to build a production version of the app just as a matter of course. You could run any old JS Node server in development mode, but JS is slow, and anything you can do to make it faster is necessary in most cases. But if sticking this thing into a JAR/Uberjar delivers no performance benefits and adds complexity, then why would I? JARs come with other benefits, but they aren't going to be important for my specific case. Thanks again 🙂

2021-01-18T18:12:15.056300Z

using a jar reduces complexity, running a build tool on prod increases it

2021-01-18T18:19:52.056500Z

(but it reduces work in the short term, because you need to do the same work (installing the build tool, running it) on the server as you do locally. but now you need to eg. ensure that the build tool version and config matches your local, and factor the build tool run time into your startup time)

2021-01-18T18:20:44.056700Z

(a jar doesn't make your code run faster, but skips the run of the clj tool, which probably takes longer to run than your code does to start)

seancorfield 2021-01-15T21:40:20.233300Z

@andyfry01 if you get stuck with depstar ask for help in the #depstar channel -- happy to explain/walk you through everything there.

🙇 1
Harley Waagmeester 2021-01-15T21:44:48.233700Z

what is a transducer ?

2021-01-15T21:45:27.233800Z

it's a transform like "map" or "filter" removed from the concrete detail of a collection as input

2021-01-15T21:45:51.234Z

they are mainly an optimization, but they also mean you can eg. filter a channel without turning it into a collection first

2021-01-15T21:46:53.234200Z

if you run (map f (map g (map h coll))) there are two lazy seqs created that are only needed in order to provide data to another immediate step. clojure doesn't optimize that away, but transducers allow building the same pipeline without that overhead.

2021-01-15T21:48:04.234400Z

so you can use (sequence (comp (map h) (map g) (map f)) coll) to get the same result as the nested map calls, but it's cheaper on resources

2021-01-15T21:48:30.234600Z

as a beginner, you can ignore transducers for a while

Harley Waagmeester 2021-01-15T21:51:21.234800Z

mmm... thanks again, and yes i don't see any need for using transducers at this time

borkdude 2021-01-15T22:05:32.237800Z

@noisesmith I don't disagree with you. I'm just curious how "(eg. - I've had prod errors caused by the programmer having a "customized" version of a lib locally)" is fixed by an uberjar vs running using a dependency tool in an environment that should be resilient to dev-local changes.

borkdude 2021-01-15T22:07:01.239100Z

Btw, I also use an uberjar even for GraalVM native-image builds, using depstar now. I can also just pass the classpath using $(clojure -Spath):classes but I hit a problem on Windows (too long classpath, or some other gnarly problem) which was fixed by using an uberjar.

borkdude 2021-01-15T22:08:10.241100Z

I still need lein for uberjars in other projects due to some errors I'm getting with clj (some OS-specific .tar.gz error message)

borkdude 2021-01-16T21:13:55.372Z

@seancorfield To be honest, I don't know what's the matter with these .tar.gz files. In lein classpath they don't even show up, but with clojure -Spath they do. When I try to continue with the depstar uberjar, I get this error from graalvm native-image:

Error: No manifest in /Users/borkdude/Dropbox/dev/clojure/plsci/plsci.jar
com.oracle.svm.driver.NativeImage$NativeImageError: No manifest in /Users/borkdude/Dropbox/dev/clojure/plsci/plsci.jar

seancorfield 2021-01-16T21:16:43.373200Z

If there's a repo with a small repro case in, with instructions, I can take a look. Needs to be something I can run locally fairly easily.

seancorfield 2021-01-16T21:18:40.374200Z

Maybe native image is looking for more stuff in the MANIFEST.MF file than depstar puts in? Can you build it with lein uberjar and then extract MANIFEST.MF and see what's in that generated file? (and compare it to depstar generating the same-named file)

borkdude 2021-01-16T21:21:49.375600Z

Will do! The repo is here: https://github.com/borkdude/plsci The branch is deps.edn. To build, set GRAALVM_HOME to a GraalVM installation, so the script can find native-image. To "install" GraalVM, you just need to download and unzip this: https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.3.0/graalvm-ce-java11-linux-amd64-20.3.0.tar.gz I am assuming you are running this in linux/WSL2.

borkdude 2021-01-16T21:22:19.376Z

And then run script/compile-libplsci

seancorfield 2021-01-16T21:25:28.378200Z

Can you drop that info into an issue on depstar so I can take a look when I'm at a computer and "working"?

seancorfield 2021-01-16T21:25:53.378400Z

(and better to assume I'm doing it on macOS, although it would have to work on 10.12 which is really old)

borkdude 2021-01-16T21:26:14.378800Z

Of course!

borkdude 2021-01-16T21:26:31.379200Z

This is the manifest from lein btw:

Manifest-Version: 1.0
Created-By: Leiningen 2.9.3
Built-By: borkdude
Build-Jdk: 11.0.8
Leiningen-Project-ArtifactId: plsci
Leiningen-Project-GroupId: borkdude
Leiningen-Project-Version: 0.0.1-SNAPSHOT
Main-Class: plsci.core
Depstar doesn't seem to have any manifest...

seancorfield 2021-01-16T21:36:05.382Z

Yes, it does. Inside the JAR.

seancorfield 2021-01-16T21:36:49.382200Z

That's why I said you needed to extract the generated file.

seancorfield 2021-01-16T21:38:20.382700Z

But you must specify a main class and you need a pom.xml (which depstar can build for you if you don't already have one).

seancorfield 2021-01-16T21:40:00.382900Z

Nothing in that repo you linked seems to be using depstar so I can't see how you are invoking it.

seancorfield 2021-01-16T21:41:10.383100Z

Ah, just went back to your original deps.edn:

{:replace-deps ; tool usage is new in 2.x
            {seancorfield/depstar {:mvn/version "2.0.165"}}
            :ns-default hf.depstar
            :exec-fn uberjar
            :exec-args {:jar plsci.jar
                        :compile-ns [plsci.core]
                        :aliases [:native]}}
You're missing :main-class!

seancorfield 2021-01-16T21:41:38.383300Z

So it would use clojure.main in the manifest.

borkdude 2021-01-16T21:42:00.383500Z

Ah, it works now with:

:sync-pom true
                        :group-id plsci
                        :artifact-id plsci
                        :version "0.0.1-SNAPSHOT"
                        :main-class plsci.core

seancorfield 2021-01-16T21:42:04.383700Z

If you specify :main-class plsci.core you don't need :compile-ns

seancorfield 2021-01-16T21:42:27.383900Z

Yup. There we go!

borkdude 2021-01-16T21:42:39.384100Z

I was confused by the docs since :main-class was suggested to have -main, which I don't have. It's a native library, not an app.

seancorfield 2021-01-16T21:43:29.384300Z

If you have suggestions to improve the docs around that, I'd love an issue created with them in!

seancorfield 2021-01-16T21:44:03.384500Z

If you want to write up how to use depstar with GraalVM to make native images and send a PR, that would also be great.

borkdude 2021-01-16T21:44:25.384700Z

ok, I'll try to wrap up this branch and I'll do that

1
borkdude 2021-01-16T22:16:54.385800Z

So all good now. I don't think there's anything GraalVM specific that needs to be included in the README actually. I was just confused by the other .tar.gz warnings which I thought were related to the missing manifest.

borkdude 2021-01-16T22:18:16.386Z

Maybe a different error message would have helped me: > [main] WARN hf.depstar.uberjar - Ignoring :main-class because no 'pom.xml' file is present! > [main] WARN hf.depstar.uberjar - Ignoring :main-class because no 'pom.xml' file is present! See :sync-pom true option... but this gets really long and the README is clear.

seancorfield 2021-01-16T22:26:51.386300Z

I'm planning a spike at the end of the month on depstar and honeysql (v2) as I'm taking four days off... and there are two open issues that touch on pom.xml: https://github.com/seancorfield/depstar/issues

seancorfield 2021-01-16T22:27:25.386500Z

Specifically 56 and 59. So those will get addressed in a few weeks.

borkdude 2021-01-16T22:28:50.386700Z

Ah right, I think 59 would have helped me here. This is good default behavior probably. Maybe if main-class is a clojure file, also AOT it automatically?

borkdude 2021-01-16T22:29:10.386900Z

Then it's pretty much the ease of use that you get from lein uberjar

seancorfield 2021-01-16T22:30:03.387100Z

Well :main-class is "just" a class name so it can't necessarily tell but I suppose it could look for a matching namespace...

borkdude 2021-01-16T22:33:40.387300Z

Yeah, and skip it when there's already __init.class or Java class. Not sure how hard this is, since maybe depstar isn't running in the same classpath context as the libs you're building the jar for.

borkdude 2021-01-16T22:34:57.387500Z

Anyway :compile-ns [foo.bar] I found more explicit and maybe nicer than :aot true since it isn't obvious to me what this will do without looking at the docs every time

borkdude 2021-01-16T22:35:23.387700Z

And once you figure this out once, it's just copy paste for the next project.

borkdude 2021-01-16T22:39:36.388500Z

Strangely enough I already used depstar in another GraalVM project: https://github.com/babashka/pod-babashka-buddy/blob/8ac874b44e3700666ce77aa94fee374a6cfae7df/deps.edn#L12 I did not specify the main class but it worked well that time. I now see why... https://github.com/babashka/pod-babashka-buddy/blob/8ac874b44e3700666ce77aa94fee374a6cfae7df/script/compile#L25 You can set the main class upon compilation, so you don't need a manifest at all 🤦

😄 1
2021-01-15T22:08:21.241200Z

because my coworkers periodically, locally install changes to libraries into a cache without changing version numbers, so they are unable to recreate or understand the bug

2021-01-15T22:08:43.241400Z

or worse, they blame the wrong thing and introduce bad changes via shotgun debugging

borkdude 2021-01-15T22:08:47.241600Z

right

borkdude 2021-01-15T22:08:59.241800Z

but this problem still manifests with an uberjar I guess?

borkdude 2021-01-15T22:09:18.242Z

considering that the uberjar is also built using the same deps that would be have been resolved in the prod env if you used a build tool there

2021-01-15T22:09:19.242200Z

no - because you open the source file that's in the uberjar when you go to source

dpsutton 2021-01-15T22:09:40.242400Z

but it sounded like they lein install some deps?

2021-01-15T22:09:48.242600Z

@dpsutton for example, yes

dpsutton 2021-01-15T22:10:01.242800Z

but any resultant uberjar from their machine would be similarly tainted right?

dpsutton 2021-01-15T22:10:31.243200Z

which is borkdude's point. build machine needs to be clean. which is largely the same problem as the prod machine needs to be clean

2021-01-15T22:10:43.243600Z

But that's not the only issue, there's also cases where a lib does AOT, so acording to project.clj you are using one artifact, but in the deployed artifact you use another. This can fool an editor that hooks into your build config, it doesn't fool editor+uberjar.

2021-01-15T22:11:07.244300Z

but my point was about dev replication

dpsutton 2021-01-15T22:11:15.244600Z

true. i don't have a strong opinion in this fight and lean towards uberjar as default

Harley Waagmeester 2021-01-15T22:11:18.244800Z

@borkdude it;s like with C code, somebody will copy and modify a system header file and include it with a sneaky #include "stupidly-modified-system-header-file.h" , which uses a locally modified "standard" file, a real bummer

2021-01-15T22:11:23.244900Z

it's a pragmatic concern - the expensive thing is developer time

2021-01-15T22:11:42.245100Z

uberjars eliminate many factors that I've seen waste days of dev time

phronmophobic 2021-01-15T22:12:30.245900Z

I think one of the key points is around how to debug what happens when there is an issue. with the uberjar, it's much easier to inspect the uberjar artifact to debug what happened. if it's just a command that was run using the state on the prod machine, it's a different story. if there's an issue, you can redeploy, but how to figure out the root cause so it doesn't happen again?

💯 1
Harley Waagmeester 2021-01-15T22:12:45.246300Z

well, maybe a "dependency tool" will stop that in clojure

borkdude 2021-01-15T22:13:13.246700Z

when you use an uberjar with a lib that's already AOT-ed... that doesn't really help the fact that that other lib was already AOT-ed. I had a problem with a lib that was AOT-ed and transitively included AOT-ed sources of cheshire, while we needed a newer version. This was a major headache, but not helped by using an uberjar.

Harley Waagmeester 2021-01-15T22:13:52.247300Z

but it's really all based on filenames

Harley Waagmeester 2021-01-15T22:13:56.247500Z

eh]

borkdude 2021-01-15T22:14:16.247600Z

when I want to know what's being used, I do: (slurp (io/resource "foo/bar.clj")) or (slurp (io/resource "foo/bar_init_.class")) which is how I managed to find the AOT issue

borkdude 2021-01-15T22:14:26.247800Z

this works regardless of uberjar or not

2021-01-15T22:14:30.248Z

@borkdude it's a pain, but when I use an uberjar, I can see via the files in the jar that the wrong thing was included - not using an uberjar slows finding issues like that

phronmophobic 2021-01-15T22:15:35.248200Z

there's also cases where you deploy, it corrupts some state in the database or some other persistent storage, but you don't find out about it until a week later. if you have the actually deployed artifacts, then it makes debugging, reproducing, and fixing those types of issues much easier.

2021-01-15T22:15:41.248500Z

I guess I could have saved a lot of words and said "it reduces many layers of complexity when diagnosing prod issues"

Harley Waagmeester 2021-01-15T22:16:20.249300Z

ok, fixed by an uberjar, ok... i see some greater stgability now

Harley Waagmeester 2021-01-15T22:16:39.249600Z

stability, sorry

borkdude 2021-01-15T22:16:50.249700Z

it can also cause issues, e.g include a conflicting resource. I don't see an uberjar as a cure-all, although they may cure some things. (slurp (io/resource ...)) is the cure for "what is being used".

2021-01-15T22:18:07.250300Z

right, but looking at the jar I can see the two resources (the .class vs. the .clj) and that they come from different artifacts

2021-01-15T22:18:52.251300Z

also, I tend to use io/resource to get coords, and open that coord in my editor rather than using slurp, but that's a trivial difference

borkdude 2021-01-15T22:18:55.251500Z

@codeperfect An uberjar is not a cure-all for this. An uberjar can be built from patched files. "Reproducible environment" is the proper answer here.

borkdude 2021-01-15T22:19:44.251800Z

(io/copy (io/resource ...) "/tmp/foo.clj") is also something I sometimes do ;)

2021-01-15T22:20:30.252100Z

my editor opens jars so I don't need to do that

ghadi 2021-01-15T22:20:36.252500Z

Wondering about this

borkdude 2021-01-15T22:20:44.252700Z

mine does too, but read-only

borkdude 2021-01-15T22:21:23.253400Z

I can reproduce it if you want

2021-01-15T22:21:41.253900Z

yeah, it sounds like we found relatively equivalent process- I often use io/resource by itself to verify where the ns comes from (which is often more interesting than the precise contents at a first glance)

Harley Waagmeester 2021-01-15T22:21:53.254200Z

@borkdude heh, as usual you reduce the dataset to the essential content ;0

Harley Waagmeester 2021-01-15T22:22:05.254500Z

🙂

Harley Waagmeester 2021-01-15T22:22:10.254800Z

wait

2021-01-15T22:22:19.255300Z

(ins)user=&gt; (io/resource "clojure/set.clj")
#object[java.net.URL 0x545607f2 "jar:file:/home/justin/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar!/clojure/set.clj"]

ghadi 2021-01-15T22:22:22.255600Z

Is it an issue with a native lib?

Harley Waagmeester 2021-01-15T22:22:24.255800Z

oh there it is

Harley Waagmeester 2021-01-15T22:23:16.256400Z

my wifi really sucks, i appologize for the lag

borkdude 2021-01-15T22:24:08.256500Z

yeah, it took me a while to figure out that our version of cheshire was using the equivalent of:

user=&gt; (io/resource "clojure/set__init.class")
#object[java.net.URL 0x3e134896 "jar:file:/Users/borkdude/.m2/repository/org/clojure/clojure/1.10.1/clojure-1.10.1.jar!/clojure/set__init.class"]

borkdude 2021-01-15T22:24:16.256700Z

so next time I'll be looking for that darned thing first

borkdude 2021-01-15T22:24:52.257Z

I think so yes.

borkdude 2021-01-15T22:25:09.257200Z

it is with the SVM dependency from GraalVM

borkdude 2021-01-15T22:29:15.258Z

I've got a repro here:

{:deps {borkdude/sci {:mvn/version "0.2.0"}
        cheshire/cheshire {:mvn/version "5.10.0"}
        borkdude/clj-reflector-graal-java11-fix {:mvn/version "0.0.1-graalvm-20.3.0"}}
 :aliases {:uberjar
           {:replace-deps ; tool usage is new in 2.x
            {seancorfield/depstar {:mvn/version "2.0.165"}}
            :ns-default hf.depstar
            :exec-fn uberjar
            :exec-args {:jar plsci.jar
                        :compile-ns [plsci.core]
                        :aliases [:native]}}
           :native {:jvm-opts ["-Dclojure.compiler.direct-linking=true"]
                    :extra-deps {org.clojure/clojure {:mvn/version "1.10.2-rc2"}}}}}
$ clojure -X:native:uberjar
[main] INFO hf.depstar.uberjar - Compiling plsci.core ...
[main] INFO hf.depstar.uberjar - Building uber jar: plsci.jar
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/nativeimage/svm-hosted-native-darwin-amd64/20.3.0/svm-hosted-native-darwin-amd64-20.3.0.tar.gz"}
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/nativeimage/svm-hosted-native-linux-amd64/20.3.0/svm-hosted-native-linux-amd64-20.3.0.tar.gz"}
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/nativeimage/svm-hosted-native-windows-amd64/20.3.0/svm-hosted-native-windows-amd64-20.3.0.tar.gz"}
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/truffle/truffle-nfi-native-darwin-amd64/20.3.0/truffle-nfi-native-darwin-amd64-20.3.0.tar.gz"}
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/truffle/truffle-nfi-native-linux-aarch64/20.3.0/truffle-nfi-native-linux-aarch64-20.3.0.tar.gz"}
{:warning "ignoring unknown file type", :path "/Users/borkdude/.m2/repository/org/graalvm/truffle/truffle-nfi-native-linux-amd64/20.3.0/truffle-nfi-native-linux-amd64-20.3.0.tar.gz"}

borkdude 2021-01-15T22:29:56.258800Z

^ @seancorfield does this look familiar?

borkdude 2021-01-15T22:30:13.259300Z

it works with lein

dharrigan 2021-01-15T22:32:24.261200Z

Let's see if I can express this. I have a grouping of vector of maps. On each group, I'm invoking (mapv :fookey my-vector-of-maps) and it's sorta working, it's pulling out from the maps and constructing a vector of the values I'm interested in. However, in certain groups, the value for the key is nil, thus I end up with [nil] on occasions. Any suggestions on how to avoid constructing the vector (or returning an empty vector []) if the value of the key is nil?

2021-01-15T22:32:34.261300Z

it would be nice to have a tool that replicated this logic https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L427 - finding the location of first of class / cljc / clj to be found

borkdude 2021-01-15T22:35:00.262500Z

Perhaps this can be added to the output of *loading-verbosely*:

user=&gt; (binding [clojure.core/*loading-verbosely* true] (require '[clojure.set] :reload-all))
(clojure.core/load "/clojure/set")
nil

2021-01-15T22:35:28.263Z

you can wrap it in filter identity or use (into [] (mapcat ...) coll) to skip maps with no results

2021-01-15T22:36:13.263300Z

yeah, it would be great if loading-verbosely was more specific about where it got that thing it was loading

Harley Waagmeester 2021-01-15T22:36:40.263800Z

yes, filter is your friend

2021-01-15T22:36:52.263900Z

currently that's decided at the innermost layer (in Rt/load), which makes it harder to instrument like that

borkdude 2021-01-15T22:37:17.264400Z

well, a small function that just tries to find the first non-nil resource using this logic is quickly written

Harley Waagmeester 2021-01-15T22:37:18.264700Z

i eat millions of nils with my filter

borkdude 2021-01-15T22:37:53.265300Z

you can hook that up with the output from loading-verbosely to print the exact locations

2021-01-15T22:37:54.265500Z

the fact that mapcat can return 0 or more results for each input is under-utilized, it can be an elegant replacement for a separate filter step

2021-01-15T22:38:37.265700Z

right, for something like this there's no harm in calculating it twice

dharrigan 2021-01-15T22:39:03.266300Z

Not sure if I've described that correctly, a moment, let me whip up a data shape...

2021-01-15T22:52:27.266600Z

user=&gt; (mapcat (fn [m] (let [[fst :as result] (:k m)] (when fst [result]))) [{:k [nil]} {:k [:a :b :c]} {:k [:d :e :f]}])
([:a :b :c] [:d :e :f])

2021-01-15T22:53:58.266900Z

or

user=&gt; (remove #{[nil]} (map :k [{:k [nil]} {:k [:a :b :c]} {:k [:d :e :f]}]))
([:a :b :c] [:d :e :f])

dharrigan 2021-01-15T22:54:18.267100Z

Hi, sorry it took some time...

dharrigan 2021-01-15T22:54:21.267300Z

(def my-coll-of-maps [{:k :a
                       :x nil
                       :y "456"}
                      {:k :b
                       :x "123"
                       :y "456"}
                      {:k :b
                       :x "789"
                       :y "456"}
                      {:k :c
                       :x nil
                       :y "456"}])

(for [[_ my-maps] (group-by :k my-coll-of-maps)]
  (let [x-vec (mapv :x my-maps)]
    x-vec))

dharrigan 2021-01-15T22:54:50.267700Z

[nil]
["123" "789"]
[nil]

2021-01-15T22:55:03.268200Z

that's without the surrounding [] of course

2021-01-15T22:55:19.268900Z

my examples above both remove [nil] - the second one is better actually

dharrigan 2021-01-15T22:55:32.269100Z

Ideally I would like to end up with either [] or nothing, just one entry, ie., ["123" "789"]

2021-01-15T22:55:42.269400Z

oh, so you do want mapcat

2021-01-15T22:55:52.269700Z

(mapcat :x my-maps)

dharrigan 2021-01-15T22:56:01.269900Z

trying 🙂

dharrigan 2021-01-15T22:56:54.270400Z

I end up with

dharrigan 2021-01-15T22:56:56.270700Z

()
(\1 \2 \3 \7 \8 \9)
()

2021-01-15T22:57:11.270900Z

yeah, I missed that for

2021-01-15T22:58:35.272Z

yeah, you probably just want (remove #{[nil]} (for ...))

2021-01-15T22:59:05.272700Z

or turn the let into :let in the for, then add a :when that excludes [nil]

dharrigan 2021-01-15T23:00:01.273Z

the remove works, but interested in the second option too

dharrigan 2021-01-15T23:00:26.273200Z

googling

dharrigan 2021-01-15T23:02:51.273400Z

(for [[_ my-maps] (group-by :k my-coll-of-maps)
      :let [x-vec (mapv :x my-maps)]
      :when (not= [nil] x-vec)]
  x-vec)

dharrigan 2021-01-15T23:03:03.273600Z

that works too

dharrigan 2021-01-15T23:03:45.273900Z

thank you @noisesmith that was very helpful 🙂

dharrigan 2021-01-15T23:03:52.274100Z

and educational too!

Andy Nortrup 2021-01-15T23:27:48.275300Z

Hi there, I’m confused by an error message that I keep getting. I have function that ends with:

(remove empty? ....) 
that outputs
({:CONNECT [:PLAT]} {:CONNECT [:CHART, :TRENDS]})
Which I then want to merge so that I have one map that looks like: {:CONNECT [:PLAT :CHART :TRENDS]}

Andy Nortrup 2021-01-15T23:30:14.276Z

I can take the straight output and do: (reduce (fn [one two] (merge-with union one two)) '({:CONNECT [:PLAT]} {:CONNECT [:CHART, :TRENDS]})) and get the output I want.

Andy Nortrup 2021-01-15T23:31:33.277500Z

But when I try to put it straight into the larger function I’m building up (reduce (fn …) (remove empty?….) I get an error > Execution error (ClassCastException) at prodops.core/eval31234$fn (form-init5220355977580653078.clj:1705). > ; class [Ljava.lang.Object; cannot be cast to class clojure.lang.IPersistentCollection ([Ljava.lang.Object; is in module java.base of loader ‘bootstrap’; clojure.lang.IPersistentCollection is in unnamed module of loader ‘app’)

2021-01-15T23:48:36.278100Z

Can you share more context for how you’re putting it into the larger function? Also, merge-with can apply to more than two maps at once, so this is a bit shorter:

(apply (partial merge-with union)
       '({:CONNECT [:PLAT]} {:CONNECT [:CHART, :TRENDS]}))