Fork me on GitHub
#beginners
<
2022-01-30
>
teodorlu08:01:57

I feel like there's things in clojure.set (https://clojure.github.io/clojure/clojure.set-api.html) that I don't understand. I'm using union, intersection and difference, but not the rest of the namespace. I want to understand what functions like index and join are meant to do. What's a rel? What's an xrel?

Antonio Bibiano10:01:19

Once somebody commented that clojure.set implements the api for an "in-memory database" and I think that at least explains functions like join and select

👍 1
teodorlu12:01:52

Yeah, I think I've seen something similar. Part of why I'm curious 😄

andy.fingerhut13:01:12

rel and xrel are relations, as in the mathemtical notion of a relation that relational databases are named for, but do not always precisely implement the mathematical notion of

teodorlu14:01:33

Sounds reasonable. How can I make use of that? Can I just improvise a relational database on top of clojure.set?

andy.fingerhut15:01:14

There is nothing built into Clojure that implements SQL, for example, so don't expect to find that.

andy.fingerhut15:01:05

There are functions in clojure.set for doing join, project, and clojure.core/filter can be used to implement a select operation. If you want to implement relational operations using those operators, they are there for you.

👍 1
andy.fingerhut15:01:37

If you want a different API, or you want persistence of data on disk or other storage, then I'd recommend instead using some other project that provides those features, of which there are many.

👍 1
teodorlu18:01:30

@U0NCTKEV8 thanks! Your example made it way more tangible.

popeye09:01:36

I have the data in the below format

(def input-data {"data" {
                        "days" {
                                "Jan"   346,
                                "Feb"   143,
                                "March" 416,
                                },
                        "date" {
                                "Jan"   "2023-02-21",
                                "Feb"   "2023-09-12",
                                "March" "2023-11-21"
                                }
                        }})

Ben Sless09:01:24

always same number of days and dates?

popeye09:01:02

what is the best way to get as

{"Jan" {
        "days" 346
        "date" "2023-02-21"
        }
 "Feb" {
        "days" 143
        "date" "2023-09-12"
        }}

Ben Sless09:01:56

Roughly something like:

(into
 {}
 (let [{:strs [days date]} (get input-data "data")]
   (for [[mon days] days]
     [mon {"days" days "date" (get date mon)}])))

atharva10:01:02

An alternate way, using map :

(into {} 
      (let [{:strs [days date]} (data "data")]
        (map (fn [[mon day] [mon timestamp]]
               {mon {"days" day "date" timestamp}}) 
             days date)))

Ben Sless10:01:14

@U030TQP563U you can't make an assumption regarding the order of days and date being the same, unfortunately

✔️ 1
atharva10:01:58

Sorry, my bad. Maps are unordered. This should fix the bug in my version:

(into {} 
      (let [{:strs [days date]} (get data "data")]
        (map (fn [[month day]]
               {month {"days" day "date" (get date month)}}) 
             days)))
By explicitly looking up date inside the map function, we can avoid the ordering problem.

popeye14:01:26

Thanks @UK0810AQ2 and @U030TQP563U for your time 🙂 I wrote below code which reduced any hard coded values, Let me know if any thoughts

(defn test-fn [input-data]
  (reduce (fn [a b]
            (assoc a (first b) (second b)))
          {}
          (second input-data)))

(println (apply merge-with vector (map test-fn (get data "data"))))

popeye12:01:48

How to apply merge-with into function for which is inside list

