Fork me on GitHub
#beginners
<
2020-12-03
>
st3fan00:12:25

I have not completely figured out how to structure apps yet .. I have a thing now that is partially using integrant and another using mount .. both are interesting

st3fan00:12:11

The thing I have not figured out yet, or at least not have an opinion about, is whether an app should have global state or not ..

st3fan00:12:35

In my Go code I heavily avoid it .. start your data/tree/state from main and pass it around basically

noisesmith00:12:25

right, that's what I do in nontrivial clojure apps (and all libraries) as well

noisesmith00:12:46

perhaps for dev tooling purposes putting the running state in a def

noisesmith00:12:14

but using globals for app data in cljs is pretty common

dpsutton00:12:39

especially as a beginner, i think some of those state management apps can really hinder making cool things as people stress about the best way to inject something they never are gonna fake either way.

☝️ 3
Jack Arrington01:12:52

Why is it that (= [1 2 3] (seq [1 2 3])) evals to true despite the differing types of the arguments but

(defrecord Foo [a b])
(= (Foo. 1 2) {:a 1, :b 2})
evals to false (which from my understanding, is only because the types are different)?

dpsutton01:12:54

check out (doc =) and the docstring of (doc defrecord)

dpsutton01:12:17

that second one is quite long with some technical bits but the relevant bits are > In addition, defrecord will define type-and-value-based =, and will defined Java .hashCode and .equals consistent with the contract for java.util.Map.

dpsutton01:12:53

and a lovely bit of documentation by andy fingerhut with tons of information, caveats, examples, etc. https://clojure.org/guides/equality

dpsutton01:12:59

all of that kind of just describes the conundrum that you're talking about. Clojure is coded around a few core abstractions and the types of collections is not emphasized. depending on operations, you can end up with very different types. and when checking for equality its just the same elements in the same order. records produced from defrecord is an explicit step that imbues values with a type and that type is consequently honored when checking for equality.

dpsutton01:12:14

records is a way to add add protocol implementations to values. because these values could behave in drastically different ways under the same function, they include the type information in the equality check

st3fan02:12:56

Me a month ago: threading macros look dumb i’ll avoid them. Me today: THREAD ALL THE THINGS.

👍 9
seancorfield03:12:53

Someone's been watching the Mandalorian 🙂

practicalli-johnny10:12:16

Slack needs a Mandalorian emoji :)

yubrshen04:12:18

When running

(run-jetty (wrap-defaults app site-defaults) {:port (:port env)})
where the :port has value 80. I got the following error:
1. Unhandled java.net.SocketException
   Permission denied

                  Net.java:   -2  
                  Net.java:  455  
                  Net.java:  447  
ServerSocketChannelImpl.java:  227  sun.nio.ch.ServerSocketChannelImpl/bind
  ServerSocketAdaptor.java:   80  sun.nio.ch.ServerSocketAdaptor/bind
      ServerConnector.java:  345  org.eclipse.jetty.server.ServerConnector/openAcceptChannel
      ServerConnector.java:  310  org.eclipse.jetty.server.ServerConnector/open
AbstractNetworkConnector.java:   80  org.eclipse.jetty.server.AbstractNetworkConnector/doStart
      ServerConnector.java:  234  org.eclipse.jetty.server.ServerConnector/doStart
    AbstractLifeCycle.java:   72  org.eclipse.jetty.util.component.AbstractLifeCycle/start
               Server.java:  386  org.eclipse.jetty.server.Server/doStart
    AbstractLifeCycle.java:   72  org.eclipse.jetty.util.component.AbstractLifeCycle/start
                 jetty.clj:  172  ring.adapter.jetty/run-jetty
I tried if I run the app with sudo then there is no problem. It looks like that I need some privilege to start the web server at the port 80? How? Thanks for your help!

solf04:12:21

You answered it yourself. You need root permissions to open any port < 1024

🙏 3
solf04:12:20

On the web, you'll often find that people use 8000 or 8080 as default ports for non-production web servers, pretty much because they look like 80, but are non-privileged

yubrshen04:12:29

My problem is solved by change the port to 8443 then it works. The solution was found at https://stackoverflow.com/questions/42014227/permission-denied-errors-when-listening-on-https-ports-with-ring-on-heroku "You are getting permission denied because it is trying to bind to the default SSL port *443* which you need *sudo* access for ports less than *1024"*

