Fork me on GitHub
#clojure
<
2020-12-06
>
zendevil.eth03:12:33

Is there a point cloud library or an open 3d alternative for clojure?

seancorfield04:12:59

We strongly discourage posting the same question in multiple channels @U01F1TM2FD5

3
Carlo16:12:23

hey! I'm refactoring the state of an application, and I'd like to state an invariant that has to be checked every time some operation is done on the state. I think spec is the right tool, but if I understand correctly, instrument is only used to instrument functions, right?

MKLUO16:12:48

I don't think it is what spec is for. Since the invariants is usually a part of your domain logic, explicitly asserting/checking it should be the way to go.

jfntn16:12:37

What’s a good way to downgrade the jdk installed by the homebrew clojure formula?

andy.fingerhut16:12:02

If I understand correctly, only a formerly-but-no-longer-supported version of the homebrew clojure formula installs a particular version of the jdk. If you use the latest officially released version of the homebrew Clojure formula, it will not install any version of the jdk for you, and you can pick any jdk you want: brew install clojure/tools/clojure as stated on https://clojure.org/guides/getting_started

andy.fingerhut16:12:35

So starting with brew uninstall clojure and removing the brew-installed jdk you do not want is probably a good start.

jfntn17:12:38

Thanks Andy, that tap was key, somehow brew install clojure is hardwired to install jdk 15?

andy.fingerhut17:12:27

Yes. There was a change made, maybe about a year ago?, by some Homebrew maintainers to cause many projects that depend upon a JDK, to depend upon a recent version of the JDK.

andy.fingerhut17:12:10

Clojure devs attempted to persuade them not to do so for Clojure, but they did not agree, so a separate Homebrew project was created for Clojure that they apparently allow to have control over that.

Gleb Posobin17:12:58

stuartsierra/component's [readme](https://github.com/stuartsierra/component) says "No function should take the entire system as an argument ... If a function depends on several components, then it should have its own component with dependencies on the things it needs." What's the reasoning for that? That reminds me of OOP a lot, where you would make a new class just to pass in structured data to a couple of functions or something like that. Why is passing the system and destructuring is not ok, like (defn f [{:keys [db task-queue]} ...] ...), and calling (f system ...) ? He says "This is unnecessary sharing of global state.", but I don't see how that is, if each function takes only what it and the function it calls needs.

lukasz18:12:01

It's not so much an OOP-ism (nothing wrong with that to be honest) but it's about separation of concerns - if you have a function which is only responsible for sending out emails, why would you pass database connections, slack client and a whole config map to it?

Gleb Posobin18:12:18

Because it might require a db connection later on when I add logging of sent emails to the db?

lukasz18:12:36

That sounds like YAGNI

lukasz18:12:49

if you will need it later, you will add it

lukasz18:12:01

Believe me - I have overcorrected with making everything a component in the early days, and it definitely needs some balancing.

lukasz18:12:40

And yes, we had some things depending on the whole system only to realize it was a smell and things had to be taken apart - I think the biggest drawback was to understand the code after while and having the whole system passed in would lead to sub-optimal design decisions

lukasz18:12:12

Basically you have a global state, but you're not admitting it because it's always the first argument of all your functions 😉

Gleb Posobin18:12:39

But adding it is a pain, have to adjust the arity of the function in all call sites.

Gleb Posobin18:12:08

Do you have a special email-component-bundle in your system then?

lukasz18:12:51

You don't have to adjust function arguments - usually you pass the dependency map as the first argument, and you use destructuring to pull them out. In case of the email-sender, they way we have modeled is that the email-sender itself is a component, injected into other bits which needed (and only them)

Gleb Posobin18:12:38

I see. One other problem I have is that there is a routing function that calls many possible functions that respond to requests, and each of those functions needs a bunch of components (some might need the db and s3, some might need access to the email and db components, etc). How do you handle that? So far the router component just depends on many other components, and passes the db s3 email etc components to the functions it calls as separate arguments. How would you structure this better?

lukasz18:12:19

Ah yes - that's definitely one of these cases where you sort of depend on most of the system. I don't think there's a good way around it unless your individual route handlers start being components. That's an overkill IMHO

lukasz18:12:20

That said, in that case - the code is always very explicit about which components it requires, so we structure it so that top level handlers responsible for each route, pick out which components to pass down (effectively a subset of dependencies). This way, for convenience you can always call a function with the whole system (e.g. in a REPL) but you can always be sure about which components it will use as these usually get pulled out via destructuring of arrgs

lukasz18:12:39

(damn it my macbooks keyboard is on its last legs...)

potetm18:12:55

@posobin The only solution I’ve seen for that is to have the whole system available to the top-level route (usually tacked onto the request), which then destructures out the components it needs.

potetm18:12:28

IME this works fine with few problems. You can still find what fn uses what components by keyword searching if you use namespaced keywords.

Gleb Posobin19:12:00

@U07S8JGF7 @U0JEFEZH6 How do you pass the components to the particular route handlers? As separate arguments or make a map from the ones that are needed and pass them? Like I am passing each component as a separate argument, and some routes didn't need the db connection previously, but now I added saving some logging to the db, and now need to pass a new argument to a bunch of functions.

