반복과 컴프리헨션: for, for/list, ...
반복과 컴프리헨션: for, for/list, ...
Racket의 반복(iteration)과 컴프리헨션(comprehension) 폼들을 알아봐요. for에서 for/list, for/vector, for/fold 같은 다양한 변형과, 새 반복 폼을 만드는 데 쓰이는 도구들까지 다룹니다.
출처: Racket Reference
본문
3.18 반복과 컴프리헨션: for, for/list, ...
The Racket Guide의 Iterations and Comprehensions에서 반복과 컴프리헨션을 소개합니다.
for 반복 폼들은 SRFI-42 [SRFI-42]에 기반해요.
3.18.1 반복과 컴프리헨션 폼
(for (for-clause ...) body-or-break ... body)for-clause | = | [id seq-expr] | | [(id ...) seq-expr] | | #:when guard-expr | | #:unless guard-expr | | #:do [do-body ...] | | break-clause | | #:splice (splicing-id . form) | | #:on-length-mismatch mismatch-expr break-clause | = | #:break guard-expr | | #:final guard-expr body-or-break | = | body | | break-clause seq-expr : sequence?
body들을 반복적으로 평가합니다. for-clause들은 body를 포함하는 스코프에 바인딩을 도입하고, body가 평가되는 횟수를 결정합니다. for-clause들 사이에 있든 body들 사이에 있든 break-clause는 추가 반복을 멈춥니다.
단순한 경우, 각 for-clause는 처음 두 형태 중 하나를 가지는데, 여기서 [id seq-expr]는 [(id) seq-expr]의 축약형이에요. 이 단순한 경우, seq-expr들은 왼쪽에서 오른쪽으로 평가되고, 각각은 시퀀스 값(〔Sequences〕 참고)을 만들어야 합니다.
for 폼은 각 시퀀스에서 요소를 하나씩 끌어내며 반복합니다. 어떤 시퀀스든 비어 있으면 반복이 멈추고(아래 #:on-length-mismatch 참고), #<void>가 for 표현식의 결과가 됩니다. 그렇지 않으면 각 id에 대해 각 요소의 값을 담을 위치(location)가 만들어집니다. seq-expr이 만든 시퀀스는 각 반복마다 대응하는 id의 개수만큼의 값을 반환해야 해요.
그런 다음 id들이 body에서 바인딩되고, body가 평가되며 그 결과는 무시됩니다. 반복은 각 시퀀스의 다음 요소와 각 id의 새 위치로 계속됩니다.
for-clause가 0개인 for 폼은, 참조되지 않는 id를 단일 요소를 담은 시퀀스에 바인딩하는 단일 for-clause와 동등해요. 모든 id는 bound-identifier=?에 따라 서로 구별되어야 합니다.
어떤 for-clause가 #:when guard-expr 형태라면, 앞선 절들(처음 두 형태를 가진)만 위와 같이 반복을 결정하고, body는 남은 for-clause들을 사용해 다음과 같이 사실상 감싸집니다:
(when guard-expr
(for (for-clause ...) body ...+))
#:unless guard-expr 형태의 for-clause는 when 대신 unless를 사용한 같은 변환에 해당합니다. #:do [do-body ...] 형태의 for-clause도 마찬가지로 중첩을 만들고 다음과 같아요:
(let ()
do-body ...
(for (for-clause ...) body ...+))
여기서 do-body 폼들은 남은 for-clause들에서 보이는 정의를 도입할 수 있습니다.
#:break guard-expr 절은 #:unless guard-expr 절과 유사하지만, #:break가 body의 평가를 피할 때 for 폼 안의 모든 시퀀스도 효과적으로 끝납니다. #:final guard-expr 절은 #:break guard-expr와 유사하지만, 시퀀스를 즉시 끝내고 body를 건너뛰는 대신, 각 이후 시퀀스에서 최대 한 요소만, 그리고 이어지는 body의 최대 한 번의 평가만 허용합니다. body들 사이에서는, 반복을 멈추고 이후 body 평가를 막는 것 외에도, #:break guard-expr 또는 #:final guard-expr 절이 새 내부 정의 문맥을 시작해요.
#:splice (splicing-id . form) 절은 (splicing-id . form)을 확장해서 만들어지는 폼들의 시퀀스로 교체되며, splicing-id는 define-splicing-for-clause-syntax를 사용해 바인딩됩니다. 그 확장의 바인딩 문맥은 #:splice 폼과 #:when, #:unless, #:do, #:break, #:final 폼보다 앞선 어떤 절의 이전 바인딩을 포함합니다. #:splice 확장의 결과는, 절 바인딩과 확장을 더 엮기 위해 더 많은 #:splice 폼을 포함할 수 있어요. #:splice 절의 지원은 소스 for 폼에서 직접 사용하기보다는 for로 확장되는 새 폼을 만드는 데 더 적합해요.
#:on-length-mismatch mismatch-expr 절은 #:when #t와 유사하지만, 바로 앞선 절들의 시퀀스 중 하나가 다른 것들보다 먼저 끝나면 mismatch-expr이 그 효과를 위해 평가됩니다(예외를 던지기 위해 등). mismatch-expr이 값을 만들면 그 값은 무시되고 반복 계층이 종료됩니다. #:on-length-mismatch가 있으면, 어떤 그룹의 모든 시퀀스가 잠재적 반복에서 종료 검사를 받는데, 앞서 불일치를 찾았더라도 그러합니다.
list와 stream 시퀀스의 경우, for 폼 자체는 각 요소를 도달 가능하게 유지하지 않아요. seq-expr이 만든 목록이나 스트림이 그 외에는 도달 불가능하고, for body가 목록 요소에 대한 id를 더 이상 참조할 수 없다면, 그 요소는 garbage collection 대상이 됩니다. make-do-sequence 시퀀스 생성자는 이런 식으로 목록과 스트림처럼 동작하는 추가 시퀀스를 지원합니다.
seq-expr가 인용된 리터럴 목록, 벡터, 정확한 정수, 문자열, 바이트 문자열, 불변 해시이거나 그런 리터럴로 확장된다면, seq-expr이 'for:no-implicit-optimization syntax property에 대해 참 값을 가지지 않는 한 in-list 같은 시퀀스 트랜스포머를 사용한 것처럼 취급될 수 있어요. 대부분의 경우 이는 성능을 향상시킵니다.
예시:
(for ([i '(1 2 3)]
[j "abc"]
#:when (odd? i)
[k #(#t #f)])
(display (list i j k)))
; (1 a #t)(1 a #f)(3 c #t)(3 c #f)
(for ([i '(1 2 3)]
#:do [(define neg-i (* i -1))]
[j (list neg-i 0 i)])
(display (list j)))
; (-1)(0)(1)(-2)(0)(2)(-3)(0)(3)
(for ([(i j) #hash(("a" . 1) ("b" . 20))])
(display (list i j)))
; (a 1)(b 20)
(for ([i '(1 2 3)]
[j "abc"]
#:break (not (odd? i))
[k #(#t #f)])
(display (list i j k)))
; (1 a #t)(1 a #f)
(for ([i '(1 2 3)]
[j "abc"]
#:final (not (odd? i))
[k #(#t #f)])
(display (list i j k)))
; (1 a #t)(1 a #f)(2 b #t)
(for ([i '(1 2 3)]
[j "abc"]
[k #(#t #f)])
#:break (not (or (odd? i) k))
(display (list i j k)))
; (1 a #t)
(for ()
(display "here"))
; here
(for ([i '()])
(error "doesn't get here"))
(for ([i (in-range 2)]
[j (in-range 3)])
(display i))
; 01
(for ([i (in-range 2)]
[j (in-range 3)]
#:on-length-mismatch (error "different"))
(display i))
; 01
; different
버전 6.7.0.4의 package base에서 변경됨: 선택적 두 번째 결과 지원 추가.
버전 7.8.0.11에서 변경됨: 암묵적 최적화 지원 추가.
버전 8.4.0.2에서 변경됨: #:do 추가.
버전 8.4.0.3에서 변경됨: #:splice 추가.
버전 9.0.0.2에서 변경됨: #:on-length-mismatch 추가.
(for/list (for-clause ...) body-or-break ... body)
for처럼 반복하지만, body들의 마지막 표현식은 단일 값을 만들어야 하고, for/list 표현식의 결과는 순서대로의 결과들의 목록이에요. #:when이나 #:unless 절 때문에 body의 평가가 건너뛰어지면, 결과 목록에는 대응하는 요소가 포함되지 않습니다.
예시:
(for/list ([i '(1 2 3)]
[j "abc"]
#:when (odd? i)
[k #(#t #f)])
(list i j k))
; '((1 #\a #t) (1 #\a #f) (3 #\c #t) (3 #\c #f))
(for/list ([i '(1 2 3)]
[j "abc"]
#:break (not (odd? i))
[k #(#t #f)])
(list i j k))
; '((1 #\a #t) (1 #\a #f))
(for/list () 'any)
; '(any)
(for/list ([i '()])
(error "doesn't get here"))
; '()
(for/vector maybe-length (for-clause ...) body-or-break ... body)maybe-length | = | #:length length-expr | | #:length length-expr #:fill fill-expr length-expr : exact-nonnegative-integer?
for/list처럼 반복하지만, 결과는 목록 대신 벡터에 누적됩니다.
선택적 #:length 절이 지정되면 length-expr의 결과가 결과 벡터의 길이를 결정합니다. 그 경우 반복을 더 효율적으로 수행할 수 있고, 벡터가 가득 차거나 요청된 반복 횟수가 수행되는 것 중 먼저 오는 것에서 종료됩니다. length-expr이 반복 횟수보다 긴 길이를 지정하면, 벡터의 남은 슬롯은 fill-expr 값으로 초기화되는데, 기본값은 0(즉 make-vector의 기본 인자)입니다.
예시:
(for/vector ([i '(1 2 3)]) (number->string i))
; '#("1" "2" "3")
(for/vector #:length 2 ([i '(1 2 3)]) (number->string i))
; '#("1" "2")
(for/vector #:length 4 ([i '(1 2 3)]) (number->string i))
; '#("1" "2" "3" 0)
(for/vector #:length 4 #:fill "?" ([i '(1 2 3)]) (number->string i))
; '#("1" "2" "3" "?")
for/vector 폼은 벡터를 할당하고 body의 각 반복 후 그것을 변형할 수 있는데, 이는 body 중에 continuation을 잡아 여러 번 적용하면 공유 벡터를 변형할 수 있다는 뜻이에요.
(for/hash (for-clause ...) body-or-break ... body) (for/hasheq (for-clause ...) body-or-break ... body) (for/hasheqv (for-clause ...) body-or-break ... body) (for/hashalw (for-clause ...) body-or-break ... body)
for/list와 같지만 결과는 불변 hash table입니다. for/hash는 equal?로 키를 구분하는 테이블을, for/hasheq는 eq?를 쓰는 테이블을, for/hasheqv는 eqv?를 쓰는 테이블을, for/hashalw는 equal-always?를 쓰는 테이블을 만듭니다. body들의 마지막 표현식은 두 값을 반환해야 합니다: 반복이 누적하는 해시 테이블을 확장할 키와 값이에요.
예시:
(for/hash ([i '(1 2 3)])
(values i (number->string i)))
; '#hash((1 . "1") (2 . "2") (3 . "3"))
버전 8.5.0.3의 package base에서 변경됨: for/hashalw 폼 추가.
(for/and (for-clause ...) body-or-break ... body)
for처럼 반복하지만, body의 마지막 표현식이 #f를 만들면 반복이 종료되고 for/and 표현식의 결과는 #f입니다. body가 전혀 평가되지 않으면 for/and 표현식의 결과는 #t입니다. 그 외에는 결과는 body의 마지막 평가의 (단일) 결과입니다.
예시:
(for/and ([i '(1 2 3 "x")])
(i . < . 3))
; #f
(for/and ([i '(1 2 3 4)])
i)
; 4
(for/and ([i '(1 2 3 4)])
#:break (= i 3)
i)
; 2
(for/and ([i '()])
(error "doesn't get here"))
; #t
(for/or (for-clause ...) body-or-break ... body)
for처럼 반복하지만, body의 마지막 표현식이 #f가 아닌 값을 만들면 반복이 종료되고 for/or 표현식의 결과는 그 (단일) 값입니다. body가 전혀 평가되지 않으면 for/or 표현식의 결과는 #f입니다. 그 외에는 결과는 #f입니다.
예시:
(for/or ([i '(1 2 3 "x")])
(i . < . 3))
; #t
(for/or ([i '(1 2 3 4)])
i)
; 1
(for/or ([i '()])
(error "doesn't get here"))
; #f
(for/sum (for-clause ...) body-or-break ... body)
for처럼 반복하지만, 마지막 body의 각 결과는 +로 결과에 누적됩니다.
예시:
(for/sum ([i '(1 2 3 4)]) i)
; 10
(for/product (for-clause ...) body-or-break ... body)
for처럼 반복하지만, 마지막 body의 각 결과는 *로 결과에 누적됩니다.
예시:
(for/product ([i '(1 2 3 4)]) i)
; 24
(for/lists (id ... maybe-result) (for-clause ...) body-or-break ... body)maybe-result | = | #:result result-expr
for/list와 유사하지만, 마지막 body 표현식은 주어진 id의 개수만큼의 값을 만들어야 해요. id들은 지금까지 누적된, for-clause와 body에서 뒤집힌 목록들에 바인딩됩니다.
result-expr이 제공되면, 반복이 종료될 때 for/fold처럼 사용됩니다. 그렇지 않으면 결과는 제공된 id의 개수만큼의 목록입니다.
id 바인딩의 스코프는 for/fold의 누적기 식별자와 같아요. id를 변형하면 누적된 목록에 영향을 주고, 비-목록을 만드는 방식으로 변형하면 각 id에 대한 마지막 reverse가 실패할 수 있어요.
예시:
(for/lists (l1 l2 l3)
([i '(1 2 3)]
[j "abc"]
#:when (odd? i)
[k #(#t #f)])
(values i j k))
; '(1 1 3 3)
; '(#\a #\a #\c #\c)
; '(#t #f #t #f)
(for/lists (acc)
([x '(tvp tofu seitan tvp tofu)]
#:unless (member x acc))
x)
; '(tvp tofu seitan)
(for/lists (firsts seconds #:result (list firsts seconds))
([pr '((1 . 2) (3 . 4) (5 . 6))])
(values (car pr) (cdr pr)))
; '((1 3 5) (2 4 6))
버전 7.1.0.2의 package base에서 변경됨: #:result 폼 추가.
(for/first (for-clause ...) body-or-break ... body)
for처럼 반복하지만, body가 처음 평가된 후 반복이 종료되고, for/first 결과는 body의 (단일) 결과입니다. body가 전혀 평가되지 않으면 for/first 표현식의 결과는 #f입니다.
예시:
(for/first ([i '(1 2 3 "x")]
#:when (even? i))
(number->string i))
; "2"
(for/first ([i '()])
(error "doesn't get here"))
; #f
(for/last (for-clause ...) body-or-break ... body)
for처럼 반복하지만, for/last 결과는 body의 마지막 평가의 (단일) 결과입니다. body가 전혀 평가되지 않으면 for/last 표현식의 결과는 #f입니다.
예시:
(for/last ([i '(1 2 3 4 5)]
#:when (even? i))
(number->string i))
; "4"
(for/last ([i '()])
(error "doesn't get here"))
; #f
(for/fold ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body)maybe-result | = | #:result result-expr
for처럼 반복합니다. 반복이 시작되기 전에 init-expr들이 평가되어 초기 누적기 값을 만듭니다. 각 반복이 시작될 때 각 accum-id에 대해 위치가 생성되고, 대응하는 현재 누적기 값이 그 위치에 놓입니다. body의 마지막 표현식은 accum-id의 개수만큼의 값을 만들어야 하고, 그 값들이 현재 누적기 값이 됩니다. 반복이 종료될 때 result-expr이 제공되면 for/fold의 결과는 result-expr을 평가한 결과이고(accum-id들이 스코프에 있고 최종 값에 바인딩됨), 그렇지 않으면 for/fold 표현식의 결과는 누적기 값들입니다.
예시:
(for/fold ([sum 0]
[rev-roots null])
([i '(1 2 3 4)])
(values (+ sum i) (cons (sqrt i) rev-roots)))
; 10
; '(2 1.7320508075688772 1.4142135623730951 1)
(for/fold ([acc '()]
[seen (hash)]
#:result (reverse acc))
([x (in-list '(0 1 1 2 3 4 4 4))])
(cond
[(hash-ref seen x #f)
(values acc seen)]
[else (values (cons x acc)
(hash-set seen x #t))]))
; '(0 1 2 3 4)
accum-id와 init-expr의 바인딩 및 평가 순서는 for-clause들에 대한 텍스트의 왼쪽에서 오른쪽 순서를 따릅니다. 단 (역사적 이유로) 가장 바깥쪽 반복의 for-clause들에서는 accum-id를 사용할 수 없습니다. 그러나 변수의 수명은 어휘 중첩과 정확히 같지는 않아요: accum-id가 참조하는 변수는 각 반복에서 새 위치를 가집니다.
버전 6.11.0.1의 package base에서 변경됨: #:result 폼 추가.
버전 8.11.1.3에서 변경됨: 평가 순서를 텍스트의 왼쪽에서 오른쪽 순서와 일치하도록 변경 — 첫 번째 for-clause의 오른쪽보다 init-expr를 먼저 평가하고 accum-id의 섀도잉을 수정.
(for/foldr ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body)accum-option | = | #:result result-expr | | #:delay | | #:delay-as delayed-id | | #:delay-with delayer-id
for/fold와 같지만, foldl이 아니라 foldr에 해당해요: 주어진 시퀀스는 여전히 같은 순서로 반복되지만, 루프 body는 역순으로 평가됩니다. for/foldr 표현식의 평가는 수행하는 반복 횟수에 비례하는 공간을 사용하고, 주어진 시퀀스가 만든 모든 요소는 루프 body의 역방향 평가가 시작될 때까지 유지됩니다(요소가 실제로 body에서 참조된다고 가정).
예시:
(define (in-printing seq)
(sequence-map (lambda (v) (println v) v) seq))
(for/foldr ([acc '()])
([v (in-printing (in-range 1 4))])
(println v)
(cons v acc))
; 1
; 2
; 3
; 3
; 2
; 1
; '(1 2 3)
게다가 for/fold와 달리, accum-id들은 break-clause보다 앞에 나타나는 guard-expr이나 body-or-break 폼 안에서 바인딩되지 않습니다.
위에서 언급한 제한 때문에 for/foldr은 for/fold보다 덜 일반적으로 유용하지만, for/foldr은 #:delay, #:delay-as, #:delay-with 옵션을 통해 게으르게(lazily) 반복하는 추가 능력을 제공하며, 이는 for/foldr의 많은 단점을 완화할 수 있어요. 그러한 옵션이 하나 이상 지정되면, 루프 body는 반복이 언제 계속될지에 대한 명시적 제어를 받습니다. 기본적으로 각 accum-id는 promise에 바인딩되는데, 강제되면 accum-id의 현재 값을 만들어요.
이 모드에서 반복은 그러한 promise 중 하나가 강제될 때까지 계속되지 않고, 값 하나를 만드는 데 필요한 추가 반복을 촉발합니다. 루프 body가 자신의 accum-id에 대해 게으르다면 — 즉, 아무것도 강제하지 않고 값을 반환한다면 — 루프(또는 그 반복들 중 하나)는 반복이 완전히 끝나기 전에 값을 만들 것입니다. 그러한 promise 중 하나 이상에 대한 참조가 유지되면, 강제하면 일시 중단된 지점에서 반복을 재개합니다, 컨트롤이 루프 body의 다이내믹 범위를 벗어났더라도요.
예시:
(for/foldr ([acc '()] #:delay)
([v (in-range 1 4)])
(printf "--> ~v\n" v)
(begin0
(cons v (force acc))
(printf "<\-- ~v\n" v)))
; \--> 1
; \--> 2
; \--> 3
; <\-- 3
; <\-- 2
; <\-- 1
; '(1 2 3)
(define resume
(for/foldr ([acc '()] #:delay)
([v (in-range 1 5)])
(printf "--> ~v\n" v)
(begin0
(cond
[(= v 1) (force acc)]
[(= v 2) acc]
[else (cons v (force acc))])
(printf "<\-- ~v\n" v))))
; \--> 1
; \--> 2
; <\-- 2
; <\-- 1
(force resume)
; \--> 3
; \--> 4
; <\-- 4
; <\-- 3
; '(3 4)
반복 순서에 대한 이 추가 제어 덕분에, for/foldr은 적어도 때로는 누적기에 대해 게으른 한, 무한 시퀀스를 소비하고 구성할 수 있습니다.
(무한 시퀀스를 게으르게 변형하는 더 편리한(덜 유연하지만) 방법은 for/stream도 참고하세요. (내부적으로 for/stream은 for/foldr로 정의됩니다.))
예시:
(define squares (for/foldr ([s empty-stream] #:delay)
([n (in-naturals)])
(stream-cons (* n n) (force s))))
(stream->list (stream-take squares 10))
; '(0 1 4 9 16 25 36 49 64 81)
#:delay 옵션이 도입한 일시 중단은 보통 루프의 최종 반환값에 영향을 주지 않지만, #:delay와 #:result를 결합하면 accum-id들이 루프 body 안에서 지연되는 것과 같은 방식으로 result-expr의 스코프에서도 지연됩니다. 이것을 사용해 원하면 전체 루프 평가 주위에 추가 일시 중단 계층을 도입할 수 있어요.
예시:
(define evaluated-yet? #f)
(for/foldr ([acc (set! evaluated-yet? #t)] #:delay) ()
(force acc))
evaluated-yet?
; #t
(define evaluated-yet? #f)
(define start
(for/foldr ([acc (set! evaluated-yet? #t)] #:delay #:result acc) ()
(force acc)))
evaluated-yet?
; #f
(force start)
evaluated-yet?
; #t
#:delay-as 옵션이 제공되면 delayed-id는 모든 accum-id의 값을 한꺼번에 반환하는 추가 promise에 바인딩됩니다. 여러 accum-id가 주어지면, 이 promise를 강제하는 것이 각 accum-id에 바인딩된 promise를 개별적으로 강제하는 것보다 약간 더 효율적일 수 있어요.
#:delay-with 옵션이 제공되면, 주어진 delayer-id가 (기본 delay 대신) 중첩 반복을 일시 중단하는 데 사용됩니다. (delayer-id recur-expr) 형태의 폼이 만들어져 표현식 위치에 놓이는데, recur-expr는 평가되면 다음 반복을 수행하고 그 결과(들)를 반환하는 표현식이에요. delayer-id에 대한 합리적 선택에는 lazy, delay/sync, delay/thread, racket/promise의 다른 promise 생성자들, 그리고 racket/function의 thunk가 포함됩니다. 그러나 thunk나 delay/name 같은 선택은 하위 표현식을 여러 번 평가할 수 있는데, 이는 상태를 가진 시퀀스에 대해 무의미한 결과를 낳을 수 있습니다. 상태가 모든 recur-expr 평가 사이에 공유되기 때문이에요.
여러 accum-id가 주어지고 #:delay-with 옵션이 제공되며 delayer-id가 delay, lazy, delay/strict, delay/sync, delay/thread, delay/idle 중 하나에 바인딩되지 않으면, accum-id들은 루프 body 안에서조차 전혀 바인딩되지 않습니다. 대신 누적기 값에 접근하려면 #:delay-as 옵션을 지정해 delayed-id를 통해 접근해야 해요.
버전 7.3.0.3의 package base에서 추가됨.
(for* (for-clause ...) body-or-break ... body)
for와 같지만, 각 for-clause 쌍 사이에 암묵적 #:when #t가 있어서 모든 시퀀스 반복이 중첩됩니다.
예시:
(for* ([i '(1 2)]
[j "ab"])
(display (list i j)))
; (1 a)(1 b)(2 a)(2 b)
(for*/list (for-clause ...) body-or-break ... body) (for*/lists (id ... maybe-result) (for-clause ...) body-or-break ... body) (for*/vector maybe-length (for-clause ...) body-or-break ... body) (for*/hash (for-clause ...) body-or-break ... body) (for*/hasheq (for-clause ...) body-or-break ... body) (for*/hasheqv (for-clause ...) body-or-break ... body) (for*/hashalw (for-clause ...) body-or-break ... body) (for*/and (for-clause ...) body-or-break ... body) (for*/or (for-clause ...) body-or-break ... body) (for*/sum (for-clause ...) body-or-break ... body) (for*/product (for-clause ...) body-or-break ... body) (for*/first (for-clause ...) body-or-break ... body) (for*/last (for-clause ...) body-or-break ... body) (for*/fold ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body) (for*/foldr ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body)
for/list 등과 같지만, for*의 암묵적 중첩을 가져요.
예시:
(for*/list ([i '(1 2)]
[j "ab"])
(list i j))
; '((1 #\a) (1 #\b) (2 #\a) (2 #\b))
버전 7.3.0.3의 package base에서 변경됨: for*/foldr 폼 추가.
버전 8.5.0.3에서 변경됨: for*/hashalw 폼 추가.
3.18.2 새 반복 폼 파생하기
(for/fold/derived orig-datum ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body)
for/fold와 같지만, 추가 orig-datum이 모든 syntax 오류의 소스로 사용됩니다.
for/fold/derived로 확장되는 매크로는 보통 split-for-body를 사용해 #:break 같은 키워드와 섞인 매크로와 다른 정의의 가능성을 처리해야 해요.
예시:
(require (for-syntax syntax/for-body)
syntax/parse/define)
(define-syntax-parse-rule (for/digits clauses body ... tail-expr)
#:with original this-syntax
#:with ((pre-body ...) (post-body ...)) (split-for-body this-syntax #'(body ... tail-expr))
(for/fold/derived original ([n 0] [k 1] #:result n)
clauses
pre-body ...
(values (+ n (* (let () post-body ...) k)) (* k 10))))
; If we misuse for/digits, we can get good error reporting
; because the use of orig-datum allows for source correlation:
(for/digits
[a (in-list '(1 2 3))]
[b (in-list '(4 5 6))]
(+ a b))
; eval:4:0: for/digits: bad sequence binding clause
; at: a
; in: (for/digits (a (in-list (quote (1 2 3)))) (b (in-list
; (quote (4 5 6)))) (+ a b))
(for/digits
([a (in-list '(1 2 3))]
[b (in-list '(2 4 6))])
(+ a b))
; 963
; Another example: compute the max during iteration:
(define-syntax-parse-rule (for/max clauses body ... tail-expr)
#:with original this-syntax
#:with ((pre-body ...) (post-body ...)) (split-for-body this-syntax #'(body ... tail-expr))
(for/fold/derived original
([current-max -inf.0])
clauses
pre-body ...
(define maybe-new-max (let () post-body ...))
(if (> maybe-new-max current-max)
maybe-new-max
current-max)))
(for/max ([n '(3.14159 2.71828 1.61803)]
[s '(-1 1 1)])
(* n s))
; 2.71828
버전 6.11.0.1의 package base에서 변경됨: #:result 폼 추가.
(for*/fold/derived orig-datum ([accum-id init-expr] ... maybe-result) (for-clause ...) body-or-break ... body)
for*/fold와 같지만, 추가 orig-datum이 모든 syntax 오류의 소스로 사용됩니다.
예시:
(require (for-syntax syntax/for-body)
syntax/parse/define)
(define-syntax-parse-rule (for*/digits clauses body ... tail-expr)
#:with original this-syntax
#:with ((pre-body ...) (post-body ...)) (split-for-body this-syntax #'(body ... tail-expr))
(for*/fold/derived original ([n 0] [k 1] #:result n)
clauses
pre-body ...
(values (+ n (* (let () post-body ...) k)) (* k 10))))
(for*/digits
[ds (in-list '((8 3) (1 1)))]
[d (in-list ds)]
d)
; eval:10:0: for*/digits: bad sequence binding clause
; at: ds
; in: (for*/digits (ds (in-list (quote ((8 3) (1 1))))) (d
; (in-list ds)) d)
(for*/digits
([ds (in-list '((8 3) (1 1)))]
[d (in-list ds)])
d)
; 1138
버전 6.11.0.1의 package base에서 변경됨: #:result 폼 추가.
(for/foldr/derived orig-datum ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body) (for*/foldr/derived orig-datum ([accum-id init-expr] ... accum-option ...) (for-clause ...) body-or-break ... body)
for/foldr와 for*/foldr와 같지만, for/fold/derived와 for*/fold/derived에서처럼 추가 orig-datum이 모든 syntax 오류의 소스로 사용됩니다.
버전 7.3.0.3의 package base에서 추가됨.
(define-sequence-syntax id expr-transform-expr clause-transform-expr)expr-transform-expr : (or/c (-> identifier?) (syntax? . -> . syntax?)) clause-transform-expr : (syntax? . -> . syntax?)
id를 syntax로 정의합니다. (id . rest) 폼은 for(또는 그 변형 중 하나)의 for-clause에서 시퀀스를 생성하는 데 사용되면 특별히 취급됩니다. 그 경우 clause-transform-expr의 프로시저 결과가 절을 변환하기 위해 호출됩니다.
id가 다른 어떤 표현식 위치에서 사용되면 expr-transform-expr의 결과가 사용됩니다. 0-인자 프로시저라면, 결과는 식별자 other-id여야 하고 id의 어떤 사용도 other-id의 사용으로 변환됩니다. 그렇지 않으면 expr-transform-expr은 (한 인자를 받는) 매크로 트랜스포머로 사용되는 프로시저를 만들어야 합니다.
clause-transform-expr 트랜스포머가 사용될 때 for-clause를 인자로 받는데, 절의 폼은 왼쪽이 괄호로 묶인 식별자 시퀀스가 되도록 정규화됩니다. 오른쪽은 (id . rest) 형태예요. 결과는 #f일 수 있는데, 폼을 특별히 취급하지 말아야 함을 나타내기 위해서입니다(바인딩된 식별자의 개수가 (id . rest) 폼과 일치하지 않아서일 수 있음), 또는 주어진 절을 교체할 새 for-clause일 수 있어요. 새 절은 :do-in을 사용할 수 있습니다. clause-transform-expr의 결과에서 식별자를 보호하려면 syntax-protect 대신 for-clause-syntax-protect를 사용하세요.
예시:
(define (check-nat n)
(unless (exact-nonnegative-integer? n)
(raise-argument-error 'in-digits "exact-nonnegative-integer?" n)))
(define-sequence-syntax in-digits
(lambda () #'in-digits/proc)
(lambda (stx)
(syntax-case stx ()
[[(d) (_ nat)]
#'[(d)
(:do-in
([(n) nat])
(check-nat n)
([i n])
(not (zero? i))
([(j d) (quotient/remainder i 10)])
#t
#t
[j])]]
[[_] #f])))
(define (in-digits/proc n)
(for/list ([d (in-digits n)]) d))
(for/list ([d (in-digits 1138)]) d)
; '(8 3 1 1)
(map in-digits (list 137 216))
; '((7 3 1) (6 1 2))
(:do-in ([(outer-id ...) outer-expr] ...) outer-defn-or-expr ([loop-id loop-expr] ...) pos-guard ([(inner-id ...) inner-expr] ...) maybe-inner-defn-or-expr pre-guard post-guard (loop-arg ...))maybe-inner-defn/expr | = | inner-defn-or-expr
for(또는 그 변형 중 하나)의 for-clause에서 seq-expr로만 사용될 수 있는 폼이에요.
for 안에서 :do-in 폼의 조각들은 기본적으로 다음과 같이 반복에 이어 붙습니다:
(let-values ([(outer-id ...) outer-expr] ...)
outer-defn-or-expr
(let loop ([loop-id loop-expr] ...)
(if pos-guard
(let-values ([(inner-id ...) inner-expr] ...)
inner-defn-or-expr
(if pre-guard
(let body-bindings
(if post-guard
(loop loop-arg ...)
done-expr))
done-expr))
done-expr)))
여기서 body-bindings와 done-expr는 :do-in 사용의 문맥에서 옵니다. for 절에 의해 바인딩된 식별자들은 보통 ([(inner-id ...) inner-expr] ...) 부분의 일부입니다. inner-defn-or-expr이 제공되지 않으면 그 자리에 begin이 사용됩니다.
body-bindings와 done-expr은 임의의 표현식을 포함할 수 있고, 원래 for 폼에서 보인다면 잠재적으로 outer-id나 inner-id 식별자에 대한 set!을 포함할 수 있으므로, post-guard와 loop-arg에서 그런 식별자에 의존하지 않도록 주의하세요.
실제 루프 바인딩과 호출은 :do-in 폼과 병렬로 반복을 지원하는 추가 루프 인자를 가지며, 다른 조각들도 병렬 반복의 조각들과 함께 수반됩니다.
:do-in의 예는 define-sequence-syntax를 참고하세요.
버전 8.10.0.3의 package base에서 변경됨: 비어 있지 않은 maybe-inner-defn-or-expr 지원 추가.
(for-clause-syntax-protect stx) → syntax?stx : syntax?
for-syntax에서 제공됨: syntax-protect와 같지만 그냥 인자를 반환합니다.
버전 8.2.0.4의 package base에서 변경됨: "armed" syntax를 반환하는 대신 단지 stx를 반환하도록 변경.
(define-splicing-for-clause-syntax id proc-expr)
for 폼의 #:splice 절을 통한 참조를 위해 id를 바인딩합니다. proc-expr 표현식은 phase level 1에서 평가되고, syntax object를 받아 syntax object를 반환하는 프로시저를 만들어야 합니다.
프로시저의 입력은 #:splice 뒤에 나타나는 syntax object입니다. 결과 syntax object는 괄호로 묶인 폼들의 시퀀스여야 하고, 그 폼들은 둘러싼 for 폼에서 #:splice 절 자리에 이어 붙습니다.
예시:
(define-splicing-for-clause-syntax cross3
(lambda (stx)
(syntax-case stx ()
((_ n m) #'([n (in-range 3)]
#:when #t
[m (in-range 3)])))))
(for (#:splice (cross3 n m))
(println (list n m)))
; '(0 0)
; '(0 1)
; '(0 2)
; '(1 0)
; '(1 1)
; '(1 2)
; '(2 0)
; '(2 1)
; '(2 2)
버전 8.4.0.3의 package base에서 추가됨.
3.18.3 반복 확장
(require racket/for-clause)
| package: base |
이 절에서 문서화한 바인딩은 racket/base나 racket이 아니라 racket/for-clause 라이브러리가 제공합니다.
(syntax-local-splicing-for-clause-introduce stx) → syntax?stx : syntax?
syntax-local-introduce와 동등하며, define-splicing-for-clause-syntax로 바인딩된 expander에서 사용하도록 되어 있어요.
버전 8.11.1.4의 package base에서 추가됨.
버전 9.0.0.2에서 변경됨: syntax-local-introduce와 동등하도록 변경.
3.18.4 Do 루프
(do ([id init-expr step-expr-maybe] ...) (stop?-expr finish-expr ...) expr ...)step-expr-maybe | = | step-expr
stop?-expr이 #f를 반환하는 한 expr들을 반복적으로 평가합니다.
루프를 초기화하려면 init-expr들이 순서대로 평가되어 대응하는 id에 바인딩됩니다. id들은 init-expr 외의 폼 안의 모든 표현식에 바인딩됩니다.
id들이 바인딩된 후 stop?-expr이 평가됩니다. #f를 만들면 각 expr이 그 부수 효과를 위해 평가됩니다. 그런 다음 id들은 step-expr들의 값으로 효과적으로 갱신되는데, 여기서 id의 기본 step-expr은 그냥 id입니다. 더 정확히는, 대응하는 step-expr들의 값으로 초기화된 id들에 대한 새 위치로 반복이 계속됩니다.
stop?-expr이 참 값을 만들면 finish-expr들이 순서대로 평가되고, 마지막 것은 do 폼의 전체 값이 되도록 꼬리 위치에서 평가됩니다. finish-expr이 제공되지 않으면 do 폼의 값은 #<void>입니다.