Search code examples
clojuremacros

Calling annonymous funciton inside macro for n number of times, depending on the the size of data structure passed as an argument to it


I am adding my practice code here for reference. The description is given below.

  (defmacro block [ctx & expr] 
(println expr)
  `(let [~@(mapcat (fn [[k v]] [k `~v]) ctx)] ~@expr))

(defmacro uia [metadata ctx arity & expr]
  `(block ~metadata 
     (fn ~arity (prn "got" ~arity ~'mt))
)
)
(def auto1 (uia {mt "dt"} 
    [{ et "wa" s "a1"}
         {et "wa" s "a2"}
         {et "um" s "a3"}] [et1 id cid] 
     (block {} (prn "auto1"))
))

(let [myarr ["x" 11 22]] (apply auto1 myarr))

On running above code, it will print "got" ["x" 11 22] "dt"only once. This is for arity inside this macro.
I want to now print it, depending on the number of elements passed inside ctx to my funciton uia.

For Eg :

from auto1, I will be passing 3 compulsory arguments to uia:

metadata : {mt "dt"}

ctx : [{ et1 "wa" s "a1"} {et1 "wa" s "a2"} {et1 "um" s "a3"}]

arity : [et id cid]

Now ctx is a vector with 3 maps in it. I want to call the anonymous function inside uia for 3 times. So it will now print;

"got" "x" 11 22 wa a1 "dt" --> on first call "got" "x" 11 22 wa a2 "dt" --> on second call "got" "x" 11 22 um a3 "dt" --> on third call

So the respective output statement according to code will be ;

(prn "got" ~'et ~'id ~'cid ~'et1 ~'s ~'md) ;=> "got x 11 22 wa a1 "dt"  ;; on first call
(prn "got" ~'et ~'id ~'cid ~'et1 ~'s ~'md) ;=> "got x 11 22 wa a2 "dt"  ;; on second call 
(prn "got" ~'et ~'id ~'cid ~'et1 ~'s ~'md) ;=> "got x 11 22 um a3 "dt"  ;; on third call and 
and so on..   ;; on nth call (where n is the number or elements present inside ctx)

Please suggest me a way through which it can be attained.

Note :

  • It should not print all the elements of ctx in one iteration.
  • Whatever changes are done, must be done inside uia.

And please let me know if some extra information related to this is required.


Solution

  • Your final function returned from calling uia doesn't include the context map anywhere:

    >>> (macroexpand-1 
         '(uia {mt "dt"} 
               [{ et "wa" s "a1"}
                {et "wa" s "a2"}
                {et "um" s "a3"}] 
               [et1 id cid] 
               (block {} (prn "auto1"))))
    
    (user/block 
      {mt "dt"} 
      (clojure.core/fn [et1 id cid] 
        (clojure.core/prn "got" [et1 id cid] mt)))
    
    >>>  (macroexpand-1
           '(user/block 
              {mt "dt"} 
              (clojure.core/fn [et1 id cid] 
                (clojure.core/prn "got" [et1 id cid] mt))))
    
    (clojure.core/let [mt "dt"] 
      (clojure.core/fn [et1 id cid] (
        clojure.core/prn "got" [et1 id cid] mt)))
    

    The result is a function that takes your specified ariety, with a closed over free variable mt bound to "dt" - the context map is nowhere to be found.

    I suspect your definition of block is not what you intended, or at the very least the signature is wrong.

    (defmacro block [ctx & expr] 
      `(let [~@(mapcat (fn [[k v]] [k v]) ctx)] ~@expr))
    

    The actual argument you pass to block is the metadata map, not the context map. If this is intentional I would change the signature to reflect that replacing ctx with mt in the signature and body.

    As to why it only prints three times, looking at the final expansion above should make that clear - the returned closure takes 3 arguments and simply prints them inside a vector along with the metdata (referenced by the closed over symbol mt from the outer let.

    (defmacro uia [metadata ctx arity & expr]
      `(block ~metadata 
         (fn ~arity 
           (dotimes [n# ~(count ctx)]
             (prn "got" ~arity ~'mt)))))
    

    With this change, now when you call uia the following prints:

    "got" ["x" 11 22] "dt"
    "got" ["x" 11 22] "dt"
    "got" ["x" 11 22] "dt"
    

    That's a step in the right direction, but your question indicates you also want the values inside the map to be printed along with the closed over metadata and the 3 function arguments. To print based on the items in the context map, two changes need to be made:

    First, the context map keys either need to be quoted symbols 'et and not et, or keywords. This is because the returned map tries to evaluate the unquoted symbol keys at run-time which are unbound (assuming you don't have some global context in which they are bound, which was not provided).

    Second, you need to iterate over each map inside the returned function and pull out the relevant values. Other than changing map keys from symbols to keywords, the only other changes are to uia as requested, and for me it seemed easiest to keep changes there anyhow.

    (defmacro block [ctx & expr]
      `(let [~@(mapcat (fn [[k v]] [k v]) ctx)] ~@expr))
    
    (defmacro uia [metadata ctx arity & expr]
      `(block ~metadata 
         (fn ~arity 
           (doseq [m# ~ctx]
             (prn "got" ~arity (:et m#) (:s m#) ~'mt)))))
    
    (def auto1 
      (uia {mt "dt"} 
           [{:et "wa" :s "a1"}
            {:et "wa" :s "a2"}
            {:et "um" :s "a3"}] 
           [et1 id cid] 
         (block {} (prn "auto1"))))
    
    (let [myarr ["x" 11 22]] (apply auto1 myarr))