Fork me on GitHub
#beginners
<
2021-04-22
>
Kevin00:04:34

I see function names like put! or <! etc. What does the "!" exclamation mark mean? And when should I name my own functions with this character appended?

seancorfield01:04:45

core.async has its own peculiarities around those functions too — the single ! means non-blocking (must be used inside a go block/loop) and the double !! means blocking.

NoahTheDuke02:04:08

in a macro, i have a let block where i put some expressions in a map. within the returned quasi-quote expression, i want to fetch one of those expressions from the map without resolving it, and then resolve it later. is that possible?

hiredman02:04:22

That doesn't parse for me

hiredman02:04:00

Something to keep in mind is macros usually generate code that does things, they don't do things

NoahTheDuke03:04:28

let me think on how best to reword this, then. the solution i've found tonight is to wrap the expressions in a function, and then call that function when i need it, but idk if that's the best method

dpsutton03:04:43

What are you trying to do?

Clark Urzo05:04:34

I realise this depends a lot on my setup, but how do I deploy a Reagent app to Heroku?

Clark Urzo05:04:18

I tried serving the index.html file of my app via ring-jetty, but it seems it’s not that simple

phronmophobic05:04:47

Is there a server side piece or is the app all client side?

Clark Urzo05:04:28

I can already handle HTTP requests just fine and even serve handcrafted HTML files, but it’s just blank otherwise

phronmophobic05:04:15

So you have it working locally on your computer, but it doesn't work when you deploy?

Clark Urzo05:04:16

@U7RJTCH6J Ah, well there should be a server-side piece because of the database I’ll connect in the future

👍 2
Clark Urzo05:04:51

Nope, not even locally. Hold on lemme post a couple of files

Clark Urzo05:04:47

So this is my index.html file and normally it works when I run it with shadow-cljs


<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="/css/style.css" rel="stylesheet" type="text/css">
    <link rel="icon" href="">
    <title>shadow reagent</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="app"></div>
    <script src="/js/main.js" type="text/javascript"></script>
    <script>window.onload= function() { client.core.main(); }</script>
  </body>
</html>

Clark Urzo05:04:16

My server on the other hand:

(ns server.core
  (:require [ :as io]
            [clojure.string :as str]
            [clojure.pprint :refer [pprint]]
            [ring.adapter.jetty :as jetty]
            [ring.middleware.reload :refer [wrap-reload]]
            [ring.middleware.resource :refer [wrap-resource]]
            [ring.util.response :refer [response 
                                        response?
                                        content-type 
                                        redirect 
                                        file-response 
                                        resource-response]]
            [reitit.ring :as reitit-ring]
            [server.routes :refer [router]]))

...

(defn resource-handler
  [request]
  (as-> request r
    (:path-params r)
    (:filename r)
    {:status 200
     :body (io/input-stream (str "public/index.html" r))}))

...

;;;; Main
(def PORT
  "Heroku requires that the server uses $PORT.
  See "
  (try 
    (Integer. (System/getenv "PORT"))
    (catch Exception e
      (println (str "Caught exception: " (.getMessage e))))))

(defn -main
  [& args]
  (jetty/run-jetty (-> resource-handler
                       (wrap-reload))
                   {:port (or PORT 3000)
                    :join? false}))

Clark Urzo05:04:06

So when I run -main, it does serve the index.html file as I specified it

Clark Urzo05:04:33

But it just displays a blank page, as opposed to when I run it with shadow-cljs where it displays the entire UI

phronmophobic05:04:46

I suspect the issue is areound (io/input-stream (str "public/index.html" r))

phronmophobic05:04:55

it's probably producing a string like "public/index.htmlindex.html"

Clark Urzo05:04:25

Ahh, hmm, but it does serve the HTML file as is, though? I checked the source in a browser and it does correspond to the contents of index.html

phronmophobic05:04:39

(defn resource-handler
  [request]
  (prn "filename" (-> request :path-params :filename))
  (as-> request r
    (:path-params r)
    (:filename r)
    {:status 200
     :body (io/input-stream (str "public/index.html" r))}))

Clark Urzo05:04:11

Ahhh hold on

Clark Urzo05:04:26