Fredrik Holmqvist12:12:54

Quering logic How would I turn a list of predicates into a filter function, reducing the list of predicates with and/or?

runQuery :: List (Device -> Bool) -> List Device -> List Device
I've tried every-pred but I don't know how to convert my list into just arguments (I see the irony of having this problem in a Lisp). Either way, I need to be able to discern between "anding" and "oring" these predicates. Example desired use:
"Give me all devices whose name are 'Example' OR whose serial number starts with '123'".

(run-query partially-applied-predicate-functions list-of-devices)
EDIT: I'm primarily looking for an idiomatic way of performing such logic, however that looks in Clojure.

chrisblom12:12:42

(fn [predicates]
     (let [matches-every (apply every-pred predicates)]
       (fn [devices]
         (filter matches-every devices))))

Fredrik Holmqvist12:12:47

That makes a lot of sense. Thank you for your time Chris, appreciated 🙂

chrisblom12:12:37

with every-pred it does "anding", you can use some-fn for "oring"

3
Michaël Salihi17:12:22

Hi everybody! I just update my todo backend repo on dedicated branch with the Juxt Clip library (Component/Integrant alternative): https://github.com/PrestanceDesign/todo-backend-clojure-reitit/tree/clip If some are interested to see how it is configured, how it works... 😉

👍 3
dilzeem18:12:21

Hi all, this is a clojurescript questions. I am able to run the following code successfully with the data being read and logging to the console. However I want to be able to use the data and eventually plot it. What is the best way to store this data so that I can use it?

