Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow reusing of another functions :doc and :arglists #1811

Open
PEZ opened this issue Apr 23, 2024 · 5 comments
Open

Allow reusing of another functions :doc and :arglists #1811

PEZ opened this issue Apr 23, 2024 · 5 comments
Labels
enhancement New feature or request good first issue Good for newcomers
Projects

Comments

@PEZ
Copy link
Contributor

PEZ commented Apr 23, 2024

Is your feature request related to a problem? Please describe.

function-a takes the same arguments as function-b. In fact, function-a calls function-b. Sometimes they also share docs. E.g. with Polylith style interfaces and implementations. Without too much synchronized updating of the function signatures, I'd like for clojure-lsp to show me the same docs/hover info for the two functions.

At the REPL, this works:

(defn function-b
  "Prints `:a`, `:b`, and `:c` from the argment map.
   * :a The a of the map
   * :b The b of the map
   * :c The c of the map"
  [{:keys [a b c]}]
  (println a b c))

(defn function-a
  {:arglists (:arglists (meta #'function-b))
   :doc (:doc (meta #'function-b))}
  [args]
  (function-b args))

It makes the sharing very clear.

(doc function-b) will print:

-------------------------
.../function-b
([{:keys [a b c]}])
  Prints `:a`, `:b`, and `:c` from the argment map.
   * :a The a of the map
   * :b The b of the map
   * :c The c of the map

Describe the solution you'd like

It would be super nice if it worked with clojure-lsp too, making for a shared way for the dynamic and static tools to help with this.

Describe alternatives you've considered

There are some ways to make the sharing a bit less verbose than full copies of the the docs and argument lists, but I don't think none is this clear and complete and also at the same time works for the REPL.

Additional context

Here's a write-up about the general problem and some ways to deal with it with the current clojure-lsp: https://blog.agical.se/en/posts/keeping-the--arglists-of-clojure-functions-dry/

Here's a Clojurians Slack thread where that blog post started from: https://clojurians.slack.com/archives/C03S1KBA2/p1713449139604419 (It also reveals that the actual suggestion in this feature request comes from @ericdallo 😄 )

@PEZ PEZ added the enhancement New feature or request label Apr 23, 2024
@ericdallo ericdallo added this to Low priority in clojure-lsp via automation Apr 23, 2024
@ericdallo ericdallo added the good first issue Good for newcomers label Apr 23, 2024
@yuhan0
Copy link

yuhan0 commented Apr 24, 2024

Note that function-a as defined will fail to compile when its body contains a call to itself:
https://ask.clojure.org/index.php/13834/compiler-exception-dynamically-arglists-metadata-recursive

The interpretation given by @puredanger in the linked thread was that :arglists in the attr-map should be restricted to literal quoted lists, so expressions to be evaluated like (:arglists (meta #'function-b)) should be at least discouraged (if not considered illegal).

@yuhan0
Copy link

yuhan0 commented Apr 24, 2024

To avoid this construction, one has to use alter-meta! on the var after its creation, which I imagine is trickier to statically analyze:

(defn function-a [args] 
  (function-b args)

(alter-meta! #'function-a 
             assoc  
             :arglists (:arglists (meta #'function-b)))
;; or
(alter-meta! #'function-a 
             merge 
             (select-keys (meta #'function-b)
                          [:arglists :doc]))

@didibus
Copy link

didibus commented May 27, 2024

It should also include :macro meta for when you want to do this for a macro.

@didibus
Copy link

didibus commented May 27, 2024

@yuhan0 I'm not sure if it matters here though, since you have one var copying another, it would never call itself no?

@yuhan0
Copy link

yuhan0 commented May 27, 2024

Well, not in the specific example given.. but I can imagine a situation where it could arise:

(defn- frob-impl ;; maybe a protocol method which can't take 0 args
  [{:keys [beep boop]}]
  ,,,)

(defn frob
  "public API with extra convenience arity"
  {:arglists (->> (:arglists (meta #'frob-impl))
                  (cons []))}
  ([] (frob default-opts))
  ([opts] (frob-impl opts)))

Of course it could be rewritten as (frob-impl default-opts), but figuring this out from the error stacktraces is not quite straightforward.

Either way if the syntax itself turns out to be officially unsupported or discouraged, it's probably not a good idea to build a feature around it? (I don't think it was definitively settled in the ask.clojure thread)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
clojure-lsp
Low priority
Development

No branches or pull requests

4 participants