could it be the case that I need to route /js/main.js as well?

👍 2
phronmophobic05:04:24

I'm not sure what middleware is hooked up, but I wouldn't be surprised if there wasn't any data located at (-> request :path-params :filename)

phronmophobic05:04:38

I wrote simple web app. Here's the route I use for serving files from the app's resources: https://github.com/phronmophobic/wavelength_helper/blob/main/src/wavelength_server/core.clj#L91

Clark Urzo05:04:51

I see. I'll find a way to make it serve the entire public dir

👍 2
phronmophobic05:04:01

also the routes for serving the html file:

(GET "/index.html" [] (response/resource-response "index.html" {:root "public"}))
   (GET "/" [] (response/resource-response "index.html" {:root "public"}))

phronmophobic05:04:46

using the built in resource-response and :root argument helps protect against serving other files not inside the "public" folder

Clark Urzo05:04:57

I see. All right, I'll try that. I'm actually using reitit though but it should be straightforward to translate your approach.

👍 2
Clark Urzo07:04:31

All right, so now I have a different problem. I am able to serve static files but for some reason my changes aren't reflected. It's like ring-jetty is caching my files on creation then just using those.

Clark Urzo07:04:02

The reason I think this is the case is because I tried deleting a file and yet both a cache-cleared Firefox and curl still return it

Clark Urzo07:04:23

I have also rebooted, hoping that it would help, to no avail

Clark Urzo07:04:32

In particular, recompiling with shadow-cljs produces a new public/js/main.js file, but those changes aren't reflected anywhere

Clark Urzo07:04:55

My question is, is there a way to disable this 'caching' behaviour?

Eric Ihli11:04:29

Are the new versions of main.js named with their release version? (Either something you specified or their git sha.) If so, do the build tools have a way of updating your html files to point to those newly named files, or is that a manual process? If you have it set up to not use that release-version behavior and instead to always generate main.js exactly as spelled, then could you try changing that behavior and following the same deploy steps and see if that fixes it? If that fixes it, at least you narrow down that it's a caching issue in a post-deploy section of the process. I'm not familiar with Heroku so I don't know what cache-related behavior/solutions are there. I'm pretty sure something like a cache in ring-jetty wouldn't persist after a restart of the application. That would just be an in-memory cache, right?

roelof08:04:27

@madstap thanks, I see it needed very complex code

Greg Rynkowski12:04:56

Hi, is there a way to invoke a no-params function without using parenthesises, I mean execute a function by using another function? e.g.

