1

UPDATE:

I re-wrote this to make the original problem clearer and added the all-macro solution. Please ignore this version and refer to the following:

How to create Clojure `defn` functions automatically without macros?

Unfortunately, SO will not allow me to delete this older version.


Originally motivated by the following question: Mapped calls to clojurescript macro


Suppose you want to create many similar functions automatically (i.e. without hand-writing them all). Suppose we have some pre-existing data and we want to write accessor functions like so:

(def foo
  {:able    "Adelicious!"
   :baker   "Barbrallicious!"
   :charlie "Charlizable"})
(def bar
  {:able    "Apple"
   :baker   "Berry"
   :charlie "Kumquat"})

(defn manual-my-foo [item] (get foo item))
(defn manual-my-bar [item] (get bar item))

(manual-my-foo :able) => "Adelicious!"
(manual-my-bar :charlie) => "Kumquat"

So manual-my-foo is "hard-wired" to use the foo global map.

You might think you need a macro to create this function, and that is one solution. However, a weakness of macros is that they cannot be passed as arguments to another function such as map. Thus, we could write a macro like:

(generate-fn :foo)  ;=> creates `my-foo` w/o hand-writing it

but the following would fail:

(map generate-fn [:foo :bar :baz])  

How can we automate the generation of these functions?

Community
  • 1
  • 1
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • This was originally motivated by this question where the user was trying to automatically generate some callback functions in CLJS: http://stackoverflow.com/questions/43897632/mapped-calls-to-clojurescript-macro?noredirect=1#comment74871840_43897632 I thought the use of `intern` instead of a macro calling a macro was a nice solution. – Alan Thompson May 11 '17 at 15:49

2 Answers2

2

How can we automate the generation of these functions?

You don't need to. The foo and bar maps operate as the functions you desire:

(foo :able) ; "Adelicious!"

(bar :able) ; "Apple"
Thumbnail
  • 13,293
  • 2
  • 29
  • 37
  • This was in response to another question here: http://stackoverflow.com/questions/43897632/mapped-calls-to-clojurescript-macro?noredirect=1#comment74871840_43897632 where the user was trying to automatically generate callback functions. I just tried to simplify to a more generic function. Perhaps I oversimplified here. – Alan Thompson May 11 '17 at 15:47
  • I could be wrong, but I really don't think that the original motivation is to write accessors for keywords in maps -- that's just a simplistic example. Of course *in this example* the complex solution is unnecessary, but I'd rather not have a 10-paragraph exposition of complexities by way of apology for asking what is ultimately a simple question. – galdre May 12 '17 at 17:12
-4

While you can't use map with a macro, you could write a second macro to perform this function. This may, in turn, require writing a third macro, etc, which is the origin of the phrase "Macros All the Way Down" as described in Clojure for the Brave and True and other places.

A similar question was answered here by using Clojure's intern function. Our problem is a little different than that question, since here we use intern in two different ways:

  • To create a global var like with def or defn
  • To access the value of a global var using var-get

To reduce typing, we will be using spy and spy-let from the Tupelo library. You will need the following in your project.clj:

[tupelo "0.9.38"]

which allows us to write the following code:

(ns tst.clj.core
  (:use clj.core )
  (:require [tupelo.core :as t] ))
(t/refer-tupelo)

(def foo
  {:able    "Adelicious!"
   :baker   "Barbrallicious!"
   :charlie "Charlizable"})
(def bar
  {:able    "Apple"
   :baker   "Berry"
   :charlie "Kumquat"})

(defn generate-event-fn
  [event-kw]
  (spy-let [
    event-str (name event-kw)
    event-sym (symbol event-str)
    fn-name   (symbol (str "my-" event-str))
    new-fn    (fn fn-name [item]
                (let [the-var (intern 'tst.clj.core event-sym) ; get the var the symbol 'event-sym' points to
                      the-map (var-get the-var) ; get the map the var is pointing to
                      the-str (get the-map item)] ; get the string from the map
                  (spyx the-var)
                  (spyx the-map)
                  (spyx the-str)))
  ]
    (intern 'tst.clj.core fn-name new-fn) ; create a var 'fn-name' pointing to 'new-fn'
  ))

(newline)  (println "*** generating functions ***")
(newline)  (generate-event-fn :foo) ; creates and interns a function 'my-foo'
(newline)  (generate-event-fn :bar) ; creates and interns a function 'my-bar'

(newline)  (println "*** calling function ***")
(newline)  (spyx (my-foo :able))
(newline)  (spyx (my-bar :charlie))

When executed, we see these results:

*** generating functions ***

event-str => "foo"
event-sym => foo
fn-name => my-foo
new-fn => #object[tst.clj.core$generate_event_fn$fn_name__32477 0x1ebd2a1b "tst.clj.core$generate_event_fn$fn_name__32477@1ebd2a1b"]

event-str => "bar"
event-sym => bar
fn-name => my-bar
new-fn => #object[tst.clj.core$generate_event_fn$fn_name__32477 0x4824083 "tst.clj.core$generate_event_fn$fn_name__32477@4824083"]

*** calling function ***

the-var => #'tst.clj.core/foo
the-map => {:able "Adelicious!", :baker "Barbrallicious!", :charlie "Charlizable"}
the-str => "Adelicious!"
(my-foo :able) => "Adelicious!"

the-var => #'tst.clj.core/bar
the-map => {:able "Apple", :baker "Berry", :charlie "Kumquat"}
the-str => "Kumquat"
(my-bar :charlie) => "Kumquat"

So we see that we created the functions my-foo and my-bar which can access the global foo and bar maps, respectively. And we didn't need to use macros!

In Clojure, the var is somewhat of an invisible "middle-man" between a symbol like foo or my-foo and the values they point to (a map and a function, respectively, in this example). For more information please see the relate post:

When to use a Var instead of a function?

Community
  • 1
  • 1
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • 2
    Just FYI, promoting your own library --unless actually required for the answer-- is frowned upon in Stackoverflow: ( http://stackoverflow.com/search?q=tupelo+user%3A1822379+ , https://meta.stackoverflow.com/questions/298734/is-it-acceptable-to-promote-my-own-library-as-part-of-a-real-answer ) – ClojureMostly May 11 '17 at 14:56
  • I could have used `println` instead of `spy` & `spy-let`, but that would have added duplication & clutter. Do you think that `println` would have been better? – Alan Thompson May 11 '17 at 15:51
  • I just can't see anywhere being asked "how to debug/print intermediate values" in the original question. If there is one day a debugging question you can propose the tupelo library. Otherwise please answer the question without debugging code. And FWIW, a simple inline `(def xx the-val)` is all I do when debugging, because `data > printed string` – ClojureMostly May 11 '17 at 16:04