Fork me on GitHub
#clojure
<
2024-05-26
>
didibus17:05:06

I've got an issue. I had a namespace where the implementation was in the same namespace as the exposed API, but made private. Now I want to switch this so that I move the impl into an impl namespace, and it's all public. That makes tests, and certain constructs that are hard to make private easier to manage. But, I have some functions that were using both other public API functions and some private impl functions. So the impl function would need to depend on the public API namespace, which in-turn depends on the impl namespace, which is circular, how can I solve this?

p-himik17:05:11

Move all functions to the impl ns, create public wrappers in the API ns.

1
seancorfield17:05:33

That's essentially what Polylith encourages: then your impl functions can all call each other directly and your interface functions are the pure public API (and can be written in alphabetical order!).

didibus18:05:51

Hum, would you do:

(def foo impl/foo))
or
(defn foo [...]
  (impl/foo ...))
Also, with the former approach, I always forget how to do it for macros or vars so that it brings along the doc-string and meta and all that too.

p-himik18:05:44

> so that it brings along the doc-string and meta and all that too. Do you need all that in the private impl?

didibus18:05:05

I can move the doc and meta to the public one I guess. But then when I work on the impl, I don't get to see the doc 😛

didibus18:05:24

I do remember there being a way to bring over the doc and meta along.

p-himik18:05:18

If it's only the docstring that you really need, you can easily "inherit" it by referencing the impl ^:doc meta in the API meta. Otherwise, there's stuff like Potemkin, which to me seems to be generally discouraged.

didibus18:05:15

Hum, no I think you could do something like (def foo impl/foo) and maybe also after call with-meta or vary!-meta.

didibus18:05:31

Unless that's just what potemkin does?

p-himik18:05:06

You can, but you still have to filter out the right meta to avoid copying stuff like :line.

didibus18:05:52

Hum think_beret . I see. That's fine. It's mostly doc I need.

didibus18:05:36

So I think for macro it was: (def ^:macro macro impl/macro)

seancorfield18:05:00

I use a defn (or defmacro) wrapper -- and I write my public docstrings for users and my private docstrings for maintainers/implementations.

1
didibus04:05:32

You're not bothered by the extra function call hop ?

seancorfield05:05:52

It'll get optimized away by the JVM...

seancorfield05:05:11

(or if it doesn't, it's fast enough that it simply doesn't matter)

didibus05:05:44

I might create a release script that just copies over the doc from the impl into the public namespace. I don't like having my doc in both places haha.

seancorfield05:05:07

As I said, I take it as an opportunity to have user docs vs maintainer docs 🙂

didibus05:05:34

Ya, I was trying that, but it's the same function, I couldn't really think what I would write different for the impl

p-himik05:05:07

So why not just reuse the meta field?

didibus05:05:30

@U2FRKM4TW What do you mean?

didibus05:05:35

Well, I decided to do this anyways, since repl based doc will work with this, just not lsp.

exitsandman06:05:50

It's lisp, put your definition in a macro-thunk and expand it twice. Jokes aside, I think that this is one of those things that are annoying but don't really amount to much in the grand scheme of things. Writing separate docs takes being really on top of your process but it's probably ideal to ensure full separation between layers and approachability for the code without an IDE, but I personally use my own defnwrapper , defvarwrapper and defmacrowrapper macros that wrap and copy over the relevant meta because I barely touch my code without Calva spun up and worst come to worst I can jump to the private definition anyway.

👍 1
pez06:05:37

There’s a feature request on clojure-lsp about supporting delegated arglists: https://github.com/clojure-lsp/clojure-lsp/issues/1811

👍 1
didibus06:05:44

By the way, I remembered for macros you have to do:

(def ^macro time (var-get #'impl/time))

p-himik06:05:41

Or you can write macros so that the impl is in a function and the function is used in two separate macros.