Fork me on GitHub
#beginners
<
2020-06-16
>
motform08:06:03

I’m using depstar to build a .jar, but I’m having some problems with including resources. I’m using :extra-paths ["resources"] in my deps.edn , which works when I run the application from clj.

dharrigan08:06:46

When you explode out the jar, does it have the resource (files) inside of it?

motform08:06:00

Not that I can see, no. I also get a file-missing-error when i try to run the .jar

dharrigan09:06:02

How are you creating the uberjar?

motform09:06:51

clojure -A:depstar -m hf.depstar.uberjar target/app.jar -S

motform09:06:45

is it idiomatic to include resources in the jar, or should they be distributed through some other means and placed in the file system?

dharrigan09:06:29

It's not so much an idomatic thing, just normal. You find that too in Java, Kotlin etc...anything that is "distributable"

dharrigan09:06:56

Of course, it also depends upon the usecase, for example, some applications load things from the filesystem, some from a database...all down to the needs of the application.

dharrigan09:06:23

Clojure is no different in that respect, or rather the bundling.

dharrigan09:06:58

What is your directory layout?

motform09:06:10

that makes sense, in this case, I just want to deploy a ring-application, so I need an .edn with some data and the regular web-sidekicks

dharrigan09:06:51

In my projects, I have this in my project-local deps.edn

dharrigan09:06:05

{:paths ["src" "resources"].........}

dharrigan09:06:23

When I build an uberjar, everything under resources gets bundled in.

dharrigan09:06:31

and in there, I have edn files that I load from the classpath.

motform09:06:18

ah, now the files seem to be there! I had it under :extra-paths,

motform09:06:31

but i still get Exception in thread "main" .FileNotFoundException: edn/cocktail-schema.edn (No such file or directory)

motform09:06:33

