announcements

Project/library announcements ONLY - use threaded replies for discussions. Do not cross post here from other channels. Consider #events or #news-and-articles for other announcements.
camsaul 2021-06-18T01:04:26.265900Z

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). 😎

5πŸ‘
Azzurite 2021-06-18T11:26:08.285Z

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

camsaul 2021-06-22T00:58:55.319700Z

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 multimethod

Azzurite 2021-06-25T06:27:28.337Z

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

eggsyntax 2021-06-18T01:17:43.266300Z

Very cool!

camsaul 2021-06-18T01:39:49.273700Z

Also: lein-check-namespace-declshttps://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 pun

2🀘
richiardiandrea 2021-06-18T15:03:07.287300Z

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

1🐣
seancorfield 2021-06-18T01:49:52.274200Z

I’m curious about what this does that clj-kondo doesn’t do for ns forms?

seancorfield 2021-06-18T01:51:45.274400Z

(it checks for duplicates, unused, and unsorted β€” I suspect the prefix/non-prefix syntax check is unique to l-c-n-d)

camsaul 2021-06-18T02:00:35.274700Z

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

1
seancorfield 2021-06-18T02:02:29.275Z

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 πŸ™‚

seancorfield 2021-06-18T02:04:09.275200Z

@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?

seancorfield 2021-06-18T02:05:11.275400Z

(looking at the source, I could just pass all those options as :exec-args right? might want to mention that in the readme)

camsaul 2021-06-18T02:05:17.275600Z

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

camsaul 2021-06-18T02:06:39.275800Z

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

camsaul 2021-06-18T02:13:02.276Z

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 πŸ˜…

camsaul 2021-06-18T02:34:08.276700Z

@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 πŸ’€

seancorfield 2021-06-18T02:43:28.277100Z

That’s built into clj-kondo β€” I rely on that one a lot!

camsaul 2021-06-18T02:45:04.277400Z

haha no way. I guess I need to pay more attention to what's going on with clj-kondo. πŸ˜…

seancorfield 2021-06-18T02:46:19.277600Z

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.

seancorfield 2021-06-18T02:46:58.277800Z

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 πŸ™‚

camsaul 2021-06-18T03:03:26.279300Z

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

1
vemv 2021-06-18T03:23:02.279800Z

See also https://github.com/gfredericks/how-to-ns

1πŸ‘
richiardiandrea 2021-06-18T03:26:17.280100Z

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

2πŸ‘€
pez 2021-06-18T05:34:40.282200Z

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

seancorfield 2021-06-18T05:49:04.282600Z

Cool video! πŸ™‚ My education for the day πŸ™‚

1πŸ˜„
borkdude 2021-06-18T09:34:54.283400Z

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!

169πŸš€9✨35πŸŽ‰
Azzurite 2021-06-18T11:26:08.285Z

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

richiardiandrea 2021-06-18T15:03:07.287300Z

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

1🐣
mauricio.szabo 2021-06-18T22:10:35.292400Z

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

4πŸ‘18πŸ™‚4πŸŽ‰
seancorfield 2021-06-18T22:14:59.292600Z

If there are no solutions to a problem, does it respond with no? That was always one of things I found adorable about Prolog πŸ™‚

2
seancorfield 2021-06-18T22:15:47.292800Z

(also, please make it work with deps.edn and the CLI πŸ™‚ )

mauricio.szabo 2021-06-18T22:16:00.293Z

Here's an example of n-queens example :D

mauricio.szabo 2021-06-18T22:17:02.293200Z

@seancorfield hahahaha, not yet πŸ™‚. Currently it returns an empty list only. But I'll handle these cases

2021-06-18T22:29:34.293600Z

> How many Prolog programmers does it take to screw in a light bulb? > No.

11
mauricio.szabo 2021-06-18T23:54:16.294200Z

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).

mauricio.szabo 2021-06-18T23:55:28.294400Z

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

1βœ”οΈ