potetm19:12:43

pass the whole system map to every route handler

potetm19:12:48

is what i was suggesting

Gleb Posobin19:12:58

Sorry, not route handler, but the function that the route handler calls.

potetm19:12:17

really just a matter of taste

potetm19:12:31

I usually do separate args

lukasz19:12:40

Similar ☝️ the handler is a component, which passes down the dependencies as a key in the request map, and then each handler function for a particular route unpicks what they need and passes it down as a map

potetm19:12:44

(logging components seems like overkill to me. I use it too prolifically.)

potetm19:12:24

But if a fn takes a bunch of components, I’ll accept a map

potetm19:12:19

I think Stuart’s advice is a little over done. It’s helpful for a fn signature to tell what components it needs. Namespaced keywords provide similar utility.

potetm19:12:37

(arguably namespaced keywords have better utility)

Gleb Posobin19:12:44

Can you expand on how namespaced keywords help?

potetm19:12:03

the upside of separate args for each component is you can tell by looking what the fn might or might not do

potetm19:12:37

if you destructure+namespace-key, you get basically the same experience

potetm19:12:09

plus: spec support and symbol-searching support

lukasz19:12:34

We have a rule to always pass components (even if it's just one) as a map - otherwise some functions would have 5 component args + input. A bit too much for my taste

potetm19:12:07

The downside of passing a big glob of components everywhere is it’s impossible to tell who’s doing what. You can mitigate this by destructuring as high in the stack as you can bear.

Gleb Posobin19:12:50

I just have trouble coming up with how I would namespace components. I have the db, email, s3 components in mind.

Gleb Posobin19:12:23

Are you speccing your system?

seancorfield19:12:44

In most of my code that uses Component, I put the "application" component (everything in the "system" except the web server itself) into the Ring request via middleware created at startup. So every handler has access to the "application" and then it can pass just what sub-components are needed down into the "model" functions that the handler calls.

potetm19:12:25

(component/system-map :app/db (db), :app/monitoring (monitoring))

potetm19:12:28

should work, no?

potetm19:12:04

(I’m honestly not sure. I work in a home-rolled component system. Do not do that. Cannot recommend.)

seancorfield19:12:30

We currently have a :database component that has several datasources nested inside it. If we were starting over, we'd probably use namespace-qualified keywords and not have them nested.

potetm19:12:48

I’ve not spec’d my system. (It’s garbage, and cannot be spec’d.) But there’s nothing stopping you from saying :app/db must be a DataSource or smth along those lines.

potetm19:12:36

I think worst case is you have to un-namespace those keys so that they can play w/ records (which do not support namespacing).

potetm19:12:54

You can do that with component/using

Gleb Posobin19:12:23

Ah, the namespace is not for grouping components, but just so that you can spec them?

Gleb Posobin19:12:53

But you can spec without namespaces too. @seancorfield's example with several datasources makes more sense.

seancorfield19:12:16

Spec'ing is orthogonal to this.

seancorfield19:12:51

And you don't have to use records for Component. The protocols can be implemented via metadata on anything that can support metadata.

💯 3
Gleb Posobin19:12:28

Ooh, didn't know that latter point.

Gleb Posobin19:12:19

@seancorfield You said "in my code that uses Component...", is Component your preferred library for state management, or do you like some other one more?

seancorfield19:12:13

For components that have dependencies, you can use hash maps or records. For components without dependencies, you have much more leeway.

seancorfield19:12:32

In next.jdbc.connection I have a function used as a component.

👍 3
seancorfield19:12:26

At work, we use Component extensively. We have a monorepo with over 100K lines of Clojure, in three dozen subprojects, that are combined into just over a dozen services.

seancorfield19:12:09

One of our apps is structured with every Ring handler being a component with its own dependencies. The rest are less granular. We have components for database connections, caching, our HTML email templating system, ...

seancorfield19:12:05

...our environment/configuration system, our JWT-based security component, Redis pooling, Redis pub/sub, and many more. We have 43 implementations of component/Lifecycle apparently (some in test fixtures where we use mocked components). Looks like we use the metadata protocol approach for three more component implementations. @posobin

noisesmith17:12:01

regarding routes using components, my solution has been for each component to make a wrap-foo function, and to have the routing code extract and use those, rather than passing the entirety of the component to every request handler

noisesmith17:12:29

(the wrap-foo function is provided as a key in the component map)

Gleb Posobin17:12:39

@U051SS2EU what does wrap-foo do?

noisesmith18:12:14

it's a middleware that offers some stateful resource to the handler - eg.

(fn wrap-connection [handler]
  (fn [request]
    (handler (assoc request :db-connection conn))))

noisesmith18:12:07

so the implementation detail that the connection comes from a component is invisible to the handler, which finds some resource under a key, as it would with any other middleware enabled feature

noisesmith18:12:49

this means changing how state is managed doesn't require refactoring request-handling code

noisesmith18:12:33

pragmatically, one of the biggest drawbacks of passing your mega state-blob everywhere is that you need to edit your entire codebase if you need to restructure that blob, this reduces the scope of that problem

noisesmith18:12:03

which is part of what Stuart Sierra is getting at in your original link

noisesmith18:12:41

one way to interpret "structured programming" is to see it as a series of firewalls which minimize the number of changes needed when you add features or change implementation; putting all your resources into a single hash-map that every function sees is effectively an unstructuring, spreading implementing dependencies through the cracks of your structure

seancorfield18:12:58

@U051SS2EU Interesting approach. So you have explicit middleware invocations around handlers directly in your routes definitions? Doesn't that get ugly with dozens of components in play? (or do you only have a few components in your systems?)

noisesmith18:12:34

@seancorfield this was done with an interfacing function that consumes all the wrap-foo functions and composes them in the desired order, returning a composed middleware for the handlers

noisesmith18:12:58

so no, it's not invoked at the point of the handlers

seancorfield18:12:39

Ah, so all handlers have access to all of the (relevant) system component parts separately but not the whole system component? Effectively "unrolling" the compound system to place just the lower-level pieces into the Ring request -- in our case that would go from having the "Application" in the request, where handlers would need to unpack it to get at :database and then :pooled-db or whatever nested resource, to the Ring request having those nested resources directly available under keys in the request?

seancorfield18:12:34

Thus removing knowledge of how the System is structured.

noisesmith18:12:59

precisely - nothing stops using namespaced keys / assoc-in instead of assoc for those resources

noisesmith18:12:19

but the key is that it's not based on knowledge of the system's structure

seancorfield18:12:20

I don't think we've ever found the need to restructure our system-level component in a way that would break functions down the call chain but I can see the benefit of "flattening" the component into the request.

noisesmith18:12:32

it might be that our benefit was partially because we had "weird" components which exposed some implementation detail of external service usage etc., so we did have restructuring of our component map as our architecture evolved

noisesmith18:12:09

and the bottleneck of providing things through functions in middleware helped keep things to an interface

seancorfield19:12:18

Ah, that makes sense then. Yeah, I can definitely see the benefit in decoupling this. I'll bear it in mind for the new web app I build 🙂

mike_ananev17:12:14

@posobin Component library has very OOP-ish approach. Try another libraries like mount https://github.com/tolitius/mount or context https://github.com/redstarssystems/context

p-himik17:12:13

Integrant is a nice one as well.

Gleb Posobin17:12:58

Well I am already using component =)

