Function that builds functions with different number or parameters

Hi All,

I have a couple of functions that return map with the same keywords. This an example how it looks like:

(defn my-func1 [x & {:as extra}]
  {:some-number x ; example of some logic inside
   :foo "foo"
   :bar "bar"})

(defn my-func2 [x y & {:as extra}]
  {:some-number (+ x y) ; example of some logic inside
   :foo "foo"
   :bar "bar"})

(defn my-func3 [x y z & {:as extra}]
  {:some-number (+ x y z) ; example of some logic inside
   :foo "foo"
   :bar "bar"})

I would like to create an alternative version of those functions. Based on my understanding the Clojure way / func. programming way is to create a function from a function which in normal circumstances is easy but in this case it’s tricky since the generated functions need to have different number of parameters. My implementation looks like this:

(defn func-returning-some-number
  [func arg-count]
  (case arg-count
    1 (fn [x & {:as extra}]
        (let [{:keys [some-number foo bar]} (func x)]
          (if (> some-number 3) ; example of some logic inside
            some-number
            :something)))
    2 (fn [x y & {:as extra}] ; <= number of arguments is the only thing that is different
        (let [{:keys [some-number foo bar]} (func x y)]
          (if (> some-number 3)
            some-number
            :something)))
    3 (fn [x y z & {:as extra}] ; <= number of arguments is the only thing that is different
        (let [{:keys [some-number foo bar]} (func x y z)]
          (if (> some-number 3)
            some-number
            :something)))))

and then I use it like this:

(def my-func1-not-returning-map
  (func-returning-some-number my-func1 1))

(def my-func2-not-returning-map
  (func-returning-some-number my-func2 2))

(def my-func3-not-returning-map
  (func-returning-some-number my-func3 3))

You can clearly see that my implementation of func-returning-some-number is bad bad bad since I’m Ctrl+c/v a block of code based on how many parameters the generated function should have. Could you please tell me is there a better way to do that?

Thank you.

Allan

Notice how my-func* functions change only by the “logic inside” more than the number of arguments of that “logic”.

Using apply you can save a lot of control:

(defn my-super-func [f & args]
  {:some-number (apply f args)
   :foo "foo"
   :bar "bar"})

(def f1 identity)
(defn f2 [x y] (+ x y))
(defn f3 [x y z] (+ x y z))

(def my-func1 (partial my-super-func f1))
(def my-func2 (partial my-super-func f2))
(def my-func3 (partial my-super-func f3))

(my-func1 1) ;; => {:some-number 1, :foo "foo", :bar "bar"}
(my-func2 1 2) ;; => {:some-number 3, :foo "foo", :bar "bar"}
(my-func3 1 2 3) ;; => {:some-number 6, :foo "foo", :bar "bar"}

Update:

Now, if you require to “dispatch” by function arity, Clojure “defn” already provides syntax for that. You don’t need to do an explicit case.

So, given my-super-func, you could have done:

(defn f*
  ([x] x)
  ([x y] (+ x y))
  ([x y z] (+ x y z)))

(def my-func* (partial my-super-func f*))

(my-func* 1) ;; => {:some-number 1, :foo "foo", :bar "bar"}
(my-func* 1 2) ;; => {:some-number 3, :foo "foo", :bar "bar"}
(my-func* 1 2 3) ;;# => {:some-number 6, :foo "foo", :bar "bar"}

I’m not sure about the “better way” you want, but I think using “apply” would be simpler.

(defn func-returning-some-number
  [func arg-count]
  (let [logic (fn [parms]
                (let [{:keys [some-number foo bar]} (apply func parms)]
                  (if (> some-number 3) ; example of some logic inside
                    some-number
                    :something)))]
  (case arg-count
    1 (fn [x & {:as extra}]
        (logic [x]))
    2 (fn [x y & {:as extra}] ; <= number of arguments is the only thing that is different
        (logic [x y]))
    3 (fn [x y z & {:as extra}] ; <= number of arguments is the only thing that is different
        (logic [x y z])))))

Hi @jgomo3 and @Lee-WonJun,

Thank you very much for the response and apologize for my late response.

I finally changed my implementation and it works great ;-). So simple - just add apply passed-func args and it just works even with & opts in the passed func (in clojure 1.11.0-alpha1).

Last stupid question if I may. Is there a lot of overhead in using apply?

I’m thinking that if I encapsulate more functions into functions and my program gets more complicated, the first (without apply) implementation would essentially turn into:

