This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-05-17
Channels
- # aws (3)
- # beginners (104)
- # boot (11)
- # calva (3)
- # clj-kondo (1)
- # cljdoc (6)
- # cljs-dev (23)
- # cljsrn (1)
- # clojure (144)
- # clojure-dev (54)
- # clojure-europe (6)
- # clojure-italy (2)
- # clojure-nl (26)
- # clojure-spec (6)
- # clojure-sweden (1)
- # clojure-uk (13)
- # clojurescript (38)
- # core-async (9)
- # cursive (14)
- # data-science (3)
- # datascript (22)
- # datomic (17)
- # figwheel (1)
- # fulcro (4)
- # graphql (6)
- # hoplon (59)
- # jackdaw (2)
- # jobs (6)
- # jobs-discuss (44)
- # juxt (14)
- # leiningen (1)
- # luminus (3)
- # nrepl (3)
- # off-topic (12)
- # re-frame (24)
- # reagent (7)
- # reitit (7)
- # rewrite-clj (1)
- # schema (1)
- # shadow-cljs (37)
- # spacemacs (4)
- # sql (25)
- # testing (12)
- # tools-deps (11)
- # utah-clojurians (1)
how do y'all go about performing major refactors?
any recommendations as I embark on my own?
(major being about 5500 lines of source total, lol)
I'd want to start out with pretty good test coverage, to be honest, for a refactor of that scale.
Also, "refactoring" for me tends to be small, incremental changes that I can verify in the REPL, via spec, or a small set of tests/test expressions. Tackling 5,500 lines sounds more like a "rewrite"...
I'm mostly attempting to spread what is currently one giant namespace across 11 files into a bunch of namespaces, as dealing with the interconnectedness is reaching high complexity
my goal is to actually change as little as possible, more some much needed organization
Depending on what the code currently does, maybe you can come up with some scenarios where you could generatively test the old code and the new code produce identical results for random, conforming input data?
Not a bad idea in general! this is a game-engine, so generating correct random data is a little harder. Thankfully, I have a fairly extensive integration test suite so I can rely on that for now
thanks for the ideas!
As a sanity check on namespace size, we have just under 300 src
namespaces in our codebase at work. Less than a dozen have more than 1,000 lines. Only one exceeds 2,000 lines (it has 2,017 lines). The total is 65k lines.
holy crap
I'm glad to know "many namespaces" works even when larger
We also have just over 300 test
namespaces, totaling just under 20k and only two namespaces exceed 1,000 lines (one is just over 1,000, one is just under 2,000).
Code organization is an ongoing issue. We're pretty much constantly looking at small refactorings to improve navigability and cognitive load.
that's my biggest issue: navigability and cognitive load
We started with a pretty flat structure and some fairly large namespaces. Then we moved to a more nested structure and smaller, more numerous namespaces.
interesting, thank you
Clojure build/config 50 files 1000 total loc
Clojure source 276 files 64691 total loc,
3065 fns, 625 of which are private,
411 vars, 29 macros, 56 atoms,
504 specs, 19 function specs.
Clojure tests 307 files 19396 total loc,
4 specs, 1 function specs.
We have a simple shell script that counts lines and certain forms.We have lots of little EDN files for configuration -- and each subproject in our monorepo has its own deps.edn
file.
That's super cool
I'm looking at your code to parse Netrunner data. I do similar, less elegant things, with Star Wars Destiny, Magic, and Warhammer Champions data.
haha hey thanks! I'm pretty sure you're the only other person to have ever looked at it!
@thegobinath going gud
I was wondering if anyone else would chip in. I've heard pros and cons of both.
We're reorganizing our code because some namespaces have become sort of "kitchen sinks" of stuff that's only vaguely related to the original purpose (and should have been put elsewhere in the first place).
We've also evolved our thinking a lot on just naming stuff in the seven years we've been working on this codebase.
(it still seems amazing to me that we have had Clojure in production for seven years now!)
Yeah, it's really hard to avoid having some sort of "util" namespace I think 🙂
and that the likelyhood of having to reload that huge NS is steeply increased
premature splitting of namespaces is a sin imo. I prefer to grow an ns until natural splits reveal themselves.
lots of util namespaces often contain a significant number of fns which never see reuse across a codebase in my experience. And when they do it is often limited to a couple instances.
not saying that’s a rule, but I’ve actually dumped disparate namespaces back into one big namespace to tease out the right splits after the fact.
Premature splitting forces new names into existence which often aren’t the names that capture what’s actually going on in the domain.
I strongly believe applications are better organised first by vertical/features then inside them horizontally.
There are many reasons the main being:
1. People tend to work on a feature at a time.
2. Parallelising work is easier to do by feature, so structuring your code by feature means less merge conflicts.
3. Code is much easier to navigate, and it’s way easier to find things and on-ramp people. You can get a good idea on what an application does by looking at it’s top level namespaces.
4. When creating new code it’s much more obvious where to put it; which leads to greater consistency.
Cross cutting concerns can be colocated under an app.concerns
, or even another root namespace, i.e. an app-lib
ns; and eventually moved into separate libraries if it makes sense.
The biggest barrier to doing this, is that most clojure app templates organise horizontally by the framework first, e.g. they create you an app.handler
, app.model
namespace first.
I also agree that you shouldn’t prematurely split; beyond giving each feature its own ns.
so a trivial app.feature
ns may contain the view and model code (still separated but together)… at some point you may break it out into app.feature.view
and app.feature.model
. A good reason to do this is when you start wanting to test the models and views independently; so it will give you parallel test ns’s.
Libraries on the other hand make often make more sense to organise horizontally… though usually each library is a horizontal anyway.
@rickmoynihan have you seen the behaviour programming paradigm?
I don’t think so
The demo can explain it better, but it's essentially about implementing each feature independently
looks like it’s based on CSP — or something similar
but that’s just based on a 20 second glance
I'd go a little deeper, that seems important but isn't. I think the async nature is important though. The important thing are the events and then collecting all the behaviours and make some decision based on interpreting them in a whole.
:thumbsup: yeah I see that… seems to me that the logical conclusion to this stuff is event streaming and a query language on events… which is where a bunch of independent efforts have ended up… e.g. FRP. Years back I experimented with using Rx to create a reactive model of XMPP… It’s definitely an approach that has its appeal, but the general problem with FRP is time leaks… and I’m not sure if anyone has solved that one. Though FRP doesn’t necessarily mean what it used to.
Rx did introduce me to marble diagrams though — which are pretty great for this kinda thing.
It's kind of like event streaming, but I assumed you asked all your producers if they had a message before moving on.
isn’t that still a potential form of time leak? How long must the producer hold the message for?
For JavaScript, I'm not sure you get a choice. It may operate on a closed world assumption, and it will queue up productions while it waits.
It is possible to use functions in lein project.clj file. But it doesn't work here: :essthree {:deploy {:type :uberjar :bucket "uberjars.foo" :path "bar" :artifact-name ~#(str (:version %)) }}
Any suggestions for a Clojure library doing CLI args parsing with the ability to add arbitrary sub parsers like Python's argparse? I tried tools.cli
which seems too low level and have to write a lot of manual parsing and boilerplate myself. Also tried https://github.com/l3nz/cli-matic which is based on tools.cli and is quite nice and declarative but it doesn't allow nested parsers yet.
@rahul080327 I saw this yesterday. I don’t know if it supports what you want, but it looks cool. maybe it works from clojure? https://picocli.info/
@borkdude Seems like it has a programmatic API too: https://picocli.info/picocli-3.0-programmatic-api.html
could be useful
Sure, trying to get to work on this Graal based CLI im putting together
https://github.com/bob-cd/wendy its a CLI to a CI tool I've been building for a while now. Was in Python, now "Graaled" my way back 😛
Im trying to implement https://github.com/bob-cd/wendy/blob/master/docs/commands.md
whats in the repo works for a single command, but was having a nightmare to extend it
I see there’s a Clojure issue here: https://github.com/remkop/picocli/issues/231 so probably not suitable right now
@borkdude Those were precisely my thoughts too. The best contender for a Clojure wrapper I see is https://argparse4j.github.io/ but was hoping if there is a more data driven solution out there
could definitely try that. picocli's features seem quite cool though. @dominicm whats the best way of doing a Java interop with java annotations?
right. Im not familiar with this kind of interop. Will try looking into it.
@dharrigan Im not sure if it supports subparsers
Hm what’s wrong with tools.cli
? https://github.com/clojure/tools.cli#in-order-processing-for-subcommands
> Note that the options to log are not parsed, but remain in the unprocessed arguments vector. These options could be handled by another call to parse-opts from within the function that handles the log subcommand.
Define options, parse, check if there’s an arg, parse its options against another sub-command definition
@artpsdev I felt its too low level and involves me writing a lot of boiler plate for validation and dispatch. https://github.com/l3nz/cli-matic built on top of it has a more declarative API and spec validation, but hides the subcommand aspect
the problem came from writing
(defn validate-args
"Validate command line arguments. Either return a map indicating the program
should exit (with a error message, and optional ok status), or a map
indicating the action the program should take and the options provided."
[args]
(let [{:keys [options arguments errors summary]} (parse-opts args cli-options)]
(cond
(:help options) ; help => exit OK with usage summary
{:exit-message (usage summary) :ok? true}
errors ; errors => exit with description of errors
{:exit-message (error-msg errors)}
;; custom validation on arguments
(and (= 1 (count arguments))
(#{"start" "stop" "status"} (first arguments)))
{:action (first arguments) :options options}
:else ; failed custom validation => exit with usage summary
{:exit-message (usage summary)})))
for every subcommand using tools.cli
plus a dispatcher like this:
(defn -main [& args]
(let [{:keys [action options exit-message ok?]} (validate-args args)]
(if exit-message
(exit (if ok? 0 1) exit-message)
(case action
"start" (server/start! options)
"stop" (server/stop! options)
"status" (server/status! options)))))
Let the root command validate common args and sub-commands validate their own args so the life is simpler.
i totally agree. what im having trouble with is writing all the (cond ...)
and the ifs
by hand. And that makes it more error prone. cli-matic
for example gives me the ability to define the whole thing in a map:
(def CONFIGURATION
{:app {:command "toycalc"
:description "A command-line toy calculator"
:version "0.0.1"}
:global-opts [{:option "base"
:as "The number base for output"
:type :int
:default 10}]
:commands [{:command "add"
:description "Adds two numbers together"
:opts [{:option "a" :as "Addendum 1" :type :int}
{:option "b" :as "Addendum 2" :type :int :default 0}]
:runs add_numbers}
{:command "sub"
:description "Subtracts parameter B from A"
:opts [{:option "a" :as "Parameter A" :type :int :default 0}
{:option "b" :as "Parameter B" :type :int :default 0}]
:runs subtract_numbers}
]})
also gives me the function dispatch too
I’m guessing the consensus might be that this is overkill and/or maybe too low level … but I think from now on, whenever I need to parse anything (including command line stuff), I’m going with instaparse. It’s so awesome and you have so much control. instaparse + a cond statement ftw 😉
Thats the all conquering solution i guess 😛
if ya need to parse, might as well do it right! lol
Does anyone know how to find from which remote repo (ex. enonic, maven, clojars) that a project dependency wall pulled from using leiningen?
Look in ~/.m2/repository/<group>/<name>/<version>/_remote.repositories
- that should list the repo the artifact came from:
~> cat ~/.m2/repository/ring/ring/1.7.1/_remote.repositories
#NOTE: This is an Aether internal implementation file, its format can be changed without prior notice.
#Mon Apr 22 09:08:21 EDT 2019
ring-1.7.1.jar>clojars=
ring-1.7.1.pom>clojars=
https://memegenerator.net/img/instances/x300/84483335/what-does-quote-say-to-repl-not-now.jpg
Regarding quotes
(meta ^:m 'm)
returns nil
so replacing the '
with (quote
is the only way out or is there something else?
I’m often confused by the ^ meta reader, it seems to have some surprising behavior in certain cases (like this) so the only time I use it now is in a def
to attach meta to a var
user=> ^:foo '1
1
user=> '^:foo 1
Syntax error reading source at (REPL:3:0).
Metadata can only be applied to IMetas
it might be entirely consistent 😅 I just find it doesn’t match what I want to do most of the time, so my tiny brain figured out with-meta
is usually what I want
interesting idea
Hoping someone here might have some experience: I'm trying to set a log level inside a log4j.properties
file just for a specific clojure namespace. Is there an easy way to do this that doesn't involve using Java interop to set the logging level inside the clojure code itself?
are you sure that is time is being burnt in jdbc and not in your db?
If I recall correctly — but I am by no mean an expert — insertion performance depends on how many indices there are on your rows
You could try inserting the same rows using the mysql client/psql, see how long that takes. I wouldn’t expect something like jdbc to add any large amount of overhead. But this is just a hunch, you should ask Sean Corfield when he’s around!
@gtzogana How exactly are you inserting multiple rows?
So you said. What exactly does your call look like?
If you provide multiple maps, it does multiple inserts (per the docs). If you provide a vector of column names and a vector of rows (a vector of vectors of row values), it tries to do a single insert -- but the actual behavior is, like so many JDBC-related things, database-specific. For example, PostgreSQL requires that you provide a special option.
The auto-gen'd docs haven't been updated for a while (infrastructure issues, I believe), but I've recently added this paragraph to the docstring for insert-multi!
Note: some database drivers need to be told to rewrite the SQL for this to
be performed as a single, batched operation. In particular, PostgreSQL
requires :reWriteBatchedInserts true and My SQL requires
:rewriteBatchedStatement true (both non-standard JDBC options, of course!).
These options should be passed into the driver when the connection is
created (however that is done in your program).
So if you're on PostgreSQL or MySQL, you'll need to provide the appropriate option in your db-spec when you create your DB connection.
(and, of course, they're different options -- thank you JDBC 😞 )
BTW, there's #sql for SQL-specific / JDBC-specific questions -- which I'm much more likely to see as I don't have that channel muted (but I do have this one muted).
(JDBC is one of the biggest piles of frustrating, non-standard, quirkiness I've ever had to work with!)