2013-01-21 2 views
2

제공 I가 심볼 벡터 생성이 함수 :사용 예외

(defmacro make-strings [syms] 
;eg given 2 strings ["abc" "ab"] => ("aa" "ab" "ba" "bb" "ca" "cb") 
(let [nl (count syms) vs (makevars nl) forargs (vec (interleave vs syms))] 
`(for ~forargs (str [email protected])))) 

매크로 (make-strings ["abc" "ab"])으로 실행 :

(defn makevars [n] 
"Gives a list of 'n' unique symbols" 
(let [vc (repeatedly n #(gensym))] vc)) 

이 문제가 매크로입니다 원하는 출력이 발생합니다. 매크로 (make-strings sy)로 실행 그러나 경우 싸이는 ["abc" "ab"]로 정의되는 경우이 오류가 발생합니다

UnsupportedOperationException count not supported on this type: Symbol clojure.lang.RT.countFrom (RT.java:545) 

무엇을 나는 잘못 수행하고 어떻게 고칠 수 있나요? 매크로 지식에 익숙하지 않은 나는 내 이해로 무언가가 잘못 될 것을 기대합니다.

+0

매크로가 아닌 함수가 필요합니다. 또는 eval을 사용하십시오 : (eval'(make-strings ~ sy)) – Ankur

+0

매크로 인자에 하드 데이터와 var를 전달하는 것과 다른 점이 있습니다. 감사. 매크로 루트를 사용하는 것은 나의 첫 번째 선택이 아니었지만 성공을 거두었지만 매우 제한적이었습니다. – Brian

답변

4

시작하려면 매크로를 여기에 놓고 일반 기능을 사용하여 달성하려는 논리를 구현하는 것이 좋습니다. 매크로는 편리한 사용자 지정 구문을 만들고 싶을 때만 유용합니다. 두 세트의 문자들의 데카르트 곱. 가장 쉬운 방법은있는 contrib lib 디렉토리 math.combinatorics을 사용하여 다음과 같습니다 발생할 문제는 매크로가 컴파일시에 실행되는 것입니다, 따라서 당신이 인수로 전달하는 평가되지 않은 형태를받을 말했다

user=> (def sy ["abc" "ab"]) 
user=> (map #(apply str %) (apply cartesian-product sy)) 
("aa" "ab" "ba" "bb" "ca" "cb") 

. 이것은 당신이 호출 할 때 것을 의미한다 :

(make-strings sy) 

sy에 의해 참조 벡터를 전달하는 매우 기호 symake-strings 매크로 호출 것을

(count sy) 

다음 발사 할 수 있도록 부여되는 대신에 위에서 언급 한 예외. 매크로는 컴파일 시간으로 인해 입력을 평가할 수 없으므로 매크로 내에서 심볼 뒤에 값을 가져올 수 없습니다. 이러한 심볼에 대한 모든 연산은 반환 된 매크로 자체에서 수행해야합니다. 런타임에 실행됩니다.

리터럴 또는 기호를 전달할 수 있도록하려면 for 바인딩을 구현할 수 없습니다.

+0

이 아이디어에 감사드립니다. 나는 cartesian-product를 고려했지만, 지금은 어리석게도 그것을 거부했다. 왜냐하면 나는 'apply'를 사용하는 것보다는 2 개의 문자열만을 다루겠다고 가정했기 때문이다. 내장 함수를 더 테스트하는 것을 잊지 않을 것입니다. 나는 clojure에 가장 감명 받았다. 필자는 Java에서 약 18 줄의이 함수를 작성했습니다. – Brian

+0

저는 3 년이 넘은 지금부터 clojure를 쓰고 있으며, 코드를 개선하는 데 도움이되는 표준 라이브러리 나 contrib 라이브러리에서 fn을 지속적으로 발견하고 있습니다. 내가 당신의 신발에 있었다면 나는 기분에 익숙해 질 것이다 ;-) – skuro

1

매크로 매개 변수는 평가되지 않습니다. 매크로에 매개 변수로 ["abc" "ab"]을 입력하면 문자열은 자체 평가 객체이기 때문에 문자열의 벡터로 가져옵니다. 하지만 당신이 sy 매크로를 보내면 기호는 'sy이됩니다. 그래서 오류가 발생합니다.

+0

고마워. 내 오류가 지금은 이해가됩니다. – Brian

3

필자는 skuro가 무엇이 잘못되었는지와 왜, 그리고 원래 목표를 해결하는 데 사용할 수있는 매우 유용한 라이브러리를 제안했는지에 대한 좋은 답을 제공한다고 생각합니다. "왜 내가 이것을 읽는거야?" 나는 당신이 묻는 것을 듣는다. 무서운 거시적 땅에 들어가는 일없이 몇 줄의 코드만으로 데카르트 제품에 함수를 적용하는 것을 일반화 할 수있는 방법을 공유하는 것이 재미있을 것이라고 생각했습니다.내가 일반화 말할 때

, 내가 목표는 같은 일을 할 수있을 것이라고 의미 : 우리는이 그리 멀리 존재 교환하다 기능을 개발하는 경우,

user> (permute str ["abc" "ab"]) 
=> ("aa" "ab" "ba" "bb" "ca" "cb") 

좋은 점입니다

user> (permute + [[1 2 3] [10 20 30]]) 
=> (11 21 31 12 22 32 13 23 33) 

이것은 장난감의 예이지만이 방법으로 일반화하면 얻을 수있는 유연성을 전달할 수 있습니다.

(defn permute [f [coll & remaining]] 
    (if (nil? remaining) 
    (map f coll) 
    (mapcat #(permute (partial f %) remaining) coll))) 

나는 결합하는 다른 문자열만큼의 반복에 대한 map 또는 mapcat과 반복 된 시작 핵심 아이디어 :

음, 여기에 내가 생각 해낸 아주 간결한 방법입니다. 나는 당신의 예를 시작하고, "상세"비 일반적인 솔루션 썼다 :

user> (mapcat (fn [i] (map (partial str i) "ab")) "abc") 
=> ("aa" "ab" "ba" "bb" "ca" "cb") 

mapcat 아래 "abc" 기능들. 구체적으로는, 의 "abc"이 s이고, 이때 사용하는 단일 요소는 "abc" (i)이고, 각 요소는 "ab"입니다.

이것을 함수로 일반화하는 방법을 이해하려면 하나의 "레벨을 더 깊게"이동시켜야하고 세 번째 문자열로 시도해야합니다. 이 mapcat

user> (mapcat (fn [i] (mapcat (fn [j] (map (partial str i j) "def")) "ab")) "abc") 
=> ("aad" "aae" "aaf" "abd" "abe" "abf" "bad" "bae" "baf" "bbd" "bbe" "bbf" "cad" "cae" "caf" "cbd" "cbe" "cbf") 

mapcatmap S str 함께 조합 적 방법으로 소자를 보내고 한창 것을 함수 S는 것을 함수이야. 휴. 이제 나는 내가 어떻게 일반화 할 수 있는지보기 시작했다. 가장 안쪽의 표현은 항상 map 일부 일종의 부분 str 문자열의 목록에서 마지막 문자열을 다시 조합하여 다시 조합합니다. 바깥 쪽 표현식은 단지 mapcat이고, 가장 앞쪽에있는 문자열은 가장 바깥 쪽 문자열이 다시 묶는 문자열 목록의 첫 번째 문자열을 사용하는 지점까지 이어집니다.

여기에서 나는 전체 부분 str을 한 번에 정의 할 필요가 없다는 것을 알았지 만 재귀 적으로 permute이라고 불렀기 때문에 "구축 할"수있었습니다.

필자는 이제 어떻게 함수가 작동하는지 설명 할 수있는 충분한 컨텍스트를 제공했으면 좋겠다. permute의 마지막 반복은 남아있는 콜이없는 경우 (즉, (nil? remaining)true을 반환) 발생합니다. 그것은 단지 map의 기능을 제공합니다 그것은 마지막 coll 아래로 주어진 것입니다.

콜이 남아있는 경우 mapcatpermute 변형을 현재 콜에서 내립니다. 이 순열 변종은 익명의 인수와 함께 f의 부분 함수를 사용하고 나머지 colls는 permute입니다. 이렇게하면 부분적으로 함수를 점증 적으로 생성하여 최종적으로 콜 목록 끝에 도달하면 호출됩니다. 내 머리 속에서, 역 추적을 상상해 보았습니다. 결과적으로 다시 결합 된 콜과 함께 해체 될 때까지 중첩 된 mapcat을 호출했습니다.

이 기능은 비록 간결하지만 아마도 최적이라고 생각하지 않습니다.솔직히 CS 배경이별로 없지만 Clojure에 대한 정보를 얻으려는 시점에서 self-recursive calls 대신 loop/recur을 사용하는 것이 훨씬 "효율적"인 경향이 있습니다. 최적화가 중요하다면 loop/recur을 사용하는 함수를 다시 작업하는 것이 상당히 간단하다고 상상해보십시오.

+0

아주 좋은 생각. 앞으로 몇 개월 동안 비슷한 기능이 추가로 필요할 것이므로 일반적인 클로저 기술이 향상된다고 가정하면 일반화가 바람직합니다. – Brian

+0

+1 생각의 흐름 – skuro