({:Jan 346, :Feb 143 :March 416} {:Jan "2023-02-21", :Feb "2023-09-12", :March "2023-11-21"}

noisesmith12:01:06

I think you want (apply merge-with into ...) - so literally what you had there :D

noisesmith12:01:38

no, that doesn't work because there's no data structure for the into

popeye12:01:00

I wanted to have data like {:Jan [ 346 "2020-02021].... so on

noisesmith12:01:52

then (apply merge-with vector ...) works - unless you have more than two maps

popeye12:01:22

awesome, i was trying with apply and into where I missed

noisesmith12:01:52

another option:

(cmd)user=> (defn group-keys [& maps]
               (into {}
                     (map (juxt identity (apply juxt maps)))
                     (->> maps
                          (mapcat keys)
                          (distinct))))
#'user/group-keys
(cmd)user=> (apply group-keys '({:Jan 346, :Feb 143 :March 416} {:Jan "2023-02-21", :Feb "2023-09-12", :March "2023-11-21"}))
{:Jan [346 "2023-02-21"], :Feb [143 "2023-09-12"], :March [416 "2023-11-21"]}

noisesmith12:01:14

juxt creates a vector by calling each function arg, and a map acts as a lookup function

popeye13:01:21

i think we hace direct function merge-with and vector which oes necessary thing 🙂

noisesmith13:01:30

when you call map on a hash-map, it calls seq on the hash-map, and seq for a hash-map returns two element vectors, key and value. technically a subtype of vector called map entry. my function above does the opposite, and feeds into with a two element vector, which becomes the key and value

noisesmith13:01:10

(ins)user=> (->> {:a 0} seq)
([:a 0])
(ins)user=> (->> {:a 0} seq first)
[:a 0]
(ins)user=> (->> {:a 0} seq first type)
clojure.lang.MapEntry
(ins)user=> (->> {:a 0} seq first vector?)
true

noisesmith13:01:55

when trying to understand a data structure, I find it useful to go into the repl and use -> or ->>, and use the arrow keys to retry and add stuff at the end

popeye14:01:29

Thanks @U051SS2EU, That helped 🙂

popeye17:01:44

I wrote below function which is working as expected ,

(defn test-fn [args]
  (fn [a]
    (do-some-thing-else (map reduc-fn args) a)))

(println ((test-fn (get data "data") ) rename-keys-sample))

popeye17:01:18

But is there any wau we can improve calling function ((test-fn (get data "data") ) rather than using ((......))

thinking-face 1
sova-soars-the-sora17:01:15

@popeyepwr can you tell me more about the function raname-keys-sample ? i notice you are invoking test-fn with arguments (get data "data") but i don't understand the motivation behind rks

popeye17:01:50

(def rename-keys-sample {"Jan" " montly jan"
                         "Feb"  "Yearly Feb"
                         "March" "Montly Match"})
i have writtern just test data here

popeye17:01:40

Ya even I can do it , I am leaning and want to make use of fn here

sova-soars-the-sora17:01:27

the implicit pass will require ((

sova-soars-the-sora17:01:52

explicit pass can do (

sova-soars-the-sora17:01:34

you could, for example, accept a in the test-fn arguments, rather than as an implicit pass. (defn test-fn [a args] ... and keep variadicity with allowing the function to be passed in at the top level (test-fn rename-sample-keys (get data "data")

sova-soars-the-sora17:01:34

Another approach is to pass in a map and use destructuring on the map coming in

Marcelo Formentão19:01:05

How can I test with a function that uses `*in*` for example:

(def input
  "Receive the stdin input lines at once"
  (str (slurp *in*)))
This code is blocking me from running tests since its waiting for a input, even using with-in-str isn't possible.

emccue21:01:29

add an “EOF” to your string maybe

emccue21:01:05

maybe (binding [in (StringReader…)]) would handle it?

👀 1
Marcelo Formentão12:01:10

thanks @U3JH98J4R, was able to solve it by defining defn instead of def for all things related to the in input, so it stopped blocking the stdin on tests

noisesmith14:01:15

@U031GTMB8QG was the issue that you used def instead of defn?

(ins)user=> (defn input []
  "Receive the stdin input lines at once"
  (str (slurp *in*)))
#'user/input
(ins)user=> (with-in-str "hello" (input))
"hello"

noisesmith14:01:22

@U3JH98J4R that's what with-in-str does

Marcelo Formentão14:01:04

the problem @U051SS2EU was that it stuck in the require waiting for the stdin while try to run the lein test command

noisesmith14:01:40

right, because all code in def runs to completion before require returns

noisesmith14:01:45

anything with any sort of state or side effect (including io) should be in defn and not def

Marcelo Formentão15:01:46

yep @U051SS2EU, first time working with clojure haha, thank you!

folcon19:01:12

Hmm, is declare inherently side-effecting? I have this puzzling case where this is happening:

(if (debug?) ;; <-- evaluates to true
  (do
    (require '[nrepl.cmdline :refer [-main] :rename {-main -nrepl-main}])
    (resolve 'nrepl.cmdline))
  (declare -nrepl-main))
#_#_=> Syntax error (IllegalStateException) compiling def at (src/example/core.clj:37:7).
-nrepl-main already refers to: #'nrepl.cmdline/-main in namespace: example.core
I was expecting the if to prevent declare from evaluating. Even this evaluates declare with the same syntax error...
(when false
    (declare -nrepl-main))

andy.fingerhut20:01:37

declare is just as side-effecting as def and defn are, and all of those are often strongly recommended not to be done inside of a function, but only at the top level of your code.

andy.fingerhut20:01:27

I see you are doing something where you explicitly want conditional requiring of namespaces, etc. so that is sometimes done for things like a user.clj file.

andy.fingerhut20:01:38

I do not know why the error is occurring for declare here.

teodorlu22:01:32

I've mostly used requiring-resolve for conditional requires, happy with the result. You pass it a symbol, and returns a var or throws. https://clojuredocs.org/clojure.core/requiring-resolve

folcon23:01:04

Ok thanks for the tips, sorry I didn't respond earlier, was at dinner 😃

folcon23:01:51

I'll check out requiring-resolves @U3X7174KS 😃... Yes, I'm trying to create dev / prod conditions and normally those work fine, but this was something I was just expecting to work @U0CMVHBL2. How is it normally done?

andy.fingerhut23:01:49

Notice that the same error occurs even if there is an empty part of the if with no declare. The error is not because declare is there.

folcon23:01:07

Is that the case?

folcon23:01:35

I just pasted this into another namespace to check and if I delete the second if branch, it doesn't happen.

folcon23:01:09

Let me try with placing an explicit nil there

folcon23:01:42

This appears to work if loaded and re-evaluated:

(if true ;; <-- evaluates to true
  (do
    (require '[nrepl.cmdline :refer [-main] :rename {-main -nrepl-main}])
    (resolve 'nrepl.cmdline)
    nil) #_(declare -nrepl-main))

folcon23:01:15

What am I missing @U0CMVHBL2? 😃

andy.fingerhut23:01:05

I am probably missing something, in that I tried it with no nrepl.cmdline in my classpath, so my bad there, sorry.

andy.fingerhut23:01:38

The error message was at least similar, but perhaps it wasn't identical. Let me check.

andy.fingerhut23:01:33

Neither of these expressions give an error with Clojure 1.10.1:

$ clj
Clojure 1.10.1
user=> (if true (do nil) (declare -nrepl-main))
nil
user=> (if false (do nil) (declare -nrepl-main))
#'user/-nrepl-main

andy.fingerhut23:01:49

That suggests to me that it isn't the declare that is causing your error, unless our environments differ substantially in a way I'm not guessing.

brendnz00:01:16

Perhaps you'll forgive me for guessing, but I guess it may be due to the compiler doing things with certain forms before other things i.e. testing the requires and declares before the runtime runs the ifs.

hiredman04:01:04

It is due to the declare

hiredman04:01:40

declare and def both have compile time effects (the same compile time effect, interning a var)

hiredman04:01:11

You can think of that as happening at macro expansion time

hiredman04:01:45

And those happen regardless of any runtime conditionals (like your if)

hiredman04:01:21

What is happening is the declares effect (interning nrepl-main in *ns* ) is happening when the code is compiled, then when the code is run, it is trying to do all the requiring and rename stuff. And that throws an error because a var with that name already exists because of the compile time effect of the declare

folcon15:02:56

Thank you, so other than using requiring-resolves is there another way to do this sort of conditional require?

Michael Lan20:01:41

What is a simple (hopefully free) way i can get a Ring server up and running on a domain I own?

phronmophobic20:01:17

To run a ring server, you need a computer connected to the internet somewhere to run it. Not exactly sure what you're trying to host, but here some options: • aws has a https://aws.amazon.com/premiumsupport/knowledge-center/what-is-free-tier/. As they explain, there's 1) services that have free trials, 2) 12 months free, and 3) always free. If you're looking at always free, then your best bet is https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=tier%23always-free&amp;awsf.Free%20Tier%20Categories=categories%23compute. I'm not sure it's ring compatible, but there is a clojure library targeting aws lambda called https://github.com/FieryCod/holy-lambda. You'll still be limited by usage, but it should work for personal usage. • Both digital ocean and AWS have servers that run around $5/mo. I've used both, but tend to prefer digital ocean for hobby projects • You can run a website using static hosting very cheaply, on the order of a few cents a month or less. The catch is that if your app requires persistent storage (eg. database), then you'll need find a cheap storage solution. There are cheap/free-ish storage solutions, but it depends on your use case. • https://pages.github.com/ can be a good option for free hosting, but this is closer to a static option rather than a ring server • If you have an extra computer or raspberry pi, then you can just run the server there. There's some setup to make it available on the public internet, but it's very doable. I'd be happy to expand on any of the options above if they sound interesting.

Michael Lan21:01:54

Thanks! I tried using aws in the past but I found it to be a bit confusing to navigate and manage; would digital ocean be any easier? I might choose the last option but there’s a router in the way and I’ve never figured out how to make if public

phronmophobic21:01:01

> would digital ocean be any easier It kind of depends on how familiar you are with linux. Digital Ocean has fewer services and options, so it might be a little easier to manage > there’s a router in the way and I’ve never figured out how to make if public Just about every router has port forwarding. As long as you have admin access to the router, then you should be able to find "port forwarding" somewhere in the options.

Fredrik01:01:48

I'm using Digtal Ocean to run a hobby web server, with NGINX as a proxy in front, if you message me I can try help walk you through it :)

Michael Lan04:01:59

Thanks! My app will need a database though, and I'm not too sure how that affects my options...

Fredrik05:01:22

I don't know exactly what kind of setup you need, but if you're able to run it locally on your machine, it will very likely run just fine on a rented server

practicalli-johnny09:01:21

You can run a Clojure project on Heroku for free, even including a postgresql database The free version puts apps to sleep after an hour of inactivity, automatically waking them up again on activity. https://devcenter.heroku.com/articles/getting-started-with-clojure

practicalli-johnny09:01:09

This is an example using Circle CI and Heroku to deploy a Clojure CLI (deps.edn) project https://practical.li/clojure-web-services/projects/banking-on-clojure/deployment-via-ci.html