Fork me on GitHub
#beginners
<
2020-03-18
>
mark54000:03:44

@streetmapp I got the impression that you're wanting to create a static web app and only refresh pages occasionally, is that right? If so, people may be able to recommend Clojure server-side template libraries. Or did you want to create a single-page dynamic app? In that case @noisesmith is asking which ClojureScript framework you're using.

streetmapp00:03:58

@mark540 i'm really starting from nothing. It seemed like a simple project to get started with. I just have the fact I'll have data most likely generated in json form and want to have it end up represented in a table. I guess my end state would be to ultimately have things dynamic. But don't have much details outside of that just yet

mark54000:03:30

If you're just learning I recommend starting with server-side HTML generation since there is much less to learn to get started. I have not been doing that in Clojure but others will have recommendations. I know that @seancorfield has recommended Selmer in the past.

seancorfield00:03:13

It might even be worth considering a command line process to read the JSON and generate HTML which you could run periodically (I'd probably still use Selmer for that) so you wouldn't even need a "web app" -- just somewhere to put your generated HTML and serve it.

vishal.gautam00:03:41

Hello, has anyone played with crux and rocksdb?

seancorfield01:03:20

Nope but there's a #crux channel full of folks who have. No idea about rocksdb.

clayton.marshall1201:03:51

I have a question about fully qualified keywords, and when they might be used. I understand that ::keyword translates into something like :my-namespace/keyword, but I'm confused why that's useful. I've found a blog post (https://kotka.de/blog/2010/05/Did_you_know_III.html) that explains it a bit more, but the example use case it gives sounds very uncommon. From the blog post:

We all know and love :keywords. They are a often used as keys in maps and their ability to be called like a function on maps makes for some very concise code. However what happens if two libraries modify the same map? On the same key? Then we are in trouble, because one overwrites the other. To remedy this issue, Clojure provides (similar to Symbols) namespace-qualified keywords. Simply add a double-colon in front and the keyword will be qualified with the current namespace.
Does anyone know of a more common use case for fully qualified keywords? I'm new to clojure so maybe this use case is more common than it seems, but I wasn't very satisfied with this use case. Any help is appreciated 🙂

seancorfield01:03:19

That's a very old blog post. Things have changed a lot around qualified keywords, with the introduction of clojure.spec (which uses them heavily).

clayton.marshall1202:03:46

thanks! will take a look at clojure.spec

andy.fingerhut02:03:42

You can of course avoid using ::keyword syntax, instead using :full.qualifer.typed.out/my-kw , or :namespace-alias/my-kw after doing a (:require [some.full.namespace.name :as namespace-alias]

andy.fingerhut02:03:54

Those are just different syntax for the same resulting qualified keyword, but do not themselves demonstrate why you would want to use them.

erp1202:03:54

Spec is definitely the dominate use case, and I think the use case mentioned in the blog post is still important, even if it is rare. If it does happen, it isn't the easiest thing to debug. I get particularly paranoid on the rare occasion that important information gets put into an object's metadata . Think of all generic attribute names that one might want to put into metadata (`:version`, :timestamp, etc). If you use qualified keywords, you won't get mixed up with the metadata from libraries (or maybe, if you application is large, your other code).

seancorfield02:03:50

Another use case I like to mention @clayton.marshall12 (sorry for the delay, I was eating dinner): in a database, id often exists in multiple tables as a primary key but its meaning is determined by the combination of the table and the PK. It's natural in Clojure to use qualified names for hash maps that represent rows from database tables, so you might have :product/id, :product/name, and :invoice/id, :person/id, :person/name and so on.

seancorfield02:03:12

That's why next.jdbc defaults to qualified keys for columns, using the table name as the qualifier -- and that works with joins as well.

glfinn8318:03:02

What is a good way to manage enviroment variables in a project (something similar to application.conf in Spring for instance)?

thom70418:03:44

Aero is nice (although slightly opinionated about environment variables)

christian.gonzalez18:03:34

I would use stuart sierra component to load up environment variables @glfinn83

glfinn8312:03:53

Hey @christian.gonzalez, could you share some code on how you do this? I've found tutorials on using mount and cprop

christian.gonzalez17:03:26

Hey there, this is the library I mentioned https://github.com/stuartsierra/component I would consider creating a config map that retrieves all the environment variables for your system (there can be different configs per environment, like dev, staging, prd) then passing it to your system

(defn example-system [config-options]
  (let [{:keys [host port]} config-options]
    (-> (component/system-map
          :config-options config-options // env vars passed to components here
          :db (new-database host port)
          :sched (new-scheduler)
          :app (example-component config-options))
        (component/system-using
          {:app {:database  :db
                 :scheduler :sched}}))))

christian.gonzalez17:03:48

It’s a bit of a learning curve compared to mount/cprop, but I think it’s actually worth it

mbarillier18:03:55

this is likely in 4clojure or a koan or something, but: what's the idiomatic way to transform two vectors into a map? e.g. (fn [:a :b :c] ["foo" "bar" "baz"]) => {:a "foo" :b "bar" :c "baz"}?

christian.gonzalez18:03:19

a component that defines the app config, then the start lifecycle loads up the env vars, but there are a few other tools for that if anyone else has ideas

glfinn8318:03:04

@christian.gonzalez Cheers

scotto19:03:38

So, I have a huge series of numbers scores ranging from about 18 to about 32. I want to find the first score of 32. I’m not even sure if there /is/ a 32. There are probably a lot on the low end, and very few if any on the top end. So, I can do something like this:

(take 10 (filter #( > % 20) scores))
=> (21 21 21 21 21 22 21 21 21 21)
(max scores) seems to take forever and not produce any output. This seems to take about a minute:
(take 1 (filter #( > % 26) scores))
=> (27)
This might reach its limit, and took about 5 minutes to run:
(take 1 (filter #( > % 27) scores))   ; prepare to wait about 5 minutes!
=> (28)
Is there a way to work on this dataset that I’m not thinking of? scores is a defined list and there’s some underlying code if that would be helpful.

scotto19:03:38

So, I have a huge series of numbers scores ranging from about 18 to about 32. I want to find the first score of 32. I’m not even sure if there /is/ a 32. There are probably a lot on the low end, and very few if any on the top end. So, I can do something like this:

(take 10 (filter #( > % 20) scores))
=> (21 21 21 21 21 22 21 21 21 21)
(max scores) seems to take forever and not produce any output. This seems to take about a minute:
(take 1 (filter #( > % 26) scores))
=> (27)
This might reach its limit, and took about 5 minutes to run:
(take 1 (filter #( > % 27) scores))   ; prepare to wait about 5 minutes!
=> (28)
Is there a way to work on this dataset that I’m not thinking of? scores is a defined list and there’s some underlying code if that would be helpful.

scotto19:03:21

Cool, that’s what I was looking for!

aisamu19:03:54

And max takes the arguments separately. You're providing the whole collection as one argument. (apply max scores), https://clojuredocs.org/clojure.core/max

hindol.adhya19:03:10

apply max on a large collection should be avoided. (reduce max scores) will do the same thing more efficiently.

scotto19:03:16

This seems to be taking a very long time. I just did a test to see if there was a score greater than 5. (All of them are, I think.)

(some #(> 5 %) scores)
Shouldn’t that find the first one and say “true”?

christian.gonzalez19:03:28

shouldn’t it be #(< 5 %)?

christian.gonzalez19:03:45

for greater than five

scotto19:03:18

Yes, that is true. 😕

scotto19:03:46

Sorry reverse-backwards notation got to me. 😄

scotto19:03:15

I’m trying (reduce max scores) right now. Seems to be taking a while. One of those “why don’t you go make tea” requests of my poor MacBook.

christian.gonzalez19:03:11

there are definitely ways to speed that up, but it depends what you’re trying to accomplish here

christian.gonzalez19:03:16

one thing you can do is eliminate numeric boxing to speed things up

christian.gonzalez19:03:54

you could also try parallelizing by using the clojure reducers library… but again depends how fast you want to make it

scotto19:03:14

Well, I’ve been going a half-hour on (reduce max scores) . Maybe I just need to be patient on such a huge dataset.

aisamu19:03:31

That's indeed a lot. How many items? Is the score part of a larger map that you're accessing while traversing? (e.g. (:score student))

thom70419:03:24

How much data is this and where does it come from?

thom70420:03:06

By the time you’ve read it into a Clojure data structure, you could have already answered the question is all. But if you are only reading it in a lazy sequence then unfortunately your best bet is O(n) which already sounds slow. The chances are you can answer the question more easily (and indeed more eagerly) closer to the source of the numbers.

hindol.adhya20:03:38

If the data is stored in chunks, you can process all the chunks independently and retain some statistics like min/max. Trying to load too much into memory may slow everything down.

hindol.adhya20:03:08

The range of the numbers is small (18 to about 32) but there are a lot many numbers. Maybe create a histogram (`frequencies` in Clojure)?

scotto20:03:42

Well, I think it might be best if I go back to some of my original functions. I started out with a combinatorial of 13 letters of the alphabet. (So, that’s a long list.) I used each combination of letters to generate a score by a second search of its appearance frequency in a set of words. There are certainly much more efficient ways of doing this.

scotto20:03:21

I think, instead of making a big huge list in the first place. I can filter each letter-combination, and generate a much smaller list. So, basically, I fix the code that I started with, and don’t make such a big list!

thom70420:03:33

Okay so you are much better off streaming through the source text and building up a hash table of frequencies as you go, and stopping at 32.

thom70420:03:10

And that’s eminently parallelisable.

hindol.adhya20:03:22

So, if there is a space-time tradeoff, here it makes sense to optimize for space first, since such huge amount of data will slow it down anyway.

scotto20:03:10

OK, thanks for your advice, folks. Appreciated!

isaacballone20:03:07

I have a js file that exports only a single function, and I want to add it to my cljs project. So I made a folder externs/myfunc.js. Then I went into my project.clj to add :externs ["externs/myfunc.js"] under my compiler section of cljsbuild. So in theory Google Closure won't overwrite my function name when doing its magic. I'm using Intellij + Cursive and my IDE can see js/MyFunc is added to the namespace, but when I restart my code, it can't find it. So I guess my question is: am I supposed to include the js file in my HTML file, or is the cljs compiler supposed to pull in my externs file automagically?

noisesmith20:03:30

I haven't used cljs in a while but I thought the externs file should just include declarations and the actual code should be elsewhere

isaacballone20:03:32

You know now that you mention that, I think I remember seeing some examples only having empty variables

noisesmith20:03:55

right, that's how externs files work, you need to put the js file elsewhere so it ends up in your app

isaacballone20:03:55

I'm going to try including it in my HTML and call it good

noisesmith20:03:51

yeah, it should be sourced in the html and then the externs file makes sure references to those functions don't get rewritten when the cljs is optimized

isaacballone20:03:15

Alright, so https://lispcast.com/clojurescript-externs/ showed an alternative solution so you can be lazy and use a library as an externs 🙂. Then you just put it :externs ["resources/public/js/yourfunc.js"] and disable any warnings with :closure-warnings {:externs-validation :off} . Now I can just include it in the header

noisesmith20:03:07

the link I posted above shows how to auto-generate externs, and also how to write them

noisesmith20:03:27

I'd think auto-generation is better than suppressing a warning if it works

timur05821:03:02

What is the correct type hint for destructured map argument?

(defn type-hinted-fn [^MyRecord {:keys [a b c] :as ^MyRecord r}]
  r)
Is the first placement of ^MyRecord correct or the second one, or both are equivalent?

alexmiller21:03:04

latter I would think

alexmiller21:03:12

seems like that works

user=> (map meta (destructure '[{:keys [a b c] :as ^MyRecord r} x]))
(nil nil nil nil {:tag MyRecord} nil nil nil nil nil nil nil)

alexmiller21:03:07

it's really the local binding symbols that carry the meta

alexmiller21:03:49

I guess maybe a better question would be, why do you need to type hint the record?

alexmiller21:03:32

you don't see stuff like this very often as there are not many cases that could benefit from it

timur05807:03:47

I am making a very high-volume real-time financial data service, and from profiling I think I am seeing assoc performance increase if I sprinkle type hints in a couple of critical places. I am not 100% sure how record field access is optimized, but I like to imagine that with type hints the compiler chooses a more "static" field access path. I might be imagining things though — will continue profiling today.

alexmiller12:03:12

I think you’re imagining things in this case