(def data-local
  (-> d3
      (.csv "timeseries.csv" )
      (.then #(js/console.log %))
      (.catch #(js/console.log "Error loading file"))))

noisesmith18:12:04

store for how long, and for who to access it?

mjw18:12:38

console.log returns undefined, so data-local is a promise to undefined. You can log as a side-effect, but your .then still needs to return the data being logged in order to assign it to data-local.

noisesmith18:12:26

if you just mean inside the app until the user exits, you can replace the console.log with something that stores the data, or provides it to some callback / channel

noisesmith18:12:09

@matt.wistrand what console.log returns doesn't even matter for "data-local", it just gets the return value of .then

mjw18:12:44

In this case, the return value of .then is the result of calling console.log, which is undefined.

noisesmith18:12:11

I thought .then returned immediately...

noisesmith18:12:01

eg. how would chaining .then calls work if .then returned the value that the callback returns?

mjw18:12:17

I’m not being precise; it returns a promise to the result of executing its callback.

noisesmith18:12:26

I'm reading the docs to double check, and they are saying .then returns a new promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

noisesmith18:12:07

right, what I'm saying that even that doesn't get what @URLR96GES wants - he wants the value to end up in the data-local def, and you need something more complicated to make that happen

mjw18:12:31

Right. The .then would either need to be removed and console.log not called as part of the def (preferable), or the .then would have to call console.log and then return the data (less preferable).

noisesmith18:12:22

returning the data from the promise callback is still only punting the root issue - it's still in a promise that way

noisesmith18:12:10

there needs to be some explicit handing of the data out of "promise world" into other code, or lifting of the other code to be called in the promise callback

dilzeem19:12:46

I don't really need the console.log I was just having it there to see if it was getting the csv file.

noisesmith19:12:24

right, that was never the real issue

dilzeem18:12:50

data-local is a promise and wanted it to be the data itself

dilzeem18:12:41

I believe I am not really understanding how promises work. but I think this will help. https://clojurescript.org/guides/promise-interop

noisesmith18:12:42

that's just not how using promises works - you need to call the code that wants to use the data inside the .then callback, or store the value for later, or put it on a channel

dilzeem18:12:46

okay thanks. I think I get it. I need to have a def somewhere inside the then block that will store the value for me to use later.

noisesmith18:12:34

that's one way to do it, but then how does your other code decide when to try to use that def?

dilzeem18:12:47

hmm is there a way for me to have data-local contain the actual data?

noisesmith19:12:33

this is a common problem with async execution mechanisms (not just with js), the common choice is to either "lift" all code that wants data produced async into async calls (put it in the function arg to .then in this case), or to use some global data store (in cljs an atom perhaps?) so that other code initiated by some timeout or user action can see it (if it's ready)

noisesmith19:12:21

consider that all it takes to do that lift is to have a function that takes the data as an argument, that's a totally normal way to write clojure code already

dilzeem19:12:43

Hmm interesting. Thanks for the explanation. I think I have clearer understanding now.

dilzeem19:12:08

For anyone else who wants to solidify what was discussed above, this was useful: https://martinklepsch.org/posts/working-with-promises-in-clojurescript-repls.html

daboone7222:12:43

I'm struggling to get a remote repl working between Linux running the remote and IntelliJ connecting. Anyone got any pointers?

daboone7222:12:49

apt-get install leiningen; export LEIN_REPL_HOST=172.16.16.34; lein repl :headless :port 7888.

sova-soars-the-sora23:12:25

I found out the random dude trying to access my box was from Russia so I returned an image of a bear on a bicycle xD

sova-soars-the-sora23:12:50

maybe better for off-topic x]

leif23:12:25

Okay, silly question, does -> mean anything special when attached to the front of a deftype?

dpsutton23:12:13

> Given (deftype TypeName ...), a factory function called ->TypeName will be defined, taking positional parameters for the fields

😮 3
dpsutton23:12:32

(from (doc deftype))

leif23:12:50

Ah, okay, thanks.

leif23:12:08

(I was only finding stuff on the threading macro from google. Sorry about the silly-ness. 🙂 )

dpsutton23:12:20

no worries at all 🙂 its the entire point of the channel

leif23:12:02

lol, fair. 🙂

dpsutton23:12:46

you can check out (source deftype) and then (source clojure.core/build-positional-factory) if you want to see it

dpsutton23:12:12

(let [fn-name (symbol (str '-> nom)) and then later (defn ~fn-name`

leif23:12:39

(In the same way that a . at the end means new)

leif23:12:32

Like, if I do:

(deftype Foo [])
(->Foo)

bronsa23:12:42

nothing special, but defrecord autogenerates a function called ->Ctor for a defrecord called Ctor

bronsa23:12:53

which is a convenience for Ctor.

bronsa23:12:07

it also creates a function called map->Ctor which allows you to create a Ctor by using a named map

bronsa23:12:43

deftype only does ->Ctor, but not map->Ctor

leif23:12:53

Oh odd, I'm actually not seeing in the docs what the difference between Ctor. and ->Ctor is.

bronsa23:12:19

->Ctor is a function

bronsa23:12:38

Ctor. is a syntax sugar for new Ctor

leif23:12:46

OOOHH..>That makes sense.

bronsa23:12:59

(so the first one is a value, the other one isn't)

bronsa23:12:22

to be precise, Ctor. isn't a thing, only (Ctor. ..)

leif23:12:35

RIght, because its a macro

leif23:12:43

reader macro*

leif23:12:52

Oh, its not a reader macro?

leif23:12:01

That's interesting.

bronsa23:12:20

it's... syntax sugar that's applied by the macroexpander

bronsa23:12:48

it's a bit of its own thing, similar to how Foo/bar can expand to . Foo bar

bronsa23:12:56

definitely not a reader macro

bronsa23:12:59

it's not applied at read time

leif23:12:21

Ah, okay.

leif23:12:37

So it's more like format-id in the racket world.

leif23:12:01

(Except that users don't have access to making new ones...probably...)

leif23:12:14

Still, cool, thanks. 🙂

bronsa23:12:03

i'm not familiar with format-id to confirm

leif23:12:10

Ah, yes, I was curious, thanks for sharing.

leif23:12:51

That looks a lot like if format-id was baked into the racket macro system, rather than being user programmable. 🙂

bronsa23:12:52

and yes, you're right in assuming that the user doesn't have access to this

leif23:12:13

Ya...programs with user defined datam->syntax would be awfully confusing without some kind of hygiene system.

leif23:12:28

It'd be pretty hard to predict what identifiers did what. 🙂

leif23:12:10

(IIRC, the clojure macro system doesn't have a hygiene system per se, more just sugar for using namespaced qualified identifiers.)

bronsa23:12:30

and autogensym

leif23:12:56

That to.

leif23:12:35

Anyway, thanks again for the feedback.