Fork me on GitHub
#beginners
<
2018-04-05
>
jcburley05:04:44

Perhaps a silly question, but: is it "normal" for my Windows 7 machine to have two java.exe instances continuously taking 13% CPU due to running Clojure repls (that aren't doing anything)? I hadn't noticed that behavior in the past, when I was doing Clojure stuff fairly regularly (then took a break to work on other stuff) -- and "lein repl" hangs after the prompt, though REPLs within existing projects seem to work okay.

Michael Fiano05:04:50

Can anyone here understand Common Lisp code? I'm wondering which iteration function to use for porting some code of mine.

jcburley05:04:48

(Can anyone really "understand" Common Lisp code? 😉) Maybe post a snippet of the code in question and see if someone can explain it?

Michael Fiano05:04:13

This is the function in question -- a nested "do loop" that is accumulating the results of calling an effect function on each item if it passes through a filter function. https://github.com/mfiano/gamebox-dgen/blob/master/src/neighborhood.lisp#L187-L195

jcburley05:04:09

Just glancing at it, I think some variant of reduce would be suitable for the affectedp handling...?

Michael Fiano05:04:18

I really have no idea. I'll look into that though. Thanks.

jcburley05:04:27

I didn't realize CL had ":" keywords tho....

callum07:04:06

How might I go about debugging a non-terminating clojure instance? I’ve been experiencing an issue occasionally where I run lein midje, there’s an exception during the tests, and the process just hangs. I don’t get this issue when running the tests via the REPL with (load-facts :all).

kari08:04:15

I have seriously fallen in love with Clojure and trying to promote its use here in Finland: https://medium.com/@kari.marttila/using-clojure-to-implement-a-web-service-server-53f62dca964f

Denis G11:04:02

hey guys, question here, I’m getting the nth not supported on this type: PersistentArrayMap All I want is to iterate through key/value. Maybe filter instead, but the same idea applies. Code:

(doseq [[k v] (seq my-hash-map)]
      (prn (apply str k v)))

acron11:04:35

@denis-v technically hashmaps aren't sequential

Denis G11:04:51

but how can I iterate through keys and values

leonoel11:04:04

@denisgrebennicov you can iterate a map like this but are you sure the error is not inside the loop ? apply expects last argument to be a sequence

leonoel11:04:39

btw seq is not needed, doseq will coerce to iterable anyway

leonoel11:04:14

I mean, everywhere a sequence is expected you can use hashmaps

Denis G11:04:17

Thanks. The problem was that I was having an array of dicts, therefore I couldn’t iterate through key/value pairs. It didn’t make sense

Denis G11:04:28

Now everything works like a charm

curlyfry11:04:56

As an aside, you don't need the apply in this case @denisgrebennicov

curlyfry11:04:21

(str k v) will give the same result

Denis G11:04:42

Gracias 🙂

Surya Poojary12:04:03

A TCP server in clojure

Surya Poojary12:04:02

A code snippet , s'il-vous-plaît ?

drewverlee12:04:51

Can some explain why I get this error?

(defn dfs
  [g nxs v]
  (let [n (peek nxs)]
   (cons n (dfs g (remove v (concat (pop nxs) (n g)))  (conj v n)))))

(dfs {:a #{:a}} '(:a) #{})
=>    clojure.lang.LazySeq cannot be cast to clojure.lang.IPersistentStack

mfikes12:04:22

For this problem, if you convert (remove v ... to (filterv (complement v) ... you can fix that but then hit an error regarding popping an empty vector

drewverlee12:04:55

I give, what's the right way to do dfs where you need to keep the path? I feel I have done 4 slightly wrong versions

drewverlee12:04:24

I searched online and found two implementations with issues to :)

drewverlee12:04:26

i suppose im trying to understand if im supposed to be leveraging some concept like reduce here. but that doesnt seem right as im not iterating over a sequence. i have done dfs several ways in the past with python and dont recall gettting this tripped up. maybe because of how i was taught the alg

grierson13:04:12

What is the recommended naming convention for a project? lein new app org.company.product?

frenata13:04:04

We normally name the project itself product, but the namespace/directory structure is structured in the java way as above. But take that as anecdote, I'm uncertain of any broader conventions.

timo14:04:12

Hi there, has anyone ever seen this error? And knows how to solve it?

Uncaught TypeError: Cannot read property 'ReactCurrentOwner' of undefined

theeternalpulse15:04:04

@uayyagari our loop bindings should have the accumulator

theeternalpulse15:04:25

and your recur should be like (recur (rest arr) (inc n))

theeternalpulse15:04:32

run

(let [n 0]
  (inc n)
  n)

theeternalpulse15:04:38

one of the pillars of clojure is immutable data, if you're not storing the value somewhere, it isn't mutating. Also some of the binding names can become confusing, try not to overload a local def with a binding of the same name

Uday15:04:37

Oh!! so its incrementing n but not storing that value in n. Right?

theeternalpulse15:04:42

yes. sticking another binding in loop to bind to that and then pass it to the recur is the clojure way to do it

theeternalpulse15:04:28

you have to keep in mind the nature of immutability in pretty much all code you write

Uday15:04:42

I had initially included the binding in the loop but I wanted to see why this wouldn't work.

Uday15:04:16

Thanks a lot @theeternalpulse

Uday15:04:45

true! I keep forgetting. I make the same mistake in python

theeternalpulse15:04:25

actually scratch that

theeternalpulse15:04:54

I think you still have to check, I think common lisp an empty list and nil are equivalent

Russ Olsen15:04:35

FYI If you use next instead of rest you will get a nil when you run out of stuff.

theeternalpulse15:04:08

Right, I know there are some guidelines of using one over the other, have to check

tbaldridge15:04:38

@theeternalpulse almost always use next

tbaldridge15:04:59

I think I've used rest about twice in the past 7 years

theeternalpulse15:04:43

ah. i use rest to name destructured arrays

theeternalpulse15:04:22

the rest params, which is probably bad practice

Uday16:04:42

is the space complexity of rest O(n) and that of next O(1) ?

Will16:04:42

I’ve been researching how to create routing APIs in my clojure services, and I’ve noticed there are a lot of libraries / frameworks that people use to accomplish this. I’ve seen Liberator, Ring which appears to have a lot of problems from the articles I’ve read, Compojure, and Pedestal. What is the best one to use? Which one overcomes the problems Ring has?

ordnungswidrig16:04:42

Will: for routing you want to use compojure or bidi. Liberator is a library that helps implementing semantically correct http resources (caveat: author speaking). ring is the api model all this is based on.

ordnungswidrig16:04:28

Pedestal uses an alternative (but compatible) abstraction to ring, and delivers routing and more libraries with it.

ordnungswidrig16:04:15

In my experience the most beginner friendly option is to use compojure and then look weather you want to use liberator for better apis

Will16:04:57

Thanks, I’ll try that! @ordnungswidrig

drewverlee16:04:03

Is there a way to call something like rest on a collection and keep the collection type?

(rest (sorted-set :a :b))
;;=> #{:b}

jcburley17:04:43

Strange -- I get (:b) as a result in my REPL. Maybe a different version of Clojure running?

jcburley17:04:03

(I'm running 1.8.0.)

jcburley17:04:38

Anyway, seems like, generally, aspects of types connoting invariants (like "in sorted order") would be dropped for arbitrary modifications to instances of those types. E.g. consider (cons :c (sorted-set :a :b)).

jcburley17:04:34

But whether certain operations, such as rest, that would preserve such invariants, should do so, is an interesting question. Ideally rest would know as little as possible about the details of its argument; and the type system as little as possible about various operations that can be performed...but maybe an invariant's description could include some kind of special knowledge such as "removal of items does not violate the invariant".

drewverlee20:04:12

sorry if that was confusing… what i wanted was something that returned a set. So my example is what i want, not what i get.

drewverlee20:04:21

i get what you get.

seancorfield17:04:22

@drewverlee Like this

(let [coll (sorted-set :a :b :c :d :e :f)] 
  (into (empty coll) (rest coll)))
;;=> #{:b :c :d :e :f}

seancorfield17:04:13

Not sure if it'll work in all cases but...

bjr17:04:57

I wonder why peek and pop (IPersistentStack) aren’t implemented for sorted sets

alexmiller17:04:12

because sets aren’t ordered

alexmiller17:04:01

peek and pop are about insertion point (ordering) and sets don’t have a known insertion point (you can “add” anywhere in a sorted set)

bjr17:04:51

so is it more about insertion order than removal order?

Russ Olsen17:04:29

@uayyagari I think they next and rest are similar, O(1) or nearly so, they just behave differently with an empty seq.

bjr17:04:49

I’m sure it would make sense if I dug into the impl, but trying to get the intuition.

drewverlee17:04:32

@alexmiller i have the same question that bjr has. In what way is a sorted-set sorted? I assumed a sorted-set had a concept of an index to value mapping. Otherwise i don’t understand the meaning of the word. If not, then then maybe another question will clarify, whats the usefulness of a sorted-set?

alexmiller17:04:55

set semantics but able to sequentially traverse in a sorted order

Denis G17:04:19

How do I achieve a “polymorphic” behavior in a sense, that I have a protocol defined, by defprotocol and I want two distinct implementations of this protocol. Since in my case I don’t need any fields, defining a record with no fields sounds wrong. I was thinking that I can smhow have 2 different keywords which implement this protocol. But keywords in my case would be objects and not classes, therefore this approach won’t work, right? To get the picture clearer, I have a protocol with methods, which should be called but have different implementations depending on if I run it in --dry-run mode or not. Ideas? Or am I thinking too much in OOP style and need to change my thinking? 😅

schmee17:04:13

@denisgrebennicov if you don’t use state, you can use a deftype instead

schmee17:04:22

or, maybe preferable, you can use multimethods

schmee17:04:39

they can dispatch on a keyword, so you can have on method for :dry-run and one for :default

Will17:04:30

I’m struggling to create an in memory h2 database for my tests in clojure. I have a file that contains all the create table statements and another with all the insert statements. How would i build the database using the schema file and dump all the data from the files?

schmee17:04:51

also, regarding OOP: polymorphism is very much a Clojure thing, but mutable state is not. OOP combines these two concepts into one, and clojure does not

Russ Olsen17:04:17

@denisgrebennicov If you don't have any state associated with your polymorphic behavior I'd think a multimethod would be what you want.

Denis G17:04:06

Thanks! Reading about multimethods right now

bjr17:04:37

I wouldn’t shy away from records and types — it can be a lot more direct to (if dry-run (DryRunObj.) (RealObj.)) than to use multimethods. Maybe sketch each out and see which you find clearer.

schmee18:04:29

if you’re doing that, why use records in the first place?

(if dry-run
  (dry-run-fn)
  (real-fn))

Denis G18:04:03

because I have like 4-5 functions/methods

bjr18:04:21

if you want that conditional in a single place

schmee18:04:27

exactly, so that’s why you want polymorphism, not conditionals

bjr18:04:59

the conditional is to select the implementation

bjr18:04:20

the rest of the code doesn’t have to care, it’s just using the protocol methods

schmee18:04:31

maybe, I don’t know, it’s hard to give more concrete advice without more details

bjr18:04:52

doesn’t polymorphism always start with a decision to choose an implementation?

Denis G18:04:09

I have like 2 cases, when I call the protocol defined methods. I write a mini auto-approver tool for some dumb pull-requests we have, so it happens to be like this (if-let [error (validate-pr pr)] (handle-error error) (approve-pr pr)) and the approve-pr has same methods to call, but different imp depending on dry-run condition or not approve-pr goes through the same procedure, like 1) change-status 2) post-comment 3) merge-pr The dry-run methods should just log it to the stdout, whereby the real imp should do some work, in this case networking requests

schmee18:04:11

I would go for records + protocols here, and pass the record as the first arg, eg (approve-pr client pr), (validate-pr client pr) and have a RealClient and DryRunClient like @U060WQTSB mentioned above 🙂

Denis G18:04:37

Thanks! But still creating a record with no fields, feels kinda wrong 🤔

schmee18:04:33

heh, I promise that soon you will have a situation where “oh, I need to pass credentials to the client” or “oh, I want pass a log path to my dry runner”, and then records will come in handy 😄

Denis G18:04:22

😄 😄 😄

bjr18:04:31

yeah, and it’s really just like an object

noisesmith18:04:38

also, instead of a record with no fields, you can use a top-level (def foo (reify SomeProto ...)) - it has all the method-implementing stuff with none of the value carrying stuff

drewverlee18:04:04

hmm ok dfs version 4.

(defn dfs
  [g nxs v]
  (let [n (peek nxs)
        v (conj v n)]
    (when n
     (cons n (dfs g (filterv #(not (v %)) (concat (pop nxs) (n g))) v)))))
I’m curios if anyone has any suggestions. I feel like seeing the different ways depth first search can be done is fairly instructional. I think (not filterv) is odd, but i can’t seem to use remove as it produces a lazy list…

drewverlee18:04:39

also i’m probably supposed to be using recur

drewverlee18:04:56

in order to avoid stack overflows if the graph is to large.

dpsutton18:04:09

filter not sounds like remove

drewverlee18:04:42

remove produces a lazy list. Which i can’t call peek on. I think maybe i should try to keep everything as a list. That way i could add and remove from the front and possible keep using the lazy-seq.

dpsutton18:04:27

does filter not produce a lazy list?

dpsutton18:04:33

oh sorry. just noticed filter v

dpsutton18:04:14

if you want to get rid of the negation

Logan Powell19:04:56

👋 Hi everyone! Newby to Clojure(script) here. Is this where we should ask questions about exercises (on [exercism](http://exercism.io/exercises/clojure), e.g.) we might be having trouble with?

schmee20:04:44

@loganpowell indeed it is, fire away! 🙂

Logan Powell20:04:37

@schmee Sweet! Thank you 🙂

Logan Powell20:04:06

I ran the tests from exercism (http://exercism.io/exercises/clojure/bob/readme) - one-by-one - on these and they worked fine, but when I ran lein test I was getting some failures due to what seems as multiple conditions getting evaluated... Might I be doing something wrong?

schmee20:04:19

can you paste some of the failed output?

noisesmith20:04:35

@loganpowell: what does "multiple conditions being evaluated" mean?

Logan Powell20:04:25

It seems that when the tests run, multiple conditions are returning true rather than just the first that returns true (which is what I expected)

schmee20:04:35

I recommend that you print the intermediate values and make sure that they are what you think they are

noisesmith20:04:36

also (if x true false) can usually be replaced with x (your (empty? string) check)

noisesmith20:04:01

@loganpowell: cond always stops at the first true condition

Logan Powell20:04:14

that's what I thought 🤔

schmee20:04:39

for instance:

user=> (seq (first [""]))
nil

Logan Powell20:04:45

@schmee I tried them out one at a time and it worked

noisesmith20:04:27

@loganpowell: could it be that the first one that returns true isn't the one you want, and you need to reorder the clauses in cond?

schmee20:04:33

hmm… when I copypaste the code into the repl I get this:

user=> (response-for "")
"Whatever."

Logan Powell20:04:57

Yep, that's what it should do

Logan Powell20:04:28

try it with a question mark in there

noisesmith20:04:29

@schmee note that "string" is a list of strings, so it is only empty if you provide no args

Logan Powell20:04:51

right, that's how I read the requirements

schmee20:04:33

are we reading the same test?

(deftest responds-to-silence
  (is (= "Fine. Be that way!" (bob/response-for ""))))

Logan Powell20:04:36

I read silence as no args

Logan Powell20:04:57

so if it's an empty string it's "whatever." 😄

Logan Powell20:04:30

or "whatevs", which is my preferred slang 😉

noisesmith20:04:33

@loganpowell: but in that case your code and the tests disagree about the spec

noisesmith20:04:43

and you are asking why those tests fail right?

noisesmith20:04:07

I guess you could rewrite the test if you are that strongly opinionated about it

Logan Powell20:04:29

So, I think the tests are failing due to multiple answers coming through, e.g.,

clj
FAIL in (responds-to-shouting-with-no-exclamation-mark) (bob_test.clj:34)
expected: (= "Whoa, chill out!" (bob/response-for "I HATE YOU"))
  actual: (not (= "Whoa, chill out!" "Whatever."))

dpsutton20:04:46

where is a "multiple answer"?

noisesmith20:04:52

that's not what is happening

noisesmith20:04:58

there's no such thing as multiple answers

noisesmith20:04:07

you just don't provide a behavior that the test is checking for

Logan Powell20:04:13

so, here two conditions seem to be met returning 1) Whoa, chill out! and 2) Whatever.

dpsutton20:04:13

it's failing because whoa chill out isn't equal to whatever

noisesmith20:04:26

@loganpowell: that's not what that message means

dpsutton20:04:32

you said to expect whoa chill out and instead it saw whatever

noisesmith20:04:40

@loganpowell: clojure.test shows you what is expected, and what it got instead

Logan Powell20:04:35

Am I mistaken to see the test is expecting "Whoa, chill out!" and is failing because it's getting: "Whoa, chill out!" "Whatever."

noisesmith20:04:54

you are mistaken

dpsutton20:04:20

(= "Whoa, chill out!" "Whatever.") is false. test fails

noisesmith20:04:24

it is saying it expected "Whoa, chill out!" and instead it got "Whatever."

Logan Powell20:04:00

ok, let me look over it again. Thank you!

Logan Powell20:04:14

You guys were so right and I so wrong, thank you for the guidance!

justinlee20:04:28

@loganpowell You’re not the only one who finds that output initially confusing.

justinlee21:04:36

you got me curious. apparently there is a with-tap-output in clojure.test.tap. i haven’t tried it but that should be less utterly confusing and you could run the output through a TAP formatter like faucet

schmee21:04:48

that stuff isn’t in mainline yet right?

schmee21:04:40

oh, sorry, that stuff is totally unrelated to the new thing in Clojure core, sorry ’bout that 😛

justinlee21:04:32

looks like stuart sierra wrote a TAP implementation 9 years ago 🙂

Daniel Hines21:04:06

In nodejs, when I'm losing the war on dependencies, I can go nuclear with "rm -rf node_modules". Is there a way to delete all the dependencies I've downloaded for a Leiningen project and start over?

dpsutton21:04:38

i don't know if its advisable but all the deps go into ~/.m2/repository

noisesmith21:04:20

@d4hines an important difference is that all the dependencies via lein / maven are immutable and versioned - if deleting a dep and then pulling it down again ever resulted in a different version, that would be a big system level failure

noisesmith21:04:59

(nb. there are SNAPSHOT deps, but those are an abstraction over weird timestamped verions)

Daniel Hines21:04:13

@noisesmith As I understand it, npm is immutable as well, in the sense that you can't upload two packages with the same name and version. I'm more concerned about when adding a dependency breaks your build.

noisesmith21:04:23

Then ask for a different version, there's no unversioned deps so deleting never helps

noisesmith22:04:16

(or remove the dep, which makes it no longer visible to the project)

noisesmith22:04:38

A key difference is an "install" (thing is available because in disk), vs "config" (it's available because you specified you wanted it, explicitly)

noisesmith22:04:20

lein doesn't use installs in normal usage, it has an on disk cache backing up configs

noisesmith22:04:14

That said, feel free to remove the cache, it will all get downloaded anyway next time

noisesmith22:04:52

Just saying that deleting the cache doesn't fix bugs unless something very rare and bad happened

justinlee22:04:17

@d4hines there’s a lot more state represented in a node_modules directory than in the .m2 repository. deleting the repository is more akin to doing a npm cache clean

noisesmith22:04:50

in fact, the m2 repo doesn't contain state - it's a cache, it just contains the presence or absence of resources

Daniel Hines22:04:56

Ooooh, I think I see. I guess the issues I'm used to dealing with are from breaking changes being released under a bugfix.

noisesmith22:04:33

right, with lein (used properly) you never get a version change unless you ask for it, and if you do, the simple fix is to ask for the older version

noisesmith22:04:22

you can treat the fact that things are on disk as a simple implementation detail allowing you to avoid downloading deps on every startup

Daniel Hines22:04:19

So I have a project I forked, and a library I wrote. The project works fine out the box. It works when I add the dependencies to my project.clj and when I import the library in a namespace, but as soon as I call the library's code, the project breaks. Here's what baffles me though: when I stash the changes, run lein clean, and try to boot up the project, it still breaks. If the dependencies are immutable, what's going on?

Daniel Hines22:04:34

If it helps, the root error looks like it's this: Caused by java.io.FileNotFoundException Could not locate figwheel_sidecar/system__init.class or figwheel_sidecar/system.clj on classpath.

noisesmith22:04:38

@d4hines sounds like the figwheel plugin didn't get loaded properly?

Daniel Hines22:04:23

So, on a whim, I deleted my profiles.clj file which I added to the project, and now it works!

noisesmith22:04:32

note that plugins are not transitive like other deps are, so the top level project should be able to decide if figwheel is being used

noisesmith22:04:41

wait, you added an extra project.clj?

noisesmith22:04:23

the primary role of project.clj is to calculate dependencies, so a broken one will be the main reason things like this break, yes

Daniel Hines22:04:35

no, I was trying to use environ to get configuration options to my library, and the lein-environ plugin requires a profiles.clj file in the root.

Daniel Hines22:04:46

The profiles.clj is just a map of the options.

noisesmith22:04:04

oh, profiles.clj, OK

noisesmith22:04:11

yes, profiles.clj is merged into project.clj

noisesmith22:04:23

and a broken profiles file cna break your project

noisesmith22:04:06

lein-environ doesn't actually require profiles.clj - you can put dev config into project.clj and then provide deployment config via the command line

Daniel Hines22:04:50

Right, but I was hoping to keep my credential info in my profiles.clj, so I didn't have to type it in every time. In that merging process, how deep does it go? I assumed it was entirely non-destructive, and that nothing would be lost, only clobbered if specified twice, but now I'm not sure.

noisesmith22:04:15

different keys have different rules, some are clobbered, some are merged

noisesmith22:04:25

:eval-in pprint can clear up that ambiguity though

noisesmith22:04:49

(if you put that in your project.clj map it just pprints your project config instead of running any task, after calculating the effective config)

Daniel Hines22:04:51

Aaah. So add that and run it with and without the profiles.clj, and see the difference?

noisesmith22:04:26

or just run it with profiles.clj and see which part got clobbered even

Daniel Hines22:04:48

Not sure I get that.

noisesmith22:04:45

:eval-in pprint ends up showing a project config, after merging apropriate profiles (whether defined in project.clj or profiles.clj etc.) - you should usually be able to tell if something is missing or misconfigured

noisesmith22:04:58

but sure, compare the pprint of two configs if that helps too

Daniel Hines22:04:19

Oh, I see. Thank you!

noisesmith22:04:55

given the error you showed, I would assume you are losing your plugins key (the one that pulled in the figwheel plugin)

Daniel Hines22:04:17

That would make sense. Here's another, higher level question though: how should I handle these configuration details in general? I was having my library use environ, but if my the project it's used in doesn't use environ, suddenly that seems less useful. Should I have the config be a parameter to every function? Or should I require the user to pass the config to a function that stores it in an atom for the other functions to use?

noisesmith22:04:01

@d4hines a common pattern is to use a state management library (like component, integrant, or mount) and it can use environ or whatever else as part of creating the initial app state

noisesmith22:04:36

I wouldn't put things into an atom unless they are a) shared globally and b) subject to arbitrary change at runtime from multiple threads

greglook22:04:17

global access to environment state is generally an antipattern to me

greglook22:04:43

injecting configuration is much safer and makes testing and reasoning about behavior simpler

greglook22:04:14

which implies that your library should not read values out of environ directly in the general case, but you could have a special “configure this thing from the environment for me” constructor that uses some standard environment variables you’ve picked

greglook22:04:15

I actually wrote a library to make tracking env access and variable definitions easier to manage after we wrestled with this in our services: https://github.com/amperity/envoy

Daniel Hines22:04:45

That's really cool. I'm a bit confused about the "constructor" though. My end user can hand the options/object to the constructor function, but where does the constructor put them if not in an atom?

Daniel Hines22:04:59

Or var of some sort.

noisesmith22:04:48

@d4hines you pass them to something that uses them

noisesmith22:04:54

this is the way component, integrant, mount, etc. generally work - you give them a thing, they figure out who needs it, and they create some state holding object and pass it to the relevant consumers as they start up their own state

noisesmith22:04:01

there are many advantages to avoiding the globals (in terms of testing, growing the system in unexpected ways, etc.) - though mount actually does use global singletons if you end up preferring that style of state management anyway (I don't recommend it)

Daniel Hines23:04:47

I really appreciate ya'll patience, but I'm still missing something. I have an api key k and a function that consumes it f. You're not saying to pass k directly to f, right? f instead references k, which is some sort of stateful object. So I use Component or Integrant to ensure that when f references k, it's defined appropriately?

noisesmith23:04:07

@d4hines the initial startup of your application is all functions

noisesmith23:04:31

one of these functions gets your api key as an argument, and passes it to the things it in turn runs (if they need it)

noisesmith23:04:45

why would your api key be stateful?

noisesmith23:04:22

and sure, if you need a stateful resource, you pass the container holding the resource as an argument to the thing that consumes it

noisesmith23:04:55

as I said, it's possible to use globals instead, but explicit args are much more flexible and forgiving as designs change, and simplify testing

noisesmith23:04:38

this reduces coupling between consuming and providing code

Daniel Hines23:04:46

Ok, I think I get it now. I was trying to store the config once, but using the Component system, every function called by the user needs to accept the config as an argument. That should be fairly easy to do. Thanks again @noisesmith, and @greg316

noisesmith23:04:52

@d4hines usually what this looks like is passing the relevant configs to your http server startup, so that the handlers that need it can be passed the api auth etc. or passing the configs to your db component so that it can use them to create a connection pool which is then passed to each function using the db etc. etc.

greglook23:04:53

component plays well with the idea of protocols (as your abstraction interface) and records (as your implementation)

greglook23:04:24

for example, you could create a protocol describing the operations you need to perform on a database (create a foo, list foos, delete a foo)

noisesmith23:04:45

and yeah, you end up with a record whose keys hold a hash-map containing all the stateful objects that are needed (based on start up declaration) by this code context

greglook23:04:47

and provide a real implementation as a record which extends the protocol - the record would have an attribute to hold the stateful connection or API auth or what have you

greglook23:04:10

but your other logic just uses the protocol methods and doesn’t need to know what’s going on under the hood

Michael Fiano23:04:22

I'm not sure why *print-length* is not being honored in my project.clj dev profile. I set it to 5 as a test, and evaluating *print-length* in the REPL says it's 100. Any ideas how to actually override this?

greglook23:04:24

which also lets you do things like swap out a mock implementation for testing that might just stick your foos in an atom

Daniel Hines23:04:28

@greg316, @noisesmith Can you recommend an example I could study?

greglook23:04:51

so for example the S3 implementation is a record which internally maintains an S3 client with appropriate auth: https://github.com/greglook/blocks-s3/blob/develop/src/blocks/store/s3.clj#L214

seancorfield23:04:22

It has a WebServer component and an Application component, and ensures the application is added to the Ring request so each handler can access it.

seancorfield23:04:11

(it doesn't use a component for the DB connection -- because it lets the JDBC library create/close a connection on each call -- but it could be refactored to do that with a connection pool inside a component, that the application would "depend on")

Daniel Hines23:04:01

Thanks @seancorfield. So... is a protocol+record combo any different than an interface/class?

greglook23:04:43

it is very similar

seancorfield23:04:44

It's similar. Clojure has both interfaces and protocols.

greglook23:04:55

the main difference is that you can extend protocols to types after the fact

greglook23:04:14

which is impossible in straight Java - imagine trying to make String implement a new interface. 🙂

seancorfield23:04:56

(which means, with Component, you can add a startup/shutdown lifecycle to arbitrary Java types 🙂 )

Daniel Hines23:04:39

My experience in OO is with TypeScript, so my perspectives are probably all sorts of skewed. I think in TS I would just make a new interface implement the existing one, or have my new class extend the other existing one. Or am I missing the whole picture?