Fork me on GitHub
#beginners
<
2020-01-19
>
matheusashton00:01:45

Hello everyone! Iā€™m starting to work with clojure, and Iā€™m having some questions about project organization, I have a project with compojure and monger, how to you usually organize the files? How do I handle the database connection among the application? Do I have to connect on every request or do I connect on application start and reuse the connection?

seancorfield00:01:57

@ When you're getting started, that's the simplest way (connect on each request) but for production apps you need to set up a connection, or a connection pool, at startup and reuse that as needed.

seancorfield00:01:57

Component and Integrant are libraries that are intended for managing stateful things with start/stop lifecycles like connections, pools, etc.

matheusashton01:01:55

There is some real application that I can check the code to see the best practices? Or some book that I can look into?

seancorfield01:01:33

Clojure Applied is probably a good book for some "real world" advice.

seancorfield01:01:54

(although it over-emphasizes records -- the author would rely less on them now)

seancorfield01:01:07

The Clojure Cookbook might also be interesting to you.

seancorfield01:01:06

You could look at the source of http://cljdoc.org -- I hear that's quite well-structured (but I haven't looked at it myself). Nearly all the open source Clojure is libraries -- the apps are nearly all private.

matheusashton01:01:19

Thank you very much Sean!

seancorfield01:01:54

Clojure isn't very opinionated about structure, so you'll get lots of different suggestions about that of thing.

matheusashton01:01:22

You said that the author would rely less on records now, what is the best practice today for modeling entities?

seancorfield02:01:55

Plain ol' hash maps.

seancorfield02:01:16

Data is the API. Entities are "just" data.

kelveden12:01:54

Following on from Sean's comment about Integrant, you might also be interested in https://github.com/duct-framework/duct. This is a framework built on top of Integrant (by the same author) that pre-rolls a lot of common use scenarios into reusable modules - database connections and pooling being one. I'd suggest sticking to rolling your own for now though as that'll help you to appreciate what Duct adds on top more. I've worked on production systems using vanilla Integrant (+ aero for config) and systems using Duct and, whilst there's definitely a learning curve for Duct, I've found it worth the effort.

carl.waerner00:01:48

Hello you all šŸ™‚ I am JUST starting with clojure and i'm just doing a simple test to try and write to a .txt file: (defn write-to-file [] (`slurp "file.txt" "test123"))` (`write-to-file)` I get the error

(slurp "file.txt" "test123"))
       ^--- Use of undeclared Var tallex.time-dive/slurp
I'm probably missing something really obvious?

seancorfield00:01:08

@carl.waerner How are you running that code? In a REPL? Where did tallex.time-dive come into that code?

carl.waerner01:01:42

Hello Sean! @seancorfield I just saw that... sorry. I am following a tutorial and that's where the tallex.time-dive came in. I tried the same in the REPL and it gave the same error however!

seancorfield01:01:11

In a fresh REPL:

$ clj
Clojure 1.10.1
user=> (defn write-to-file []
         (slurp "file.txt" "test123"))
#'user/write-to-file
user=> (write-to-file)
WARNING: (slurp f enc) is deprecated, use (slurp f :encoding enc).
Execution error (FileNotFoundException) at .FileInputStream/open0 (FileInputStream.java:-2).
file.txt (No such file or directory)
user=>

seancorfield01:01:31

clojure.core/slurp reads from a file.

seancorfield01:01:51

Do you have a link to this tutorial? Perhaps it's wrong/outdated?

carl.waerner01:01:14

It's this one. But it's nothing wrong with the tutorial šŸ™‚ I just wanted to try out stuff on my own

carl.waerner01:01:42

Even if I do this one in a fresh REPL. I get "use of undeclared Var"

seancorfield01:01:10

Ah, ClojureScript

seancorfield01:01:53

slurp and spit are Clojure functions -- I don't think they exist in ClojureScript.

carl.waerner01:01:26

I see...! My idea was to write to a .txt file to use as a super simple database šŸ™‚

seancorfield01:01:22

ClojureScript is intended to run in a browser or on Node.js. Clojure is intended to run on the JVM -- lots more file system and database stuff available there.

seancorfield01:01:59

Clojure for the Brave and True is a good place to start, if you're really just getting started. Otherwise consider Living Clojure as a beginners' book.

seancorfield01:01:12