Gleb Posobin17:12:44

I saw @seancorfield say that mount complects things, and I am starting to agree with him after using it for a while.

✔️ 3
dharrigan18:12:47

My favourite is Juxt Clip

dharrigan18:12:41

I use that for managing all my "dependencies", such as connections to the db, redis, external APIs etc..

dharrigan18:12:58

I have a small example of its usage here with a tiny example project.

p-himik18:12:24

> Project status: Alpha. The only thing that stops me from using clip.

dharrigan20:12:15

most of the core clojure libraries have been alpha until very recently...

dharrigan20:12:49

anyhooo, I've been using Juxt Clip in production, on systems running 24-7, processing millions of messages per day. Zero issues.

dharrigan20:12:22

A testiment to how well it has been written and how well Clojure itself is put together 🙂

p-himik20:12:16

It's not the quality that concerns me, it's the probability of backwards incompatible changes.

p-himik20:12:51

Although, if alpha is stable enough then there might be no reason to update to a newer version that changes some API.

dharrigan20:12:18

That is always a problem with any library one uses. A breaking change at some point. One deals with it, if it arises. However, I'm encouraged by this line The core API is unlikely to change significantly, but there may be some small changes to the system-config format

kenny23:12:25

Does anyone know of a code security scanning tool for Clojure? I imagine it to be quite tricky to do without many false positives. However, it checks a box on security checklists and seems to make certain customers happy.

denik23:12:00

When running a REPL w/ tools.deps is there a way to access the names of the active aliases?

seancorfield23:12:56

@denik No. That information is gone by the time the process starts.

🙏 3
seancorfield23:12:18

The aliases are used to create the classpath that in turn is used to run the REPL.

seancorfield23:12:57

@kenny I've never heard of a useful security scanning tool that works with Clojure code.

kenny23:12:32

Yeah... We're trying Fortify's static scan and I imagine it's going to be completely useless. It's unfortunate that so many people consider scans like this as a base-level security metric to work with you.

seancorfield23:12:07

I've done a huge amount of work with static source code analyzers over the decades and while there are a few constructs which can be identified in nearly all languages as being suspect, in order to "sell" a security tool, it has to be "thorough" and err on the side of false positives, which in turn makes the tools pretty much useless really.

👍 3
seancorfield00:12:29

And security is a big enough industry that feeds on a lot of paranoia and generates a lot of money so it perpetuates 😐

Alex Miller (Clojure team)00:12:09

I am not aware of any that work for Clojure. Fortify will not.

dominicm08:12:06

Running the scan is not the same as reading it, of course :).