(defn function-producer1 [f] (fn [x y] (f x y)))

(defn func1 [x y] (str x "@" y))
(def func2 (function-producer1 func1)) ; (func1 x y)
(def func3 (function-producer1 func2)) ; (func2 x y)
(def func4 (function-producer1 func3)) ; (func3 x y)
(def func5 (function-producer1 func4)) ; (func4 x y)

(func5 "foo" "bar")
; "[email protected]"

But if I use apply it’ll be something like:

(defn function-producer2 [f] (fn [& args] (apply f args)))

(defn func1b [x y] (str x "#" y))
(def func2b (function-producer2 func1b)) ; (apply func1b args))
(def func3b (function-producer2 func2b)) ; (apply func2b args))
(def func4b (function-producer2 func3b)) ; (apply func3b args))
(def func5b (function-producer2 func4b)) ; (apply func4b args))

(func5b "foo" "bar")
; "foo#bar"

Which must be slower at first look. But maybe it’s not noticeable?

Is using apply something that I should not worry about or will this come back to bite me one day when the program gets larger and more complicated?

Thank you.

Allan

I don’t know too much about the performance, but I can perceive advantages on using apply just because is a more general solution, ready for different demands in the future.

So if you are worried about “‘that’ come back to bite ‘you’ one day when the program gets larger and more complicated?”, you definitely want to use the apply version. The “speed” will not be the main problem then, it will be the difficulty to adapt the system to new challenges: multi-arity solution is already adapted at least to the multi-arity challenge, 2-arity is not.

If it was speed, and apply is actually slow to the level it is the problem: Bad luck. But now all we can do is to bet, and adaptability is the BEST bet not knowing anything else.

Aside of what you are asking.

Notice how you are calling the functions function-prodcer* (let’s call it FP for the rest of this text), repeatedly in a predictable way. Let’s say we have the functions F1, … F5 for brevity (instead of func1. Then, you are doing:

F1 -> [FP] -(F2)-> [FP] -(F3)-> [FP] -(F4)-> [FP] -> F5

Diagram explanation: The boxes ([ X ]) are processes. What is in parenthesis are just notes ((A comment)). Boxes receive Input as “->[” or “]<-” and produce output as “]->” or “<-[”.

Notice, FP is being called repeatedly in a way that we could have a loop:

F1 -> [FP] -> F5
      ^   \
     / (x3)\
    . ----- .

There is a function in Clojure core library for that pattern: iterate

Usage: (iterate f x)
Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects

In this case, we could implement the same diagram with:

(def F5 (nth (iterate  FP F1) 4))
(F5 "foo" "bar")
; "foo#bar"

Thank you very much @jgomo3 :wink:

Thank you. Other possible argument is that apply is the “Clojure-way”. :wink:

Absolutely, but that’s not what I wanted to describe in the example. The function-producer shouldn’t be the same since there is no reason to

F1 -> [FP] -(F2)-> [FP] -(F3)-> [FP] -(F4)-> [FP] -> F5

Correct example would be:

F1 -> [FPa] -(F2, F3)-> [FPb] -(F4, F5)...

which would explain lifecycle of a real program. You write F1, then you decide that you also need something else so you create F2 and F3 using FPa. A month later you realize you need F4 and F5…

I wonder if I can use apply for something like this:

(defn my-func1 [x y & {:keys [abcd efgh]
                       :as opts}]
  {:some-number (+ x y)
   :some-abcd abcd
   :some-efgh efgh})

(defn function-producer
  [func arg-count]
  (case arg-count
    2 (fn [x y & {:keys [foo bar]
                  :as opts}]
        (let [{:keys [some-number] :as func-result} (func x y opts)]
          (assoc func-result
                 :added-number (+ some-number 10)
                 :added-thing (str foo bar))))))

(def modified-my-func1 (function-producer my-func1 2))

(my-func1          3 4 :abcd "aaa" :efgh "bbb")
; {:some-number 7 :some-abcd "aaa" :some-efgh "bbb"}

(modified-my-func1 3 4 :abcd "aaa" :efgh "bbb" :foo "ccc" :bar "ddd")
; {:some-number 7 :some-abcd "aaa" :some-efgh "bbb" :added-number 17 :added-thing "cccddd"}

The problem is that I can’t type (fn [& args] ... because I need to get foo and bar from opts that are used in a function returned by function-producer

Maybe by macro