Or Getting Clojure, another good book to get started with.

carl.waerner22:01:37

Thank you, appreciate it! šŸ™‚

derpocious01:01:46

hello, I am trying to take this code and do something similar in clojurescript: https://github.com/joinr/spectest/blob/master/src/spectest/core.clj

derpocious01:01:44

but I have errors that clj-kondo can't resolve the symbols.

derpocious01:01:08

Also, I am not totally sure if I am requiring the correct libraries

derpocious01:01:54

@seancorfield I took your advice of trying to use spec instead of schema, but I would like to try to use the orchestra "defn-spec"

seancorfield01:01:26

I've never looked at Orchestra.

seancorfield01:01:57

(and I don't do anything with ClojureScript)

derpocious01:01:08

I had asked about this on reddit, and someone wrote this for me: https://github.com/joinr/spectest/blob/master/src/spectest/core.clj

derpocious01:01:00

It seems to me like a nice way to putting the spec right into the function definition

seancorfield02:01:17

I get the impression some sort of combined spec/defn thing is going to be part of Spec 2, based on what Alex has been saying. Rich is deep in thought about function specs for Spec 2. So I'm waiting until that drops since it'll be standard and built-in.

seancorfield02:01:28

In the meantime, I'm sticking to plain Spec 1.

mattias50408:01:32

So, random morning thought. TypeScript, Python types on the rise... specs in Clojure. Is it a trend? For now? Will it pass? What does it say, if anything? :face_with_monocle:

andy.fingerhut08:01:26

That many developers are willing to spend a lot of time designing and implementing various kinds of type systems, both static and dynamic, for languages that originally did not have them?

mattias50410:01:50

Yes, seems like it. In the year 40000, a family of genes will be discovered that innately drives us to create ever more complex type systems. Until a prophet comes and declares that no, there is another way. And then the cycle repeats, forever.

mattias50410:01:25

(Compare this to the human tendency to sort, categorize and arrange by <all the attributes>. Type systems are just a more advanced symptom. šŸ˜„)

jackpark15:01:24

Still, there are final causes for categorization :male-farmer:

borkdude08:01:14

@derpocious You can use a configuration to make errors around orchestra.core/defn-spec disappear. See last note here https://github.com/borkdude/clj-kondo/releases/tag/v2020.01.10

dharrigan12:01:16

I'm having a bit of difficultly understanding core.cache. What I'm trying to do appears very simple (in fact, just following the wiki), yet the results are not what I expect

dharrigan12:01:19

(require '[clojure.core.cache :as cache])
(def C (cache/ttl-cache-factory {} :ttl 5000))
(-> C (assoc :a "Hello World"))
(:a C)

dharrigan12:01:32

the result of evaluting (:a C) is nil.

dharrigan12:01:20

pretty much a 1:1 from here

dharrigan12:01:58

the structures are immutable

dharrigan12:01:16

(def TTL-CACHE (cw/ttl-cache-factory {} :ttl 5000))

(defn expensive-rest-call
  [_]
  (println "Sooopar expensive networky REST call")
  {:name "Foo" :surname "Bar"})

(defn get-data
  [k]
  (cw/lookup-or-miss TTL-CACHE k expensive-rest-call))

(get-data :a)

dharrigan12:01:55

That works as expected. First hit prints out the println on the repl, a few more hits does not, until the 5 seconds has expired.

michal74113:01:25

Hi! I'm looking for something like maplist in Common Lisp:

(maplist (lambda (x) x) '(1 2 3 4))
ā†’ ((1 2 3 4) (2 3 4) (3 4) (4))
Is there any clojure equivalent or should I just use loop with recur on (rest ...) ?

qythium14:01:57

(take-while seq (iterate next ...))

nberani14:01:30

I had this (take-while #(not (empty? %)) (iterate rest my-list))

michal74114:01:45

Thanks! my idea looked overcomplicated to me for something so simple šŸ˜‰

alexmiller14:01:56

You might find reductions to be a useful tool here, maybe with pop

neo255115:01:37

I would like to store a sample of the arguments and the image from functions that are called during a test run, such that I could run regression test afterwards. Is there a library that already provide such functionality? I am thinking of a non intrusive library (that is one that would perform alter-var-root

neo255116:01:00

and wrap functions from some namespace with the right behavior?

neo255116:01:37

The goal would be to log the arguments that are run during the program to make regression test during libraries updates.

jakob.durstberger17:01:41

Hello šŸ™‚ I am reading Programming Clojure and am following along the chapter on spec. I ran into a bit of a problem which I don't fully understand. Trying to execute the following code returns me a Runtime Exception Invalid token: ::music/id ...

(s/def ::music/id uuid?)
(s/def ::music/artist string?)
(s/def ::music/title string?)
(s/def ::music/date inst?)

(s/def ::music/release
  (s/keys :req [::music/id]
          :opt [::music/artist
                ::music/title
                ::music/date]))
Converting them to single colons work. I think the ones starting with :: are "qualified keys" but I am not exactly sure what that means and why that wouldn't work. Any help is much appreciated.

seancorfield17:01:22

@jakob.durstberger it means auto -resolve the alias music so I wonder if you don't have a namespace required with that alias?

jakob.durstberger17:01:16

@seancorfield yeah, I definitely don't have a namespace music. The book mentions auto-resolving namespaces a few pages earlier but I haven't fully understood them yet. At least now I know what I have to search for / re-read. Thank you šŸ™‚

alexmiller18:01:12

This is a bug in the book - should either be : or define the alias

alexmiller18:01:13

There was a period in 1.9 pre releases where this did work and I think this code may have been checked during that time, then not checked at release time

zignd21:01:26

Which would look more like "good" Clojure code?

alexmiller21:01:12

Either is fine

zignd21:01:12

Sometimes I think heavy use of threading compromises the readability, are both really fine? @alexmiller

andy.fingerhut21:01:38

I suspect the readability of a threading form is more related to how familiar the reader is with each of the functions/macros in the steps, than with the length. Also general recommendations like avoiding relying upon subtle details (or commenting those).

zignd21:01:12

Thanks for the input @, it's clearer now! Thank you @alexmiller as well!

didibus23:01:04

Good threading macros read like a bullet list.

didibus23:01:48

But if some intermediate function does a lot, and it gets difficult to read, you can use more and more of let to add names to the intermediate results in order to help the reader

seancorfield23:01:44

@zignd They are not quite equivalent, I think? In the first one you have (apply (resolve-controller operation) operation) and in the second you have ((resolve-controller operation) operation) -- apply treats the last argument as a sequence (of arguments) in the call. Assuming the controller function takes a single argument, I think the first form would need (apply [operation]) so that the semantics match.

seancorfield23:01:28

And that apply would make me want to use the second form instead.

seancorfield23:01:28

Or I might use (as-> f (f operation)) instead of (apply [operation])

zignd23:01:30

Would it be ok to use as-> only for this line?

zignd23:01:39

You're correct @seancorfield! After a few iterations not understanding what was going wrong I realized I was not using apply correctly. But thanks for noticing that and telling me xD

zignd23:01:18

(let [operation (adapters/from-json-to-map line)]
    (-> operation
      (resolve-controller)
      (apply [operation])
      (adapters/from-map-to-json)
      (println)))

zignd23:01:53

This is the current state of that code xD

seancorfield23:01:05

(-> line
    (adapters/from-json-to-map)
    (as-> op ((resolve-controller op) op))
    (adapters/from-map-to-json)
    (println))
another possibility šŸ™‚

zignd23:01:32

That indeed looks pretty šŸ¤”

seancorfield23:01:50

Yes, as-> is designed to be used in a -> pipeline to provide a binding, to be used in an expression where the item being threaded is neither the first argument nor the last argument.

zignd23:01:29

Interesting! I'm going to change to that! Thanks @seancorfield!

seancorfield23:01:52

You can also put ->> into a -> pipeline for any subset that needs to be "last argument" as opposed to "first argument" threaded, but some people consider that a code smell and an indication that you're switching between thing/collection and sequence so you should refactor into smaller functions.

seancorfield23:01:34

(there are lots of good pieces of advice in that blog series -- although not all of them are considered universally good style in Clojure)

zignd23:01:05

Nice! I'll make sure to read them, might enlighten me in new possibilities with the threading macro. This one you showed me with the as-> is really clever tho, I don't that would come to my mind hahahah

seancorfield23:01:34

To give you an idea of usage, we have 32 instances of as-> in 19 files, out of a 90,000 line codebase.