even though the path in the jar should be edn/*.edn. Is it because I’m reading it with slurp?

dharrigan09:06:19

Try using io/resource

dharrigan09:06:34

Let me get you a reference a moment...

dharrigan09:06:00

so try (io/resource "edn/cocktail-schema.edn")

dharrigan09:06:24

If you need to slurp a file from a JAR file, don't call io/file on the result of calling io/resource, or you will get an exception that says the resource is "not a file". Instead, call slurp directly on the result of io/resource.

motform09:06:02

that makes sense! I guess my naked slurp tried to read form the file system, as it should

motform09:06:53

that worked, thank you!

dharrigan10:06:54

You're most welcome.

💫 3
🎉 3
Lukas11:06:28

Hey, do you have any recommendations for someone who would like to create his own datalog implementation? I already done some research and found a good amount of papers/books about the topic and also look at the source code of clojure.contrib.datalog. But what I'm missing are resources that leaning more towards the practical side of implementing such an engine and/or understanding the requirements needed. I will probably figure out what I have to do with the resources I already found but I don't find it particular joyful to read/understand research papers. I know that I eventually have to read them anyways 😂 but just want to check if maybe someone knows a bit more motivational material which gets me started? 😇

andy.fingerhut13:06:06

You may want to ask on the #datascript or #datahike channels for such questions, too.

Daniel Tan11:06:57

datahike exists btw

Dennis Tel11:06:45

Hi! I’m considering Clojure/Clojurescript for a project. (Don’t have a lot of experience with it yet!) I need to create a library that has to run on both our backend (java/kotlin) and in the browser. Is it possible to compile the source to both JavaScript and a JAR? And are there other things to consider or are there any pitfalls?

Cameron Kingsbury15:06:13

what is the harm of nil in maps in clojure?

Cameron Kingsbury15:06:26

I seem to recall Rich Hickey warning against this in "Maybe Not"

Daniel Tan15:06:02

it’s more like don’t use nil if you don’t need to

Daniel Tan15:06:07

you can prune them off

Daniel Tan15:06:25

unless you explicitly need to convey that it is broken

bartuka15:06:05

may cause ambiguous interpretation: (get m :key) ;; => nil now.. the :key is missing in your m or it is there, but its value is nil ?

Daniel Tan15:06:35

it’s like conveying that humans having two hands, but this guy has one missing hand

Daniel Tan15:06:50

but snakes has no hands, not no missing hands

noisesmith15:06:00

and clojure uses the same data representation for snakes and humans

bartuka15:06:26

and if you need to explicit tell that some hands are not there hahaha .. maybe is better to be explicit: {:hands :both}, {:hands :one}, {:hands :missing}

Daniel Tan15:06:30

representing objects as just data has the nice thing of handling them the same way

(defn chop-hands [animal] 
  (if (contains? animal :hands) 
    (assoc animal :hands :missing)))

Cameron Kingsbury15:06:05

yep sounds good tyty

vlad_poh15:06:17

i'm trying out leiningen on WSL and for the same repo i get this warning WARNING: cat already refers to: #'clojure.core/cat in namespace: net.cgrand.regex, being replaced by: #'net.cgrand.regex/cat

vlad_poh15:06:01

but it doesn't appear in a command prompt or powershell

noisesmith15:06:37

that warning is standard, and doesn't indicate an error - cgrand.regex was implemented before cat existed, so won't be broken by shadowing the new clojure.core/cat

noisesmith15:06:18

you could compare the output of (System/getProperty "java.class.path") in case you are getting different versions - another possibility is different logging config

👍 3
vlad_poh15:06:07

ah i think that's it WSL : Leiningen 2.8.1 on Java 1.8.0_252 OpenJDK 64-Bit Server VM Command Prompt : Leiningen 2.9.1 on Java 11.0.2 Java HotSpot(TM) 64-Bit Server VM

noisesmith15:06:34

yeah, that would explain it

👍 3
vlad_poh15:06:45

switching to see if i can get a colorful repl. pretty drab working in the command prompt

noisesmith15:06:45

you might be interested in rebel in that case https://github.com/bhauman/rebel-readline

sova-soars-the-sora16:06:00

how to (map fancshan coll) when the coll is more than one element and simply (fankshan coll) when there's only one element?

sova-soars-the-sora16:06:21

cs and ks interchangeable in my world today

seancorfield16:06:53

@sova I would change the code to not have that ambiguity -- see Heisenparameter on Stuart Sierra's Don'ts list.

noisesmith16:06:58

first off, avoid ever doing that but if you are forced to because it's data from an api, transform the single thing into a coll at the earliest point you can

seancorfield16:06:58

I used to do it quite a bit when I was learning Clojure and I still curse my earlier self when I run across an old function in our code base that still does this 😞

sova-soars-the-sora16:06:22

okay so i want to make sure it's a collection (even of one item) before passing it in.

noisesmith16:06:50

right - the general solution is hard to do in clojure since we use collections of collections everywhere but at an API boundary you probably have a better idea of when to convert

noisesmith16:06:09

I do the conversion in the same place where I get the data

sova-soars-the-sora16:06:24

I see... Very good very good. tie the shoes before entering clojureland

sova-soars-the-sora16:06:55

Cool, i feel like each day i'm moving a little bit faster and more fluidly... it's a delight to figure stuff out

sova-soars-the-sora16:06:59

"clojure by example"

Phil Hunt19:06:29

Quick question about Reagent. Following a couple of tutorials now, I add reagent as a dependency, then (:require [reagent.core :as r]) When I do r/render it doesn't work the way any of the tutorials suggest it should, I get errors suggesting r/render is undefined, but if I (:require [reagent.dom :as rd]) and do rd/render it works fine. Has something changed recently?

noisesmith19:06:41

are you doing this in clojure or clojurescript?

Phil Hunt19:06:08

happened both with shadow and lein plugin thingy

Phil Hunt19:06:50

I mean, I seem to be able to do everything expected using the reagent.dom version, but I'm puzzled because I've hit this on several different people's tutorials now.

Phil Hunt19:06:57

It's an issue with reagent 1.0.0-alpha2. If I use 0.9.1 it works the way the tutorials expect and compiles without error.

Phil Hunt19:06:10

Oh well. Sorry to bother people.

dpsutton19:06:47

Reagent was updated. Now there’s a reagent.dom namespace which has the render function

Phil Hunt19:06:03

Ah, so that's the intention rather than a bug?

Phil Hunt19:06:18

render was separated out into the .dom namespace?

Phil Hunt19:06:28

it was in both places in the dox

phronmophobic19:06:19

does that mean reagent is close to supporting other renderers besides react?

Phil Hunt19:06:33

thanks dpsutton 🙂

marreman20:06:48

I’m trying to swap! an atom inside a ring handler but I can’t seem to get it to work. I would appreciate a pointer. 🙂 games is not getting updated here:

(defonce games (atom {}))

(defn view [game]
  (layout
    [:div (:name game)
     [:pre {} @games]]
    ))

(defn create-game [name]
  (swap! games assoc name {:name name}))

(defn handler [request]
  (let [name (->> (:uri request)
                       url-decode
                       (remove #{\/})
                       (apply str))
        game (get @games name (create-game name))]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (html (spyfall.game/view game))}))

marreman20:06:33

Also I’m running this with ring.adapter.jetty/run-jetty but the REPL doesn’t seem to be in sync with the state of the server running separately.

noisesmith20:06:17

are the repl and server in the same vm?

seancorfield20:06:43

I suspect @games is evaluated before (create-game name) since arguments are evaluated left-to-right in function calls.

noisesmith20:06:08

here the value of "game" will either be a specific game (if it exists) or all the games defined so far (if it wasn't found, so created)

noisesmith20:06:37

because swap! returns the entire state of the atom

seancorfield20:06:34

Hmm, good point. That's another bug.

noisesmith20:06:57

oh! never mind, this is even worse - game is a global state - the name of the game will change on every request no, I was right the first time

noisesmith20:06:11

I was momentarily confused by the weird structure of the hash

seancorfield20:06:12

user=> (def foo (atom {}))
#'user/foo
user=> (defn bar [n] (swap! foo assoc n {:name n}))
#'user/bar
user=> (get @foo "hello" (bar "hello"))
{"hello" {:name "hello"}}
user=> (get @foo "hello" (bar "hello"))
{:name "hello"}
user=> (get @foo "world" (bar "world"))
{"hello" {:name "hello"}, "world" {:name "world"}}
user=> (get @foo "world" (bar "world"))
{:name "world"}
user=> 

👍 3
seancorfield20:06:11

In the first get, @foo is {} and does not contain "hello" so the not-found value is used, which is the full hash map after creating the new item.

seancorfield20:06:35

In the second get, @foo is now the hash map containing "hello" so its value is returned.

noisesmith20:06:39

as for the atom not changing at all, I go back to my first question: you mention a repl and a http server, are they in the same jvm?

marreman20:06:58

> are the repl and server in the same vm? it’s one lein repl and one lein run separately

noisesmith20:06:14

so the answer is no, so the one in the repl won't change

noisesmith20:06:20

atoms are local to one process

seancorfield20:06:42

And so is the code -- so changing things in the REPL won't affect your running server.

marreman20:06:51

So I would start the jetty server in the repl process

noisesmith20:06:35

right,launching a future and binding it is handy for that (def server (future (run-jetty ...)))

noisesmith20:06:42

that way you can still get at the process (to check if it exited with realized? or see the return value / error by using deref if it has...)

marreman20:06:00

Created a namespace like this with stuff to evaluate when developing:

(ns spyfall.dev
  (:require [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]
            [spyfall.game]))

(comment
  (def server
    (future
      (run-jetty (wrap-reload #'spyfall.game/handler)
        {:port 3000 :join? false})))
  )

noisesmith20:06:06

facepalm if you use join? false already, you don't need a future, sorry

noisesmith20:06:11

but yeah, that's the idea

marreman20:06:36

Sorry I’m completely new to this

noisesmith20:06:11

oh no, the facepalm was at myself, I should have remembered that run-jetty already lets you start in a background thread and return a server handle

marreman20:06:13

But it’s interesting with the more general approach

marreman20:06:22

Knowledge that can be transfered

marreman20:06:48

So this made things work better

(defonce games (atom {}))

(defn view [game]
  (layout
    [:div (view-name (:name game))
     [:pre {} @games]]
    ))

(defn create-game [name]
  {:name name
   :players []})

(defn handler [request]
  (let [name (->> (:uri request)
                       url-decode
                       (remove #{\/})
                       (apply str))
        game (get @games name (create-game name))]
    (swap! games assoc name game)
    {:status 200
     :headers {"Content-Type" "text/html"}
     :body (html (spyfall.game/view game))}))

marreman21:06:15

Thank you for your help! 🙂

seancorfield21:06:17

@US3HM05U5 If you have multiple concurrent requests to the same "game" URI you will have a race condition there because you're fetching the game's current value and then swapping it back into the atom as two separate operations.

seancorfield21:06:09

Depending on exactly how/where you are also updating that atom, you may lose changes (because both concurrent get's could get the old version of the game, make changes, and then whichever assoc happens last will "win").

marreman06:06:24

Ah, ok! I’m not sure how that can be mitigated. I guess the only way to make sure is to queue up the mutations so that they happen one at a time?

marreman06:06:03

And I guess that’s something that databases are good at? 😛

marreman06:06:10

I just want this ad hoc “database” atom for my program. Implementing something my co-worker did in Elixir to see how the implementations differ. 🙂

seancorfield06:06:35

swap! is fine, if you operate on the current value rather than trying to fetch a value first and then bashing it back in.

seancorfield06:06:04

Consider swap! with update instead of assoc and take a look at fnil as a way to provide a "default" for a nil value.

marreman06:06:36

Right, I’ll take a look! Thank you! 🙂

Harshana20:06:38

Hey! I have been writing an API in clojure but I have some doubts in what wrap-json-body /`wrap-json-params` actually do. Why is it required to include this in the response?

noisesmith20:06:27

ring uses middleware to add features to the http stack, using those middleware allows using json automatically, but seeing the data as clojure data in your code (in your params or response)

noisesmith20:06:39

wrap-json-body takes a request handler function, and returns a new one which will transform output data into json

thanks3 3
adam20:06:45

How can I apply a middleware to a specific set of routes? I am using bidi and its make-handler function. I need to force authentication on some routes.

adam22:06:08

Answering my own question... it seems, since bidi is data based and compatible with Ring, I need to wrap my handlers one by one before passing them to bidi's make-handler fn