Fork me on GitHub
#clojure
<
2021-06-05
>
Arun Mascarenhas02:06:42

I am trying to write a macro that takes in a function and returns its args lists. This is what I have:

(defmacro print-arglists
  [f]
  `(:arglists (meta (var '~f))))
This works when I call it from the REPL or pass in a function directly:
(print-arglists map)
;; => ([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
But I want to be able to call it from another function like this:
(defn foo
  [f]
  ...
  (print-arglists f))
But that does not compile. I get the following error:
Unable to resolve var: f in this context
I am not sure what I am missing. Any advice/help would be appreciated.

phronmophobic02:06:39

macros only receive the syntax/code (ie. f in this case). Getting the arglists from a function is kind of tricky and I'm not sure there's any guaranteed way to do that. This is probably the closest, but relies on some clojure implementation details that you probably shouldn't rely on:

;; from 
(defn- fn-sym [^Object f]
  (let [[_ f-ns f-n] (re-matches #"(.*)\$(.*?)(__[0-9]+)?" (.. f getClass getName))]
    ;; check for anonymous function
    (when (not= "fn" f-n)
      (symbol (clojure.lang.Compiler/demunge f-ns) (clojure.lang.Compiler/demunge f-n)))))

(defn fn-arglists [f]
  (let [sym (fn-sym f)
        v (resolve sym)]
    (-> v
        meta
        :arglists)))

> (fn-arglists +)
;; ([] [x] [x y] [x y & more])

Arun Mascarenhas02:06:36

> macros only receive the syntax/code (ie. `f` in this case). Ah! I did not realize that. Thank you for pointing that out.

Arun Mascarenhas02:06:08

I do have another, not guaranteed, way to get arglists based on reflection. Your function, however, is very interesting.

Arun Mascarenhas02:06:56

Your response has taught me a lot. Thank you!

👍 3
phronmophobic02:06:58

How does the reflection technique work? That sounds interesting.

Arun Mascarenhas02:06:23

Give me a minute to dig up the code.

Arun Mascarenhas02:06:01

(defn- non-defn->arities
  [f]
  (let [arity-info (->> f
                        class
                        .getDeclaredMethods
                        (map (fn [x] method->name-and-arity x))
                        (apply merge-with into)
                        (methods->arities f))]
    arity-info))

(defn- methods->arities
  [m arities]
  (let [arity-list (sort < (:invokeStatic
                            arities (:invoke
                                     arities (:doInvoke
                                              arities []))))
        is-variadic (instance? clojure.lang.RestFn m)]
    (if is-variadic
      (conj (map (fn [x] {:arg-count x :variadic false}) (butlast arity-list))
            {:argcount (last arity-list) :variadic true})
      (map (fn [x] {:arg-count x :variadic false}) arity-list))))

(defn- method->name-and-arity
  [m]
  {(keyword (.getName m)) [(method->arity m)]})

(defn- method->arity
  [m]
  (->> m .getParameterTypes alength))

Arun Mascarenhas02:06:51

It's a little bit of a mess, but that is the gist of the code.

km03:06:23

Hey guys, Anyone have an easy way to test multipart/form-data requests with ring-mock?

Andrew Lai13:06:33

Hi @U01RLB0GUD7 - was just reading through some old slacks and didn't see if you got an answer. I have been running into the same thing, and solved it using the peridot library. (peridot.multipart/build {"name" value-or-file}) Out of the box, the library supports multipart requests to send java.io.File s and it converts other types to strings before sending. If you need support for sending other data types (such as sending InputStreams), you can extend their multimethod, peridot.multipart/add-part .

Vlad08:06:08

what is the best way to parse dates from CSV in Clojure as it stands?

borkdude09:06:03

@cstmlcodes CSV is just text, so no different than parsing dates from any other string

javahippie11:06:09

I am looking for a way to rate-limit a contact form on a webpage and came up with this solution after some toying around. As an example, if you called (is-rate-limited {:limit 5 :duration-in-secondes 10}), the first 5 calls in 10 seconds would return true, the 6th would return false. Is there a more idiomatic way to do this, or a way without an atom?

raspasov11:06:54

There are Clojure libraries available that implement rate-limits/circuit breakers.

javahippie11:06:30

The page is too small to make me want to add a library, but I will take a look at how they are doing it, thanks!

👍 3
Karol Wójcik12:06:42

How can I copy BufferedInputStream to a file?

alexmiller12:06:48

it can copy from anything that can be coerced to an input stream to something coercible to an output stream

alexmiller12:06:31

so something like ( the-input-stream ( "foo"))

Karol Wójcik12:06:41

Thank you so much!

Karol Wójcik12:06:53

I've forgotten to wrap the path with io/file

lspector13:06:06

What is the most beginner-friendly way to auto-reformat a snippet of Clojure code, that works with incomplete expressions? Could be an IDE or a standalone reformatter, but for newbies who may not have a complete setup or specialized skills. In a web page would be great, but looking for anything with close-to-zero setup or experience required.

delaguardo13:06:39

incomplete how? including unbalanced parens?

indy14:06:46

Try parinfer, it’s included in Cursive

lspector14:06:31

The point is to help people who have not yet installed something like Cursive. Incomplete as in unfinished -- may be missing closing brackets.

indy14:06:35

And cursive + IntellJ will format code. Indentation can be customised too

indy14:06:40

Parinfer will add brackets automatically. It’s what I use, has all the features of paredit but less stricter

lspector14:06:21

Cursive is great once you've installed it, created a project, and figured out how to use it. This question is for people who have not yet done that, have a snippet of Clojure code, and want to auto-reformat it properly.

lspector14:06:46

http://pretty-print.net is exactly what I have in mind!! Thanks. But alas, it doesn't work. Paste in:

(defn foo
(swish
tepplo))

lspector14:06:19

and hit "Format" and you get:

(defn foo)

lspector14:06:49

Yes, what I tried was missing an argument list... but it should be formattable...

lspector15:06:45

BTW I see that I did say "Could be an IDE" but I meant something that a total newbie could easily install and use right away. I use and love Cursive, but getting started does take some time and work and a learning curve, esp if you don't yet have Java installed or understand anything about projects, etc.

lspector15:06:05

BTW also my current use case is students who are just starting and can use http://replit.com to write and evaluate code with zero setup (just a browser), but it doesn't do auto-formatting of Clojure which I've found to be essential in this context.

lspector15:06:50

BTWx3, Calva is a little easier for total newbies than Cursive to set up and use, but can't auto-format incomplete expressions.

lspector15:06:16

zprint looks promising since there are standalone executables, but I can't figure out how to make it work for newbies.