제너레이터

제너레이터 (Generators)

제너레이터(generator)는 호출할 때마다 값의 시퀀스를 하나씩 증가시키며 반환하는 프로시저예요. 이 문서에서는 racket/generator 라이브러리가 제공하는 generator, yield, in-generator 등을 설명합니다.

출처: Racket Reference

본문

제너레이터는 값들의 시퀀스를 반환하는 프로시저인데, 제너레이터가 호출될 때마다 시퀀스가 하나씩 증가합니다. 특히 generator 폼은, 제너레이터에서 값을 반환하기 위해 yield를 호출하는 body를 평가함으로써 제너레이터를 구현합니다.

(require racket/generator) package: base
procedure
(generator? v) → boolean?
v : any/c

v가 제너레이터이면 #t, 그렇지 않으면 #f를 반환합니다.

syntax
(generator formals body ...+)

formals = (id ...)
        | (id ...+ . rest-id)
        | rest-id

formals가 인자를 지정하는 제너레이터를 생성합니다. 키워드 및 선택적 인자는 지원되지 않습니다. 이것은 단일 case-lambda 절의 formals와 같아요.

제너레이터에 대한 첫 번째 호출에서, 인자들이 formals에 바인딩되고 body의 평가가 시작됩니다. body의 동적 범위(dynamic extent) 동안 제너레이터는 yield 함수를 사용해 즉시 반환할 수 있습니다. 제너레이터에 대한 두 번째 호출은 yield 호출에서 재개되는데, 두 번째 호출의 인자들을 yield의 결과로 만들어 냅니다, 이런 식으로 계속됩니다. body의 최종 결과는 암묵적인 마지막 yield에 제공됩니다; 그 마지막 yield 이후 제너레이터를 다시 호출하면 같은 값을 반환하지만, 그러한 모든 호출은 제너레이터에 0개의 인자를 제공해야 합니다.

예시:

> (define g (generator ()
             (let loop ([x '(a b c)])
               (if (null? x)
                   0
                   (begin
                     (yield (car x))
                     (loop (cdr x)))))))
> (g)
'a
> (g)
'b
> (g)
'c
> (g)
0
> (g)
0
procedure
(yield v ...) → any
v : any/c

제너레이터에서 v들을 반환하며, 제너레이터 내부(즉, 제너레이터 body의 동적 범위 안)에서 실행 지점을 저장해 두었다가 다음 제너레이터 호출에 의해 재개되게 합니다. yield의 결과는 제너레이터에 대한 다음 호출에 제공되는 인자들입니다.

generator, infinite-generator, 또는 in-generator body의 동적 범위에 있지 않을 때 yieldexn:fail:contract을 일으킵니다.

예시:

> (define my-generator (generator () (yield 1) (yield 2 3 4)))
> (my-generator)
1
> (my-generator)
2
3
4

예시:

> (define pass-values-generator
    (generator ()
      (let* ([from-user (yield 2)]
             [from-user-again (yield (add1 from-user))])
        (yield from-user-again))))
> (pass-values-generator)
2
> (pass-values-generator 5)
6
> (pass-values-generator 12)
12
syntax
(infinite-generator body ...+)

generator와 같지만, 마지막 body가 암묵적으로 yield하지 않고 완료되면 body들의 평가를 반복합니다.

예시:

> (define welcome
    (infinite-generator
     (yield 'hello)
     (yield 'goodbye)))
> (welcome)
'hello
> (welcome)
'goodbye
> (welcome)
'hello
> (welcome)
'goodbye
syntax
(in-generator body ...+)
(in-generator #:arity arity-k body ...+)

(generator () body ...)로 형성된 제너레이터를 감싸는 시퀀스를 만들어 냅니다. 제너레이터가 만드는 값들이 시퀀스의 요소들을 형성하는데, 제너레이터가 만들어 내는 마지막 값(즉, 반환으로 만들어 내는 값들)은 제외됩니다.

예시:

> (for/list ([i (in-generator
                 (let loop ([x '(a b c)])
                   (when (not (null? x))
                     (yield (car x))
                     (loop (cdr x)))))])
    i)
'(a b c)

in-generatorfor(또는 for/list 등) 바인딩의 오른쪽에서 즉시 사용되면, 그 결과의 애리티(arity, 즉 시퀀스의 각 요소에 있는 값의 수)를 추론할 수 있습니다. 그렇지 않고 제너레이터가 각 요소에 대해 여러 값을 만들어 낸다면, 그 애리티는 #:arity arity-k 절로 선언되어야 합니다; arity-k는 리터럴, 정확한, 음이 아닌 정수여야 해요.

예시:

> (let ([g (in-generator
            (let loop ([n 3])
              (unless (zero? n) (yield n (add1 n)) (loop (sub1 n)))))])
    (let-values ([(not-empty? next) (sequence-generate g)])
      (let loop () (when (not-empty?) (next) (loop))) 'done))
stop?: arity mismatch;
  the expected number of arguments does not match the given
  number
   expected: 1
   given: 2
> (let ([g (in-generator #:arity 2
            (let loop ([n 3])
              (unless (zero? n) (yield n (add1 n)) (loop (sub1 n)))))])
    (let-values ([(not-empty? next) (sequence-generate g)])
      (let loop () (when (not-empty?) (next) (loop))) 'done))
'done

기존 제너레이터를 시퀀스로 사용하려면, 그 제너레이터에 대해 알려진 정지 값(stop-value)과 함께 in-producer를 사용하세요:

> (define abc-generator (generator ()
                         (for ([x '(a b c)])
                           (yield x))))
> (for/list ([i (in-producer abc-generator (void))])
    i)
'(a b c)
> (define my-stop-value (gensym))
> (define my-generator (generator ()
                        (let loop ([x (list 'a (void) 'c)])
                          (if (null? x)
                              my-stop-value
                              (begin
                                (yield (car x))
                                (loop (cdr x)))))))
> (for/list ([i (in-producer my-generator my-stop-value)])
    i)
'(a #<void> c)
procedure
(generator-state g) → symbol?
g : generator?

제너레이터의 상태를 설명하는 심볼을 반환합니다.

  • 'fresh — 제너레이터가 막 생성되었고 아직 호출되지 않았습니다.
  • 'suspendedyield 호출로 인해 제너레이터 내부의 제어가 중단되었습니다. 제너레이터를 호출할 수 있습니다.
  • 'running — 제너레이터가 현재 실행 중입니다.
  • 'done — 제너레이터가 entire body를 실행했으며, 마지막 호출과 같은 결과를 계속해서 만들어 낼 것입니다.

예시:

> (introspective-generator
   (lambda () (generator-state introspective-generator)))
'running
> (generator-state introspective-generator)
'done
> (introspective-generator)
'running
procedure
(sequence->generator s) → (-> any)
s : sequence?

시퀀스를 제너레이터로 변환합니다. 제너레이터는 호출될 때마다 시퀀스의 다음 요소를 반환하는데, 시퀀스의 각 요소는 단일 값이어야 합니다. 시퀀스가 끝나면, 제너레이터는 최종 결과로 #<void>를 반환합니다.

procedure
(sequence->repeated-generator s) → (-> any)
s : sequence?

sequence->generator와 같지만, s에 더 이상 값이 없으면 제너레이터가 시퀀스를 다시 시작합니다(그래서 제너레이터는 값 만들기를 멈추지 않습니다).

더 알아보기