(-> #(println "hello") invoke)
Some type of invoke function? I mean I found I could use apply with nil,
(-> #(println "hello") (apply nil))
but still I’m wondering is there anything built-in to execute no-params functions…

yuhan12:04:31

(defn invoke [f] (f))

Lennart Buit12:04:42

Well there is nothing stoping you from writing ^that, I’m just wondering in what cases it would be useful ^^

Lennart Buit12:04:56

(was about to type the same as yuhan ^^)

Greg Rynkowski12:04:15

(defn invoke [xf & args] (apply xf args))
Actually this does the job

yuhan12:04:17

I think it's just trivial enough not to warrant being in clojure.core, heh

delaguardo12:04:15

(-> #(println "hello") (.invoke))

delaguardo12:04:16

also works for multiarity functions as well (-> #(println "hello" %&) (.invoke 1 2 3))

👍 2
Edward Ciafardini14:04:19

having an issue running planck on a localhost port & evaluating blocks in Atom. I use planck -n 777 to start the REPL, then "Connect To Socket REPL" in Atom- I get a success message initially, but when I try to evaluate a block, I get an error. Any ideas? (uploaded images follow the timeline of actions)

mfikes14:04:54

@esciafardini Maybe as a test separately try telnet 0 777 (or some other TCP client) just to be sure you can connect

Edward Ciafardini14:04:55

getting similarly conflicting response... nc: connectx to localhost port 777 (tcp) failed: Connection refused Connection to localhost port 777 [tcp/multiling-http] succeeded!

dpsutton14:04:00

can you try with a port greater than 1024? I think these ports require more privileges to use. Try with port 7777?

Edward Ciafardini14:04:18

same issue on port 7777

Edward Ciafardini14:04:40

**thank you for suggestion

mfikes15:04:07

$ telnet 0 777
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
cljs.user=> (+ 2 3)
5
cljs.user=>

mfikes15:04:13

^ This is what I get

mfikes15:04:36

Perhaps local firewalls are in place on your box?

Edward Ciafardini15:04:00

same here, must be an issue in Atom.

➜  ~ telnet 0 7777
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
cljs.user=> (+ 2 3)
5
cljs.user=> 
Everything works in Atom if I use this instead of planck: clj -J'-Dclojure.server.repl={:port,5555,:accept,clojure.core.server/repl}'

marciol20:04:16

@esciafardini there is a channel #chlorine-clover and @U3Y18N0UC (chlorine's author) is usually active there.

dharrigan14:04:35

What would be a good construct to use as a "fire-and-forget" action to happen on another thread, i.e., I want to execute this function (that has side effects), but I don't care if it works or not, just go off and do it in another thread (as the side effect can take a few seconds to action)

dharrigan14:04:06

right, my thoughts too, so I dont need to deref it ever...will the future be cleaned up once the function finishes?

alexmiller14:04:14

No one will refer to it

dharrigan14:04:16

perfecto, thank you 🙂

dharrigan15:04:08

Gotta say, that working with threads in this manner is so nice, Clojure makes it easy 🙂

Franco Gasperino17:04:21

Good morning. i have the a case where a spec returns false from a valid? call, but does not provide any context on what failed with an explain. I can provide details in a thread here if anyone is familiar with spec.

Franco Gasperino17:04:07

(s/def ::element-shape? 
  (s/and
   (s/map-of keyword? some? :count 7)
   (s/keys :req-un [::is-uuid?
                    ::is-name?
                    ::is-short-desc?
                    ::is-long-desc?
                    ::known-behavior?
                    ::is-config?])))

  (def mock-element
    {:id (str (random-uuid))
     :name "mock-element-1"
     :short-desc "Mock Input Element 1"
     :long-desc "This is a mock element which is intended to be used for event input consumption."
     :behavior :input
     :component :square
     :config {}})

(s/valid? ::element-shape? mock-element) 
  => false

 (s/explain ::element-shape? mock-element)
  =>
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :is-uuid?) spec: :eventflow.ui.flow.elements.repo/element-shape?
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :is-name?) spec: :eventflow.ui.flow.elements.repo/element-shape?
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :is-short-desc?) spec: :eventflow.ui.flow.elements.repo/element-shape?
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :is-long-desc?) spec: :eventflow.ui.flow.elements.repo/element-shape?
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :known-behavior?) spec: :eventflow.ui.flow.elements.repo/element-shape?
{:id "a76406e2-21b7-4cc1-b293-529e2a5bfef0", :name "mock-element-1", :short-desc "Mock Input Element 1", :long-desc "This is a mock element which is intended to be used for event input consumption.", :behavior :input, :component :square, :config {}} - failed: (contains? % :is-config?) spec: :eventflow.ui.flow.elements.repo/element-shape?
nil

Franco Gasperino17:04:55

omitted all of the individual defined functions, but have confirmed the work individually and are evaluated in the repl

seancorfield17:04:31

The explain is telling you that all those required keys are missing from the data — and it is correct.

Franco Gasperino17:04:01

oh crap! i missed the word "failed" in the output

Franco Gasperino17:04:40

too much coffee, or not enough

seancorfield17:04:41

Your s/keys spec says that :is-uuid? etc should be keys in the data.

seancorfield17:04:14

But the keys in your data are things like :id etc.

Franco Gasperino17:04:03

yes, you're correct. Im only about 2 hours into the :: namespace macro use - still wrapping my brain around it

Franco Gasperino17:04:40

let me back up my comprehension to a more basic point. If I have the following:

