Fork me on GitHub
#clojure
<
2018-05-30
>
danielcompton00:05:09

We use Component, but I've heard good things about Integrant would love to check it out sometime

tolitius02:05:37

@caleb.macdonaldblack atoms work well for simple applications. they also have a great property of being built in to the language, hence no additional deps or ways that lock you in someone else's app design approach. in case you need lifecycle management: i.e. restart stateful components inside repl, depending on number of these stateful components, you could choose to go with a library of framework that does it for you. I use mount (when I need it) and it works great for me. But do start with a few atoms, they are great.

caleb.macdonaldblack02:05:31

Are you guys using these libraries mainly for dependency injection or lifecycle management? In terms of dependencies and decoupling it seems to be overkill in clojure to manage it this way if we have no need of hots wapping services like a database. A well structured app using namespaces to separate concerns seems to be sufficient to me even if they functions are directly calling implementation. It feels like more work to manage dependencies using these libraries than it would be in the rare chance we swap out an entire service that is tightly coupled. I can definitely see value in managing lifecycle though. Perhaps I’m misunderstanding were the value is in these libraries

seancorfield02:05:53

Lifecycle management. DI isn't really a thing in Clojure: you pass the arguments a function needs.

seancorfield02:05:15

DIME is about the closest I've seen to actual DI in Clojure and... well... it's very clever...

seancorfield02:05:23

Component lets you define start/`stop` lifecycle for services and specify an implicit graph of what must be started before each service.

seancorfield02:05:13

(Mount and Integrant basically do the same in different ways)

seancorfield02:05:52

To me, DI really implies automated injection of dependencies which Component et al only do on a very small scale (and rightly so).

seancorfield02:05:39

The main thing that Component (and Integrant) help with is avoid global (mutable) state for services that you use -- database connections, web servers, etc.

seancorfield02:05:06

Yes, they also let you swap out services but that's really much less of a concern (except perhaps for integration testing?). The big benefit is no global mutable state and automated start/`stop` of service to assist with resource management (which is really beneficial when you're working in the REPL @caleb.macdonaldblack).

tolitius03:05:47

@caleb.macdonaldblack it usually comes down to solving this or similar class of problems: > if a database connection needs config, and it often does, config should be created first before a database connection gets created if a function, that creates a database connection takes config as an argument then there should be a "power that be" that makes sure this config is created and injected, passed, into that create-database-connection function. this can be done manually of course, but with a high number of such "dependencies" it could become hard to maintain: hence you could choose to use a library or a framework to help you out with that. mount lets you to still have create-database-connection function that takes the config, but to reference the need for this config in a :start function:

(:require [app.env :as env]
          ...)
...
(defstate database-connection :start (create-database-connection env/config)
                              :stop (disconnect database-connection))
