Methodical 0.12.0 is out -- notable features include https://github.com/camsaul/methodical/releases/tag/0.12.0 (click link for screenshot) and a handful of bugfixes. This was inspired by http://christophe.rhodes.io/notes/blog/posts/2018/sbcl_method_tracing/. Probably the most common reason I've heard against using fancy CLOS-style multimethods with :before
, :after
, and :around
aux methods (like Methodical provides) is that they're harder to debug. That certainly used to be true. With the new trace facilities, Methodical multimethods are actually easier to debug than vanilla Clojure multimethods (allegedly). π
somehow I'd like each library to have a "why?" somewhere in their documentation π can someone explain to me for what, or why, I'd use this library? currently, I don't see it
Here are a few reasons.
A) Vanilla multimethods in Clojure either dispatch to a specific method or :default
but there's no in-between. You can't do something like
(defmulti describe-animal-location (fn [animal location] (keyword animal) (keyword location)))
(defmethod describe-animal-location [:bird :tree]
[_ _]
"Bird in a tree")
;; can't do this
(defmethod describe-animal-location [:default :tree]
[animal _]
(str animal " in a tree"))
B) Aspect-oriented programming. Write a single logging method for all your implementations.
(m/defmethod describe-animal-location :before :default
[animal location]
(println "describe-animal-location called with" animal location)
location)
C) next-method
.
In Clojure if you want call the "parent" method, you'd have to do something like
(defmethod describe-animal-location [:songbird :tree]
[animal location]
(println "A songbird is in a tree.")
((get-method describe-animal-location :bird :tree) animal location))
That requires you to know what the "parent method" is. In Methodical you can simply do
(m/defmethod describe-animal-location [:songbird :tree]
[animal location]
(println "A songbird is in a tree.")
(next-method animal location))
D) Programmatic multimethod creation.
Normal multimethods can't be passed around and modified on-the-fly like normal functions or other Clojure datatypes -- they're defined statically byΒ `defmulti`, and methods can only be added destructively, by altering the original object. Methodical multimethods are implemented entirely as immutable Clojure objects (with the exception of caching).
(let [dispatch-fn :type
multifn (-> (m/default-multifn dispatch-fn)
(m/add-primary-method Object (fn [next-method m]
:object)))
multifn' (m/add-primary-method multifn String (fn [next-method m]
:string))]
((juxt multifn multifn') {:type String}))
;; -> [:object :string]
E) Custom invocation behavior: you can write a multimethod that invokes all its implementations -- the canonical use-case for this is creating a shutdown hook.
F) Debuggability: You can use tools that ship with Methodical like trace
to see what methods are getting called and trace calls to a multimethodThanks @camsaul! However I think you missed my point a bit π I understood (some of it, because it does a lot) what features the library has - and that's what you explained here again. The question wasn't "what" - it was "why"? Why do I want to use A) (i.e. what would a use-case be where I couldn't easily handle it some other way)? Or B), C), D), E)? I just don't see the applications of the features. I.e. why I would use this library instead of something else. For example for E) (one of the only ones where you mentioned an application), I can just create a collection of functions and call those during shutdown. Why use multimethod implementations for that? (F, debuggability, I understand the why of course - but if I just want to debug something there are also some other very good libraries/tools out there - I wouldn't want to use a multimethod replacement just for better debugging)
Very cool!
Also: lein-check-namespace-decls
https://github.com/camsaul/lein-check-namespace-decls#add-an-alias-to-depsedn`deps.edn`https://github.com/camsaul/lein-check-namespace-decls#add-an-alias-to-depsedn. Elevator pitch: Tired of people writing ns
forms like
(ns my-namespace
(:require [some.thing [else :as else] [x :as x]]
[another.namespace.b :as b]
[didnt.even.use.this :as unused]
[another.namespace.b :as b]
[another.namespace.a :as a]))
? Wish you had a linter that would complain if you didn't sort your :require
s, or if you had duplicate requires, or unused ones, or if you used prefix-style requires when you prefer non-prefix-style (or vice versa)? You're in luck. https://github.com/camsaul/lein-check-namespace-decls#add-an-alias-to-depsedn can do all that and more, and even works on mixed Clojure/ClojureScript projects with reader conditionals in the ns
form. And now, it supports deps.edn
projects as well as Leiningen projects. (Sorry Boot users)
We've been using this plugin to keep almost 1200 namespaces looking sharp in https://github.com/metabase/metabasefor two and a half years now. Add it to your project today!
lein-check-namespace-decls
isn't a good name for a project that isn't tied to Leiningen anymore, so I'm also soliciting suggestions for a new name here. Preferably a bird-related punNice video! I've got a Robin's nest here and I now I am looking forward to the hatching :D
Iβm curious about what this does that clj-kondo
doesnβt do for ns
forms?
(it checks for duplicates, unused, and unsorted β I suspect the prefix/non-prefix syntax check is unique to l-c-n-d
)
actually now that I look a little more closely, clj-kondo
does 90% of what this linter does. π I think enforcing prefix or non-prefix syntax might be the only difference. I wrote this a while before clj-kondo
had namespace checking stuff tho.
I mostly updated this just because I've been using deps.edn
for new libraries lately and I wanted to port some existing tooling over.
FWIW we're using both this linter and clj-kondo
at Metabase. I'm more of a "can't have too many linters" person
I almost never see prefix requires (and I think theyβre horrible) so theyβd never get into our codebase because they wouldnβt pass PR review π
@camsaul The README says βAdd a :check-namespace-decls key to your project.clj to configure the way refactor-nrepl cleans namespaces. All options are passed directly to refactor-nrepl.β β how do you control this via CLI / deps.edn
invocation?
(looking at the source, I could just pass all those options as :exec-args
right? might want to mention that in the readme)
I like to have things like that that can be determined programmatically enforced by linters if possible.
Sorry, I guess I need to go thru the README again and make sure it makes sense now that it works with either Leiningen or deps.edn. You just pass all the options as :exec-args
instead of under the :check-namespace-decls
key in project.clj
e.g.
{:aliases
{:namespace-checker
{:extra-deps {lein-check-namespace-decls/lein-check-namespace-decls {:mvn/version "1.0.4"}}
:exec-fn check-namespace-decls.core/check-namespace-decls
:exec-args {:prefix-rewriting false
:source-paths ["src" "test"]}}}}
Also on a side note RE prefix namespaces. We were using them at Metabase for a long time. I came around to the benefits of flat namespace declarations a while back but I didn't want to deal with converting a thousand namespaces over all at once so we kept using prefix ones for a long time. I finally did a big PR to convert everything in January: https://github.com/metabase/metabase/pull/14281
I ended up writing some Emacs Lisp to find and then iterate over every Clojure file in the entire project, load the namespace, and call cljr-clean-ns
on them so I was able to do it all programmatically. Still a big change tho.
So the linter helps ensure I don't end up having to do that sort of PR again π
@seancorfield while we're on the subject of linters do you know if anything else does something like this? https://github.com/camsaul/lein-docstring-checker This is next on my list to port. This one is about 4 years old at this point. I don't think a lot of people care enough about docstrings to enforce them with a linter but I've found that having an "either document it or make it private" rule works well for libraries. For big projects it makes refactors easier too since stuff ends up being private more often you don't have to spend time grepping the codebase before renaming stuff (or for Metabase, we support 3rd-party database drivers, so we have to be careful about breaking stuff 3rd-party drivers might be using, so keeping more stuff private lets us move faster and make more improvements to the core project) I don't want to waste time porting it and find out that clj-kondo/Eastwood/Bikeshed/Kibit/whatever added it last year π
Thatβs built into clj-kondo
β I rely on that one a lot!
haha no way. I guess I need to pay more attention to what's going on with clj-kondo
. π
I still use Eastwood as part of CI for one project but I mostly just rely on clj-kondo
for everyday use now, esp since itβs integrated with editors and LSP etc and so it can run all the time while you are typing.
I think there are a lot of very specialized Leiningen plugins that really ought to just be archived at this point β they date back to a βsimplerβ era π
Yeah. I might have to archive some of my old liters now. Better to pool efforts on Kondo and submit PRs there than to spend time maintaining old stuff
and last but not least zprint
supports formatting according to "How to ns"
https://github.com/kkinnear/zprint/blob/master/doc/reference.md#how-to-ns
Maybe you can find a name if you ask the question βwhere's the poop in a Robin's nest?β. https://youtube.com/watch?v=lG7OmThrq5g
Cool video! π My education for the day π
clj-kondo new release: 2021.06.18 β¨
New
- Lint arities of fn arguments to higher order functions (`map`, filter
, reduce
, etc.)
E.g. (map-indexed (fn [i] i) [1 2 3])
will give a warning about the function argument not being able to be called with 2 arguments.
- Add map-node
and map-node?
to hooks API
Enhanced / fixed
- Disable redefined-var warning in comment
- :skip-comments false
doesn't override :skip-comments true
in namespace config
- False positive duplicate set element for symbols/classes
https://github.com/clj-kondo/clj-kondo/blob/master/CHANGELOG.md#20210618
Happy linting!
So, I've been experimenting with adding a real prolog in Clojure. It's not yet feature-complete, but as a proof-of-concept, it's working for some examples! https://github.com/mauricioszabo/spock
If there are no solutions to a problem, does it respond with no
? That was always one of things I found adorable about Prolog π
(also, please make it work with deps.edn
and the CLI π )
Here's an example of n-queens example :D
@seancorfield hahahaha, not yet π. Currently it returns an empty list only. But I'll handle these cases
> How many Prolog programmers does it take to screw in a light bulb? > No.
BTW, I just saw that there's some confusion on the conversion (keywords translate to prolog atoms, but symbols sometimes become atoms, sometimes become vars).
I think I'll make symbols become atoms all the time, and keywords become vars all the time. This makes more obvious how to unify things for example, and solvers will be less confusing