(s/def ::is-uuid?
  (s/and string? #(re-matches uuid-regex %)))

Franco Gasperino17:04:24

the :: expands to the current namespace + local binding, no?

dpsutton17:04:44

you can evaluate ::is-uuid? in your repl and you'll see exactly what the reader expands it to. What do you mean local binding though?

dpsutton17:04:20

qp=> ::uuid?
:dev.nocommit.qp/uuid?

Franco Gasperino17:04:31

thanks, that repl eval illustrated it for me

dpsutton17:04:55

it's been years, but i think i remember a situation where using a function as a spec would fail when calling valid? but not when calling explain. I think the docstring was updated to reflect this rather than allowing functions in the spec. Let me see if i can dig it up

dpsutton17:04:03

qp=> (require '[clojure.spec.alpha :as s])
nil
qp=> (s/def ::vector vector?)
:dev.nocommit.qp/vector
qp=> (s/valid? (s/coll-of any? :kind ::vector) [])
false
qp=> (s/explain (s/coll-of any? :kind ::vector) [])
Success!
nil
`

Franco Gasperino17:04:38

thanks for that. my issue was with poor reading comprehension

Franco Gasperino17:04:46

replied to my self above with some truncated but illustrative example

Michael Stokley17:04:51

do clojure developers prioritize having just one data model in a system? is the idea that we'd chose the 'correct' data model for each component considered harmful?

hiredman18:04:20

I think https://www.slideshare.net/mtnygard/architecture-without-an-end-state paints a good picture of the world clojure is designed to thrive in, a world where there is no single system of record, and no single model

hiredman18:04:07

if you google around there are videos of the talk that goes with those slides too

🙏 2
👀 2
sova-soars-the-sora22:04:06

I certainly try to achieve as much as possible with one canonical store of data, but just recently I invoked having 2 atoms instead of one for a dataset to ensure lookups would be fast if the datasets were to get enormously large. So in almost all cases I prefer one canonical representation / store of data, but some cases it's smarter or easier to flex provided you update them in a synchronous way. The potential to include bugs or glitches with multiple data representations is greater. There is the react adage "The View is a Function of the State" for the UI and the underlying representation in web programming -- and I think it applies on a lot of levels for computing and UI. Mainly that the methods playing on the data can be many, while the data ought be singular. Of course, it's not a hard-and-fast rule, and it makes sense to rely on whatever is best for the specific issues involved, but in general the fewer the stores of data, the fewer the inadvertent bugs due to out-of-sync'ness

practicalli-john06:04:15

A single data model is my default starting point. It helps keep the overall design of the code simple. If components of a system are very distinct or have little interaction with each other, then it seems more likely that they would be treated as individual sub-systems (or even spilt into their own system), so a specific data model would seem more appropriate. If components are highly integrated, then typically I would expect them to share a common data structure. These are my general guidelines, every system has its own constraints that need to be considered carefully.

🎯 3
raspasov12:04:07

Fewer (ideally one) data models/data structures, more functions that operate on them

💯 6
raspasov12:04:21

"It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures." - Alan J. Perlis https://clojure.org/about/rationale

3
3
Michael Stokley15:04:16

@U050KSS8M - fewer data models? or data structures?

Michael Stokley15:04:28

i never thought of clojure as preferring fewer data models

Michael Stokley16:04:03

i mean, sure, prefer as few as possible, but with data structures, clojure is quite opinionated and quite dogmatic - here are the handful you'll ever need. i never saw clojure as having a broad opinion about the different ways my application represents the concept of a domain entity

raspasov04:04:19

I think if having multiple data models is justifiable, sure; when we say “multiple data models” I understood it as having different data views/shapes onto the same model; perhaps I didn’t understand the question well enough, apologies.

sova-soars-the-sora01:04:11

Model is a highfalutin term with many abstract connotations. But in general I think you are both in agreement: creativity in how you access the data is unlimited / unbound / untethered, while the data itself is ideally only present once. (But consider the dish and the meal to be served.)

sova-soars-the-sora01:04:50

@U7EFFJG73 tell me more about the difference between data model and data structure. for example, does one data structure imply 1+ models on the data? And what does this distinction look like in code? ( if I have correctly observed the nuance)

phronmophobic17:04:21

It almost certainly depends on what you mean by data model, but I think having good, idiomatic support for namespacing makes it easier for multiple "data models" to coexist. I think that's one of the reasons clojure has found strong adoption in data processing.

💯 2
phronmophobic18:04:44

That doesn't mean you should create multiple data models if you can avoid it. It probably depends on the use case.