This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-05-26
Channels
- # announcements (7)
- # babashka (42)
- # beginners (349)
- # chlorine-clover (9)
- # cider (16)
- # circleci (2)
- # clj-kondo (6)
- # cljs-dev (61)
- # cljsrn (15)
- # clojure (95)
- # clojure-europe (11)
- # clojure-italy (2)
- # clojure-nl (4)
- # clojure-spec (4)
- # clojure-uk (24)
- # clojurescript (21)
- # conjure (2)
- # core-async (8)
- # cursive (12)
- # datascript (2)
- # emacs (4)
- # exercism (1)
- # figwheel-main (86)
- # fulcro (27)
- # graalvm (4)
- # helix (36)
- # hoplon (3)
- # interop (44)
- # kaocha (6)
- # lein-figwheel (4)
- # malli (7)
- # meander (9)
- # off-topic (95)
- # pathom (33)
- # pedestal (13)
- # re-frame (20)
- # reitit (3)
- # shadow-cljs (102)
- # tools-deps (14)
- # xtdb (16)
(defn load-from-path [path]
(atom ... path ...))
(defn current-value [config]
@config)
(defn check-for-updates [config]
...)
(:require [your.company.config :as config])
(defn make-system []
(let [config (config/load-from-path)
queue (create-queue)]
(start-webserver {:context-for-requests {:config config :queue queue}})))
and wanting different parts of your code that rely on those values to update themselves
which would necessitate either some kind of restarting of the whole system or of keeping a shared reference to mutable state
but you should never be creating an atom of the s3 client which you correctly identify
(defn create-s3-client [{:keys [whatever values you need from config]}]
(AWSS3Client. ...))
(... any functions you want to put here, but probably you don't need any if you use the client apis directly ...)
(defn make-system []
(let [config (config/load-from-path)
s3 (s3/create-s3-client @config)]
(start-webserver {:context-for-requests {:config config :s3 s3}})))
(defn create-s3-client [stateful-config]
{:config-reference stateful-config})
(defn make-put [s3-client stuff]
;; What your code now calls s3-client is a stateful object, but not the same as you got from the library.
;; So you need to echo through any api calls you want
(let [current-config @stateful-config
client (S3Client. current-config)] ;; In this case the client isn't stateful, its dependencies are
(aws.s3/put! client stuff))) ;; But if you needed to, you could also store the client along, manage deps, etc
so in general - namespaces defining objects and functions in those namespaces being considered "attached" to those structures
you can make it work somewhat if you use something like mount, which will give you the machinery to mock out namespace vars for testing
the missing part here is how to do this stuff in a REPL, but you can get to that later
ymmv and it depends on the use case, but I would still prefer a design that separates what from how instead of one where every function has an extra parameter
public enum S3Client {
private static final Config config = Config.INSTANCE;
INSTANCE;
public void putObject(Object obj) {
new AWSS3Client(config.port()).send(config.rateLimit());
}
}
public enum Config {
private static final Map<String, String> configVals = ...;
INSTANCE;
public String port() {
return this.configVals.get("port");
}
public String rateLimit() {
return this.configVals.get("rateLimit");
}
}
so write your code like you would write java - if you need to make a "new" api for things, give those things their own "class" and "methods" and "constructor"
and if you manage to reduce dependencies on stateful components to be behind an interface, you can always protocol that stuff up for easier testing
the trick, I think, is to realize that the same advice as java really does still apply (minus paranoia about private)
Thanks guys, this advice is incredibly helpful. It's a difficult thing to quiz people about since as you can all see a lot of people have same/similar intuitions but different angles and taking a look at those perspectives really supports further thinking.
Ya, it's tricky. The idea is simple: Reduce the surface area of each part of the code to its minimum. But actually doing so is challenging. And generic advice doesn't always help much when dealing with concrete situations
TIL:
(merge {} [:a 3] [:b 7])
; => {:a 3, :b 7}
(into [] {:a 3, :b 7})
reverses the operation
@vincent.cantin because (conj {} [:a 3] [:b 7])
is {:a 3, :b 7}
yes .. I took a look at the implementation โฆ it allowed a bug to pass unseen, but that was my bad ^_^โ
Oh? What bug?
@danieltanfh95 seq
on a hash map produces a sequence of MapEntry
items which look like pairs (vectors).
user=> (type (first (seq {:a 3, :b 7})))
clojure.lang.MapEntry
my code was (merge context bindings)
, where bindings was [:a 1]
instead of {:a 1}
.
It worked for [:a 1]
but not for [:a 1 :b 2]
.
Ah, interesting. Yes...
hi guys! i'm looking at this question on so: https://stackoverflow.com/questions/62007445/infinitely-recursive-lazy-sequence-appears-as-empty-sequence-in-clojure
(def primes
(remove (fn [x]
(some #(zero? (mod x %)) primes))
(iterate inc 2)))
so i caught myself not getting what semantic rules are below the fact that primes
inside the remove
predicate is just the realized part of overall primes
sequence.
printing it out, like this:
(fn [x]
(println (realized? primes) primes)
(some #(zero? (mod x %)) primes))
says
> false ()
> true (2)
> true (2 3)
> true (2 3)
> true (2 3 5)
> true (2 3 5)
> true (2 3 5 7)
> ...have you figured this out? i don't get how the evaluation of primes
terminates. just looking at the code i would guess that this would also lead to a stack overflow like the example presented in the SO question
i would expect for (realized? primes)
to be true after the first lazy sequence has been realized. primes
is of the form (lazy-seq (cons 2 (lazy-seq (cons 3 (lazy-seq ...)))))
, so (realized? primes)
just tells you that the first lazy sequence in the chain has been realized.
if you want me to explicitly mention your username in the post, instead of just linking to the post, let me know and i can update it
Iโve not done any Prolog or Datalog, but Iโd like a suggestion on dealing with a problem. I have to split a number of people into batches of certain sizes. There are constraints on certain people being in the batch or being in different batches, and other constraints based on other factors. This sounds like something I can use core.logic for. Am I wrong? If not, can anyone provide any pointers on what I should be looking for?
Hey is me again. I have done some reading on all the things you mentioned yesterday and it seems 2 strategies seem to address this "stateful component" problem. 1. Component lib by Stuart Sierra, 2. Pining the initialization on the upper namespaces. What is the general community take on this? I am pretty sure both aproaches are better than using atoms(I can see how atoms are this giant hatchets for this jungle). To me it feels like component lib introduces some semantics and indirection(which is "not great, not terrible"), but provide clean containment of functions and initialization. Where separating initialization from continuous invocations seems less dev friendly but vastly more explicit.
You might want to look at integrant and JUXT clip as alternatives with less semantics
the state of the application can be mostly contained within databases, which can be thought of a giant atom with good querying ability anyway, no?
@danieltanfh95 50k loc that doesn't assume this makes difficulties in trying to convert ๐ trying babysteps and containing stateful parts seems the least radical step
We use Component very heavily at work but we were using globals before that. Yes, it's a bit hard to convert, but what you can do @roguas is to start writing components and have the start
function set up your globals (instead of how you're setting them now) and then slowly migrate code to use the components directly instead of referring to the globals.
We'll still in that process -- we have a 95k loc codebase and we still have a handful of globals left. The refactoring is ongoing ๐
Do I wish we'd started out with Component? Hell yeah! Do I at any point regret switching to Component? Nope.
That's true, real world code bases are big beasts, it's good to keep in mind while you discuss best practices, a real code base will rarely be able to really keep up with all of them.
Given all the advice yesterday, I decided to lurk more on github on some production code and found this one: https://github.com/metabase/metabase/search?p=1&q=%22atom+%22&unscoped_q=%22atom+%22 and while a lot of the results are test/* where using atoms may be a little more permissive since its outskirts, it still remains that atom becomes this hatchet for their state requirements
Hum, ya it's hard to say from a quick glance. I think some might be unnecessary and others seems totally fine
An issue Iโve seen newbies get tripped up by a lot is the distinction between maven artifact names and namespaces; is there any canonical documentation or excerpts to tutorials that covers that well already? Otherwise Iโm inclined to write something
no (and this trips up newbie Java users too)
particularly that they are two similar, often overlapping, things with only an arbitrary relationship without even norms much less standards
Rich and I have actually spent a bunch of time talking about this (he talks about it in Spec-ulation too iirc)
talking about it in the context of add-lib for example
i think i hit on this for a second because git libs take an artifact name in deps.edn but that name is essentially meaningless right?
there is some guidance on this particular issue in https://clojure.org/guides/deps_and_cli#_using_git_libraries
"When artifacts are deployed in a Maven repository, itโs a best practice to use a groupId (the first part of the name) that is something you control (usually via DNS or trademark). In the case where you have neither, you can instead combine the name of a site that establishes identities (like GitHub) with your identity on that site, here github-yourname
."
the second sentence is guidance from Rich and I and not something I've ever seen anywhere else, fyi
@lvh FWIW, that's why I decided that clj-new
should prefer <org-or-username>/<project-name>
and create a folder called <project-name>
with a pom.xml
that had group <org-or-username>
and artifact <project-name>
and the main namespace would be in src/<org-or-username>/<project-name>.clj
to try to get some sort of sane standardization at least going forward in the CLI/`deps.edn` world.