생성일: 2019년 12월 01일
수정일: 2023년 07월 30일

4clojure - Function Composition (58)

  1. 문제
  2. 풀이
    1. 풀이 과정

문제

(= [3 2 1] ((__ rest reverse) [1 2 3 4]))

(= 5 ((__ (partial + 3) second) [1 2 3 4]))

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

(= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world"))

제한 : comp

풀이

(fn [& f]
	(fn [& args]
		(let [rf (reverse f)]
			(reduce (fn [acc x] (x acc)) (apply (first rf) args) (rest rf)))))

풀이 과정

high-order-function 에 대한 사용 방법이 필요 할 것 같다. 함수를 넘겨주는 함수를 만드는데 시행착오이다.

(defn rf [f]
	(f))

((rf +) 1 2 3 4)
;; Execution error (ClassCastException) at user/eval230 (REPL:1). java.lang.Long cannot be cast to clojure.lang.IFn

(defn rf [f]
	(fn [& x]
	(f x)))

((rf +) 1 2 3 4)
;; Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3369). Cannot cast clojure.lang.ArraySeq to java.lang.Number

((rf +) [1 2 3 4])
;; Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3369). Cannot cast clojure.lang.ArraySeq to java.lang.Number

만드는 방법을 확실하게 모르는 상태에서 시도만 계속 하다보니 오류를 남발하고 말았고, &를 이용한 가변 인수가 어떠한 방식으로 안에서 사용 되는지도 모르는 상태에서 하다보니 오류만 발생시카고 말았다 가변 인수는 1 2 3 4 형식으로 사용하는 것이고, 내부에서는 [1 2 3 4] 로 들어온다

연산이 필요한 경우에는 apply를 사용하여 연산을 한다.

(defn rf [f]
	(fn [& x]
	(apply f x)))

((rf +) 1 2 3 4)
;; 10

모든 함수를 처리하는 것을 한번에 만들 수 없을 것 같아서 high-order-function 정의부터 하나하나 테스트 하면서 문제를 풀어보려고 한다.

(defn rf [& f]
	(fn [& args]
		((first f) args)))

((rf rest reverse) [1 2 3 4])
;;()

rest reverse중 rest를 사용하는 함수를 만들어보고 싶었는데 원하는 결과대로 나오지 않는다.

직접 rest를 넣어보기로 했다.

(defn rf [& f]
	(fn [& args]
		(rest args)))

((rf rest reverse) [1 2 3 4])
;; ()

그래도 원하는 결과가 나오지 않는다.

args에 어떻게 파라미터가 들어가는지 확인을 해봤다.

(defn rf [& f]
	(fn [& args]
		(println args)))

((rf rest) [1 2 3 4])
;; ([1 2 3 4])
;; nil

()로 감싸서 들어오는 것을 확인을 하니 apply는 꼭 필요한 것 같다.

apply를 넣고 만들어본 함수이다.

(defn rf [& f]
	(fn [& args]
		(apply rest args)))

((rf rest) [1 2 3 4])
;; (2 3 4)

원하는 결과대로 나온다

이제는 first를 넣고 rest 함수를 선택하도록 만들었다.

(defn rf [& f]
	(fn [& args]
		(apply (first f) args)))

((rf rest reverse) [1 2 3 4])
;; (2 3 4)

reduce로 사용이 가능할 것 같은데 방법은 알아 냈다

(reduce #(%2 %) [1 2 3 4] (reverse [rest reverse]))
;; (3 2 1) 

근데 문제는 다른 테스트로 있는 , partial, 익명함수에 대한 처리가 문제이고, [rest reverse]는 vector로 해야 결과를 얻을 수 있다 안그러면 nil로 표시가 되는 현상이 있다.

해결 방법이 있는데 4clojure에서는 사용하지 못한다..

(reduce (fn [acc x] `(~x ~acc)) [1 2 3 4] '(rest reverse))
;; (reverse (rest [1 2 3 4]))

`는 list로 만들기 위한 reader macro를 사용할 수 있는 기호이다. ~는 list 생성시 저장된 값을 사용하게 해준다. 즉 ‘(x acc)로 리스트가 생성되는 것이 아니라 (rest [1 2 3 4])로 리스트가 생성이 된다.

결과 값을 가지고 eval 연산을 수행하면 된다.

(eval (reduce (fn [acc x] `(~x ~acc)) [1 2 3 4] '(rest reverse)))
;; (4 3 2)

reduce에서 함수를 받을 때에는 함수라는 것을 인식을 할 수 있도록 해줘야 한다. ~ 매크로를 넣어줘야 한다.

(reduce (fn [acc f] (f acc)) [1 2 3 4] `(~rest ~reverse))
;; (4 3 2)

(reduce (fn [acc f] (f acc)) [1 2 3 4] (reverse `(~rest ~reverse)))
;; (3 2 1)

그렇다면 인자로 받은 함수를 어떻게 함수로 인식을 해줘야 할까?

(defn rf [& f]
(fn [& args]
(reduce (fn [acc f] (f (println f))) args (reverse `(~@f)))))

((rf rest reverse) [1 2 3 4])
;; #object[clojure.core$reverse 0x2db2dd9d clojure.core$reverse@2db2dd9d]
;; #object[clojure.core$rest__5388 0x35293c05  clojure.core$rest__5388@35293c05]
;; ()

원하는 답은 아니지만 f로 들어오는 함수의 리스트를 풀어서 함수임을 인식하는 ~@ 매크로를 사용하면 된다.

args에 대해서 아직 풀지못한 숙제가 있는데 그것은 [1 2 3 4]로 들어오는 args는 ([1 2 3 4])로 되어 버린다. 이것을 [1 2 3 4]로 만들어줘야 정상적인 답을 얻을 수 있다.

(defn rf [& f]
(fn [& args]
(reduce (fn [acc f] (f (println args))) args (reverse `(~@f)))))

((rf rest reverse) [1 2 3 4])
;; ([1 2 3 4])
;; ([1 2 3 4])
;; ()
(fn [& f]
	(fn [& args]
		(let [rf (reverse f)]
			(reduce (fn [acc x] (x acc)) (apply (first rf) args) (rest rf)))))

문제의 답을 조금 참조를 해서 너무나도 쉽게 문제를 풀 수 있었다. 거의 다 오긴 했었는데 apply의 활용을 헤매다 보니 좀 많이 오래 걸렸다.

apply를 활용해야 ([])를 해결 할 수 있는데 그것을 생각 하지 못했다. 왜 초기화 인수에 apply를 해볼 생각을 못했었는지.. 무엇에 사로잡혀 문제를 헤메고 있었는지 나도 잘 모르겠다..조금 만 더 apply를 생각 했었다면 풀수도 있었을 것 같은데 안될거라고 생각을 했던 것이 다른 방향으로 문제를 풀려고 했던 것 같다, 결론 적으로는 `(~@)를 사용하면서까지 할 필요는 없었다는 것이고, 계산의 결과를 args로 해도 된다는 것을 깨달았다.

Tags: 4clojure Today I Learn