Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

clojure - Function composition with variable function arguments

I am currently struggling with an assignment to create an anonymous function, in order to fulfil the following test cases:

  • Test case 1: (= [3 2 1] ((__ rest reverse) [1 2 3 4]))

  • Test case 2: (= 5 ((__ (partial + 3) second) [1 2 3 4]))

  • Test case 3: (= true ((__ zero? #(mod % 8) +) 3 5 7 9))

  • Test case 4: (= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world"))

I came up with the solution:

(fn [& fs]
(fn [& items] (reduce #(%2 %1) 
    (flatten items) 
        (reverse fs)))) 

My idea was to create a list of the functions bound to the outer function, and then to apply a reducer on this function list, beginning with array "items". As this works fine for chaining single arity functions in test cases 1 and 2, I have no idea how to modify the inner Lambda-function, in order to deal with multi-arity functions:

(apply + ___ )    ;; first function argument of test case 3
(take 5 ___ )     ;; first function argument of test case 4

Is there still a way to get around this problem? Many thanks!

Source: 4Clojure - Problem 58

Addendum: I came across a "funky" solution using:

(fn [& fs] (reduce (fn [f g] #(f (apply g %&))) fs))

I don't fully understand this approach, to be honest...

Addendum 2: There was a similar discussion on this topic 7 years ago: Clojure: Implementing the comp function

There I found the following solution:

(fn [& xs]
  (fn [& ys]
    (reduce #(%2 %1)
            (apply (last xs) ys) (rest (reverse xs))))) 

However, I still do not understand how we are able to kick off the reducer on the expression (apply (last xs) ys) , which represents the left-most function in the function chain. In test case 1, that would translate to (apply rest [1 2 3 4]), which is wrong.

question from:https://stackoverflow.com/questions/65938429/function-composition-with-variable-function-arguments

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is very similar to how comp is implemented in clojure.core.


(defn my-comp
  ([f] f)
  ([f g]
   (fn
     ([] (f (g)))
     ([x] (f (g x)))
     ([x y] (f (g x y)))
     ([x y & args] (f (apply g x y args)))))
  ([f g & fs]
   (reduce my-comp (list* f g fs))))

The key to understanding higher order function like comp is to think about what needs to happen when we compose functions. What is the simplest case ? (comp f) Comp only receiving a single function, so we just return that function, there is no composition yet. How about second most simple case: Comp receiving two functions, like (comp f g), now we need to return another function which when called, does the composition, like (f (g)). But this returned function needs to support zero or more arguments, so we make it variadic. Why does it need to support zero or more arguments ? Because of function g, the inner most function can have zero or more arguments.

For example: what does (comp dec inc) return ? It returns this fn:

  (fn
     ([] (dec (inc)))
     ([x] (dec (inc x)))
     ([x y] (dec (inc x y)))
     ([x y & args] (dec (apply inc x y args)))))

It assumes that inc (the inner most function which gets executed first) could receive zero or more args. But in reality inc only supports one argument, so you would get the arity exception if you called this function with more than one argument like this ((comp dec inc) 1 2), but calling it with single argument would work, because the inner most function inc has a single arity, ((comp dec inc) 10). I hope I am clear here, why this returned function needs to be variadic.

Now for the next step, what if we compose three or more functions ? This is simple now, because the bread and butter was already implemented with two argument function that my-comp supports. So we just call this 2 argument function while we reduce through a list of supplied functions. Each step returns a new function which wraps the input function.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...