Fork me on GitHub
Cam Saul01:06:26

Methodical 0.12.0 is out -- notable features include (click link for screenshot) and a handful of bugfixes. This was inspired by 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). 😎

👏 14

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

Cam Saul00:06:55

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)
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]
      multifn'    (m/add-primary-method multifn String (fn [next-method m]
  ((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 multimethod


Thanks @U42REFCKA! 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)

Cam Saul01:06:49

Also: lein-check-namespace-decls`deps.edn` 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. 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 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 pun

metal 5

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)

Cam Saul02:06:35

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 🙂


@U42REFCKA 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)

Cam Saul02:06:17

I like to have things like that that can be determined programmatically enforced by linters if possible.

Cam Saul02:06:39

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.

  {: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"]}}}}

Cam Saul02:06:02

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: 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 😅

Cam Saul02:06:08

@U04V70XH6 while we're on the subject of linters do you know if anything else does something like this? 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!

Cam Saul02:06:04

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 🙂

Cam Saul03:06:26

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"

👀 6

Maybe you can find a name if you ask the question ”where's the poop in a Robin's nest?”.


Cool video! 🙂 My education for the day 🙂

😄 3

Nice video! I've got a Robin's nest here and I now I am looking forward to the hatching :D

🐣 3

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 Happy linting!

🎉 104
clj-kondo 48
🚀 27

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!

🙂 21
👏 12
🎉 9

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


@U04V70XH6 hahahaha, not yet 🙂. Currently it returns an empty list only. But I'll handle these cases

Jacob O'Bryant22:06:34

> 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

✔️ 3