this way Clojure compiler makes sure that config is created before the call to create-database-connection, since it is :required plus you have all the start/stop lifecycle functions i.e.
(mount/start)
or start/stop parts of applications
(mount/start #'app/database-connection #'app/another-component ...)
I would still recommend to start with just atoms though, and figure out what you really need before jumping into any of the dependencies. there is no one right way, it all depends on the context, problem, your personal preference, atmospheric pressure and which infinity stones you possess

😁 4
caleb.macdonaldblack03:05:12

@seancorfield @tolitius Thank you. This definitely cleared everything up for me. I was getting too hung up on the DI side of things coming from OOPS languages. As I now understand it, DI is not really an issue in clojure and these libraries are more for life cycle management which is absolutely a problem area for me when managing state for services and the REPL.

ScArcher03:05:35

When is the connection actually opened?

seancorfield03:05:53

@scott.archer I can't speak for Mount, but in Component-based systems, it would happen when you call start on your overall system component.

ScArcher03:05:44

Thanks, I was following a conversation in beginners on jdbc and saw this one so I thought I'd ask.

seancorfield03:05:00

So your -main function would look like

(let [running-system (component/start (app/build-system args))]
  (-> running-system :done deref))
(where done might be a promise created at startup -- that is delivered when the system is ready to shutdown)

tolitius04:05:08

@scott.archer connection is opened whenever create-database-connection function is called. and it is called whenever (mount/start) is called, which usually happens at either -main or REPL time

👍 4
dnaeon06:05:57

Looks like when using clojure.walk/keywordize-keys any reader tags are stripped off.

dnaeon06:05:09

Is this a known thing/issue?

annarcana08:05:33

So this is possibly a stupid question, but when basing a project on a lein/boot template, does this necessitate the resulting project be licensed under the same license as the parent template? I'm pretty sure the answer is yes in as much as there is an answer, but I'm curious

pesterhazy08:05:08

Surely not. The files are generated by the lein new command like JPEG files are generated by Photoshop or TXT files by Emacs, but you don't have to license your art under Adobe's license, nor your novel under GNU's

annarcana08:05:12

One might think, but also there's a considerable amount of actual original source code being copied when using a template. It's not like when Photoshop generates an image it's including huge chunks of some Adobe-made image in every output file

jgh09:05:17

@seancorfield thanks for the Selmer recommendation, i really like it!

4
Yehonathan Sharvit14:05:28

Hi there, what are going to be the main features of Clojure 1.10?

jgh14:05:14

the ability to conquer the world in 4 lines of code

Yehonathan Sharvit14:05:32

That sounds very promising @jgh 😎

josh_tackett15:05:55

Anyone know how to set up lighttable to not compile a new connection for each file in the project? but rather to create one connection per project?

josh_tackett16:05:12

@cgrand Hey I'm using enlive for some html parsing, have a few questions. Please let me know if you have some time to discuss

seancorfield16:05:21

@josh_tackett I don't know how many people still use LightTable these days but there's a #lighttable channel, if that helps? (it looks pretty quiet/small)

ajk19:05:49

I’m writing a macro that looks at the signature of a protocol, then generates a record that implements that protocol. This works as intended when I :refer the protocol: (protocol/spy BasicProtocol) but when I attempt to use a protocol with an ns prefix: (protocol/spy my-protocol/MyProtocolInADifferentNs) then I get a CompilerException:

CompilerException java.lang.RuntimeException: Can't refer to qualified var that doesn't exist
I think this is due to my usage of gensym: https://github.com/alexanderjamesking/spy/blob/master/src/clj/spy/protocol.clj#L40 If anyone can take a look it would be much appreciated!

hiredman19:05:06

you are passing a namespace qualified symbol in to gensym, so you get a namespace qualified symbol out, namespace qualified symbols are pretty much cannot be used as names in def (defn, def, defrecord, etc) or local binding forms (let, fn, etc)

noisesmith19:05:48

defprotocol methods will accept namespaced method names if they point to the right namespace at least, it simplifies writing macros

hiredman19:05:09

I mean, I don't know if that is the source of your error (I would need to see the full stacktrace) but it is definitely an error

hiredman19:05:54

so your macro will end up generating something like (defrecord foo/Whatever [...] ...)

noisesmith19:05:33

@alexanderjamesking backing up for a moment - it would be easier to use a reify here instead of defrecord, is there a reason you can't use reify or maybe proxy?

noisesmith19:05:04

reify and proxy are designed for making one-off anonymous implementations of things

ajk19:05:59

I’m using defrecord as I’m passing in a map of functions that I need to be able to access again - so I can verify those functions were called, and for functions not passed in I generate a default (that I also want to be able verify against). The calls are stored on the functions as metadata hence the reason I need access to them.

noisesmith19:05:46

you can store a reify for later as easily as a record instance

noisesmith19:05:14

I mean, for workflow you might need to have a map or var that holds both your reify and the functions, instead of a record that could directly hold the functions, but that's still simpler than anonymous defrecords with automatically generated global names

ajk19:05:00

thanks @hiredman and @noisesmith, that makes sense. I think I’ll take a different approach rather than using defrecord.

richiardiandrea22:05:07

is there a predicate in Clojure against a namespace object returned by find-ns?

richiardiandrea22:05:33

apart from (instance? clojure.lang.Namespace n)?

andy.fingerhut23:05:02

None that I can find.

richiardiandrea02:05:18

Thanks for confirming