Fork me on GitHub
#beginners
<
2018-05-01
>
bolivier02:05:55

I’m reading the docs for Luminus, and I don’t understand how you’re supposed to keep your application manageable as it grows. It doesn’t seem like there’s a clear place for “business logic”.

orestis10:05:28

@bolivier I have the same question, I think you’re supposed to just create a few namespaces and put some functions there. Then call those functions from the various web handlers.

orestis10:05:13

You might use something Component or Integrant if you have some state (e.g. database connections/setup) — I’m not sure what Luminous uses by default.

Denis G14:05:23

Does anybody know what the difference between recur and trampoline is? Kinda confused right now 😞

mfikes14:05:19

@denisgrebennicov I actually think if you do (source trampoline) you might come away with a clear "no-mysteries" understanding of it, since it is build right on top of recur.

Alex Miller (Clojure team)14:05:41

there’s a whole chapter with more detail in Programming Clojure if you want lots more words

mfikes14:05:20

My mental model is that it is kind of like a recur but that looks at the return value to see if it is a function and should continue going...

mfikes14:05:16

It has a good name in that it mentally reflects what it does

Denis G14:05:19

@mfikes thanks 😅. Still not used to look up to the source code implementation for better understanding 😄

mfikes14:05:41

Yeah, 🙂 in this case trampoline is nice and compact in its impl

mfikes14:05:30

Also, if you haven't seen it, this can aid in understanding: https://clojuredocs.org/clojure.core/trampoline

Denis G14:05:33

I guess, when I was reading the description I was confused, because of that mutual recursive part and other than that, it seemed like it was the same as what recur does, namely, calling the function while reusing the stack

mfikes14:05:49

You can also use trampoline to get around (non-mutually-recursive) cases where recur can't be used. For example, you can't recur across a try, but if you really need to, you can fashion a solution using trampoline.

noisesmith14:05:08

@denisgrebennicov recur literally reuses the stack, trampoline extends the stack to a new function but only after returning from the previous. It's reuse vs. avoid growing too far.

noisesmith14:05:53

recur is a goto, trampoline a new function call

Russ Olsen14:05:04

The other way to look at recur vs trampoline is that chances are excellent that you mean recur. 😉

Denis G14:05:35

😄 😄 😄

Denis G14:05:21

I was just reading this blog post: https://deque.blog/2017/06/01/clojure-high-performance-fibonacci/ and they guy was comparing fibonacci implementation recur vs trampoline

Alex Miller (Clojure team)14:05:45

almost any numeric benchmark on Clojure is dominated by how well you avoid boxing

Alex Miller (Clojure team)14:05:02

as boxed math is ~100x slower than primitive math

tdantas14:05:29

what is a good practice to handle database connection functions I’m doing my execute using a global invisible connection I could obviously (defn execute [conn query params]) but I can’t see why should I do that

(def conn (nr/connect ""))

(defn execute [query params] 
  (-> (cy/tquery conn query params) walk/keywordize-keys))

tdantas14:05:59

what best practice in this case, should I pass the connection to be more functional and my function be more pure and functional

Alex Miller (Clojure team)14:05:10

in general, I would start from: never “def” a stateful thing

💯 4
dimovich15:05:01

or if you really have to, use defonce? 🙂

Alex Miller (Clojure team)15:05:26

well, that depends a bit on the semantics

Alex Miller (Clojure team)15:05:57

defonce is a pain for interactive development so I try to minimize that too

dimovich15:05:08

yeah, that's true

Alex Miller (Clojure team)14:05:11

(all rules are made to be broken eventually, but I think this is a good thing to strive for)

Alex Miller (Clojure team)15:05:03

I’ve only ever regretted def’ing state. create it during initialization and pass it around. don’t enshrine it in a var.

tdantas15:05:08

when you say @alexmiller create it during initialization you meant in the begining of the process , boot process ?

tdantas15:05:41

for instance, in my main application and passing into my handlers ?

tdantas15:05:48

and creating a closure

tdantas15:05:54

sure, makes sense to me

Alex Miller (Clojure team)15:05:21

in the path of starting your application, cause the state to be created, then inject, close over, pass to those who need it (which of those will depend on what you’re doing)

john15:05:07

@alexmiller would you recommend def'ing your state initially, to play with it in the repl? And then rework your functions to be more of a pipeline as you massage your functions into a compilable program?

Alex Miller (Clojure team)15:05:34

oh sure, you will likely need some way to def state for edevelopment

Alex Miller (Clojure team)15:05:12

Clojure is most glorious when arranged as pipelines of functions handling data

Alex Miller (Clojure team)15:05:23

not all work looks like that though :)

john15:05:24

I find when I first approach a problem, I start with all state "exploded" into its parts, sitting in vars

john15:05:40

but then I massage things into a pipeline once I understand the domain

orestis18:05:50

I wonder how closed over state would look in case of failures - ie some database goes down momentarily, connection is closed and you need to reconnect and update all dependencies...

eurythmia19:05:46

@orestis you can always pass a map back from functions with new state, and function results

noisesmith20:05:11

I've sometimes found it useful to have a record with a reconnect method, and a shared internal state - but these things are less elegant than I'd like. I think the right solution is to make something like a jdbc connection pool object API, and let it manage its own open connection(s) in its own isolated scope

noisesmith20:05:37

so what you pass into the code at startup carries not just a currently working connection, but the config that knows how to reconnect, and behavior that manages that

noisesmith20:05:13

trying to make an implicitly stateful thing act like an immutable thing can make things more complex rather than less if you do it clumsily

orestis20:05:39

Right @noisesmith so instead of passing a connection around you pass a connection manager or something similar. I keep thinking about the Erlang way where you use named processes to hide the details. It’s still global state but it’s hidden away and the calling code is by even aware that there is any state, as at the call site it looks like any plain function. Of course, that’s what we are trying to avoid all along?

noisesmith20:05:18

right, in clojure you can do that with a closure that exposes a safe api, and handles any trickiness internally

noisesmith20:05:39

though that should be reserved for things that are intrinsically mutable / stateful