구조체 타입 정의하기: struct

구조체 타입 정의하기: struct

struct는 Racket에서 새로운 구조체 타입을 정의하는 핵심 형식이에요. 프로그래머가 정의하는 데이터 타입의 개요는 《Racket 가이드》의 [Programmer-Defined Datatypes] 절에서 소개하고 있어요.

이 절에서는 struct와 그 변형들(define-struct, struct/derived, define-struct/derived) 및 struct-field-index를 다뤄요.

출처: Racket Reference

본문

syntax struct

(struct id maybe-super (field ...)
        struct-option ...)
  • `maybe-super = `|` super-id
  • `field = `field-id` `|` [field-id field-option ...]
  • `struct-option = `#:mutable` `|` #:super super-expr | `#:inspector inspector-expr` `|` #:auto-value auto-expr | `#:guard guard-expr` `|` #:property prop-expr val-expr | `#:properties prop-list-expr` `|` #:transparent | `#:prefab` `|` #:sealed | `#:authentic` `|` #:name name-id | `#:extra-name name-id` `|` #:constructor-name constructor-id | `#:extra-constructor-name constructor-id` `|` #:reflection-name symbol-expr | `#:methods gen:name-id method-defs` `|` #:omit-define-syntaxes | `#:omit-define-values` ``
  • `field-option = `#:mutable` `|` #:auto
  • `method-defs = `(definition ...)` ``

새로운 구조체 타입을 만들고(#:prefab이 지정되면 기존 구조체 타입을 사용하고), 그 구조체 타입과 관련된 트랜스포머와 변수들을 바인딩해요.

field가 n개인 struct 형식은 최대 4+2n개의 이름을 정의해요.

  • struct:id — 구조체 타입을 나타내는 구조체 타입 디스크립터 값.

  • constructor-id(기본값은 id) — m개의 인자를 받아 구조체 타입의 새 인스턴스를 돌려주는 생성자 프로시저. 여기서 m은 #:auto 옵션을 포함하지 않는 필드의 개수예요.

  • name-id(기본값은 id) — 구조체 타입 선언에 대한 정보를 담는 트랜스포머 바인딩. 이 바인딩은 하위 타입을 정의하는 데 사용되며, sharedmatch 형식에서도 동작해요. name-id 바인딩에 대한 자세한 내용은 「Structure Type Transformer Binding」을 참고해요.

    constructor-idname-id는 같을 수 있으며, 그 경우 name-id가 두 역할을 모두 수행해요. 그 경우 표현식으로서의 name-id의 확장은 그렇지 않으면 접근할 수 없는(accessor) 식별자를 만들어내고 그 식별자가 생성자 프로시저에 바인딩돼요. 확장된 식별자는 'constructor-for 속성을 갖는데, 그 값은 name-idfree-identifier=?인 식별자이며, name-idfree-identifier=?인 식별자로 syntax-procedure-alias-property를 통해 접근 가능한 구문 속성도 가져요.

  • id? — 구조체 타입의 인스턴스(constructor-id나 하위 타입의 생성자로 만들어진)에 대해 #t를 돌려주고 다른 값에는 #f를 돌려주는 술어 프로시저.

  • field에 대해 id-field-id — 구조체 타입의 인스턴스를 받아 대응하는 필드의 값을 꺼내는 접근자(accessor) 프로시저.

  • #:mutable 옵션을 포함하는 각 필드에 대해, 또는 #:mutable이 구조체 옵션으로 지정된 경우 set-id-field-id! — 구조체 타입의 인스턴스와 새 필드 값을 받는 변경자(mutator) 프로시저. 구조체는 새 값으로 파괴적으로 갱신되며 #<void>가 돌려져요.

super-id가 주어지면, 그것은 name-id에 바인딩된 것과 같은 종류의 트랜스포머 바인딩을 가져야 하며(「Structure Type Transformer Binding」 참고), 구조체 타입의 슈퍼타입을 명시해요. 또는 #:super 옵션으로 구조체 타입 디스크립터를 만들어내는 표현식을 명시할 수 있어요. 구조체 서브타입과 슈퍼타입에 대한 자세한 내용은 「Structures」를 참고해요. super-id#:super가 둘 다 주어지면 구문 오류가 보고돼요.

예시:

> (struct document (author title content))
> (struct book document (publisher))
> (struct paper (journal) #:super struct:document)

개별 필드에 #:mutable 옵션이 지정되면, 그 필드는 구조체 타입의 인스턴스에서 변경될 수 있고 변경자 프로시저가 바인딩돼요. #:mutable을 구조체 옵션으로 지정하는 것은 모든 필드에 지정하는 것과 같아요. #:mutable이 필드 옵션과 구조체 옵션 둘 다로 지정되면 구문 오류가 보고돼요.

예시:

> (struct cell ([content #:mutable]) #:transparent)
> (define a-cell (cell 0))
> (set-cell-content! a-cell 1)

#:inspector, #:auto-value, #:guard 옵션은 각각 inspector, 자동 필드의 값, guard 프로시저를 명시해요. 구조체 타입의 이 속성들에 대한 자세한 내용은 make-struct-type을 참고해요. 여러 번 제공될 수 있는 #:property 옵션은 구조체 타입에 속성 값을 붙여요. 속성에 대한 자세한 내용은 「Structure Type Properties」를 참고해요. 여러 번 제공될 수 있는 #:properties 옵션은 연관 리스트(association list)로 여러 속성과 그 값들을 받아요. #:transparent 옵션은 #:inspector #f의 줄임말이에요.

예시:

> (struct point (x y) #:inspector #f)
> (point 3 5)
(point 3 5)
> (struct celsius (temp)
    #:guard (λ (temp name)
              (unless (and (real? temp) (>= temp -273.15))
                (error "not a valid temperature"))
              temp))
> (celsius -275)
not a valid temperature

적용 가능한(appliable) 구조체를 구현하려면 prop:procedure 속성을, 인스턴스가 동기화 가능한 이벤트가 되는 구조체 타입을 만들려면 prop:evt를 사용하는 식이에요. 관례상 속성 이름은 prop:로 시작해요.

#:prefab 옵션은 새 구조체 타입을 만드는 대신 prefab(미리 정의된, 전역적으로 공유되는) 구조체 타입을 얻어와요. 그런 구조체 타입은 본질적으로 투명하고 실링(seal)되지 않으며, guard나 속성을 가질 수 없으므로 #:prefab#:transparent, #:inspector, #:guard, #:property, #:sealed, #:authentic, #:methods와 함께 쓰는 것은 구문 오류예요. 슈퍼타입이 지정되면 그것도 prefab 구조체 타입이어야 해요.

예시:

> (struct prefab-point (x y) #:prefab)
> (prefab-point 1 2)
'#s(prefab-point 1 2)
> (prefab-point? #s(prefab-point 1 2))
#t

#:sealed 옵션은 #:property prop:sealed #t의 줄임말로, 구조체 타입이 다른 구조체 타입의 슈퍼타입으로 사용되는 것을 막아요. 자세한 내용은 prop:sealed을 참고해요.

#:authentic 옵션은 #:property prop:authentic #t의 줄임말로, 구조체 타입의 인스턴스가 인페르소네이팅(impersonate-struct 참고), 채퍼로닝(chaperone-struct 참고), 또는 비플랫 계약을 획득하는 것(struct/c 참고)을 막아요. 자세한 내용은 prop:authentic을 참고해요. 슈퍼타입이 지정되면 그것도 prop:authentic 속성을 가져야 해요.

name-id#:extra-name으로 제공되고 id가 아니라면, name-idid 둘 다 구조체 타입에 대한 정보에 바인딩돼요. 하나의 struct 형식 안에서는 #:extra-name#:name 중 하나만 제공할 수 있고, #:extra-name#:omit-define-syntaxes와 결합할 수 없어요.

예시:

> (struct ghost (color name) #:prefab #:extra-name GHOST)
> (match (ghost 'red 'blinky)
    [(GHOST c n) c])
'red

constructor-id가 제공되면, name-id의 트랜스포머 바인딩은 constructor-id를 생성자 바인딩으로 기록해요. 그 결과 예를 들어 struct-outconstructor-id를 내보내기로 포함해요. constructor-id#:extra-constructor-name으로 제공되고 id가 아니라면, 생성자에 object-name을 적용하면 constructor-id가 아니라 id의 기호 형태가 나와요. constructor-id#:constructor-name으로 제공되고 name-id와 같지 않다면, name-id는 생성자 역할을 하지 않고, 생성자에 대한 object-nameconstructor-id의 기호 형태를 생성해요. 하나의 struct 형식 안에서는 #:extra-constructor-name#:constructor-name 중 하나만 제공할 수 있어요.

예시:

> (struct color (r g b) #:constructor-name -color)
> (struct rectangle (w h color) #:extra-constructor-name rect)
> (rectangle 13 50 (-color 192 157 235))
#<rectangle>
> (rect 50 37 (-color 35 183 252))
#<rectangle>

#:reflection-name symbol-expr이 제공되면, symbol-exprstruct-type-info 같은 반영(reflective) 연산에서 구조체 타입을 식별하는 데 사용되는 심볼을 만들어내야 해요. 이것은 make-struct-type의 첫 번째 인자에 해당해요. 구조체 출력은 물론 struct에 의해 바인딩되는 여러 프로시저도 그 반영 이름을 사용해요.

예시:

> (struct circle (radius) #:reflection-name '<circle>)
> (circle 15)
#<<circle>>
> (circle-radius "bad")
<circle>-radius: contract violation
  expected: <circle>?
  given: "bad"

#:methods gen:name-id method-defs가 제공되면(여러 번 제공될 수 있어요), gen:name-iddefine-generics가 만들어낸 제네릭 인터페이스의 정적 정보에 대한 트랜스포머 바인딩이어야 해요. method-defsgen:name-id 인터페이스의 메서드들을 정의해요. define/generic 형식이나 보조 정의 및 표현식도 method-defs에 나타날 수 있어요.

예시:

> (struct constant-stream (val)
    #:methods gen:stream
    [(define (stream-empty? stream) #f)
     (define (stream-first stream)
       (constant-stream-val stream))
     (define (stream-rest stream) stream)])
> (stream-ref (constant-stream 'forever) 0)
'forever
> (stream-ref (constant-stream 'forever) 50)
'forever

#:omit-define-syntaxes 옵션이 제공되면, name-id(#:extra-name이 지정된 경우 id도)는 트랜스포머로 바인딩되지 않아요. #:omit-define-values 옵션이 제공되면 일반적인 변수들은 전혀 바인딩되지 않고 id만 바인딩돼요. 둘 다 제공되면 struct 형식은 (begin)과 동등해져요.

예시:

> (struct square (side) #:omit-define-syntaxes)
> (match (square 5)
    ; fails to match because syntax is omitted
    [(struct square x) x])
eval:28:0: match: square does not refer to a structure
definition
  at: square
  in: (struct square x)
> (struct ellipse (width height) #:omit-define-values)
> ellipse-width
ellipse-width: undefined;
 cannot reference an identifier before its definition
  in module: top-level

#:auto-value에 제공된 표현식은 한 번 평가되고 구조체 타입의 모든 인스턴스 사이에서 공유돼요. 특히 가변 #:auto-value에 대한 갱신은 현재 및 미래의 모든 인스턴스에 영향을 줘요.

#:auto가 필드 옵션으로 제공되면, 구조체 타입의 생성자 프로시저는 그 필드에 대응하는 인자를 받지 않아요. 대신 #:auto-value 옵션이 명시한 구조체 타입의 자동 값을 그 필드에 사용하며, #:auto-value가 제공되지 않으면 기본값 #f를 사용해요. 그 필드는 (예를 들어 반영 연산을 통해) 가변적이지만, 변경자 프로시저는 #:mutable이 지정된 경우에만 바인딩돼요.

어떤 필드가 #:auto 옵션을 포함하면, 그 뒤의 모든 필드도 #:auto를 포함해야 하며 그렇지 않으면 구문 오류가 보고돼요. #:property 외의 어떤 필드 옵션이나 구조체 옵션 키워드가 반복되면 구문 오류가 보고돼요.

예시:

(struct posn (x y [z #:auto #:mutable])
  #:auto-value 0
  #:transparent)
> (posn 1 2)
(posn 1 2 0)
> (posn? (posn 1 2))
#t
> (posn-y (posn 1 2))
2
> (posn-z (posn 1 2))
0

(struct color-posn posn (hue) #:mutable)
(define cp (color-posn 1 2 "blue"))
> (color-posn-hue cp)
"blue"
> cp
(color-posn 1 2 0 ...)
> (set-posn-z! cp 3)

직렬화에 대해서는 define-serializable-struct를 참고해요.

package base 버전 6.9.0.4에서 변경됨: #:authentic 추가. 버전 8.0.0.7에서 변경됨: #:sealed 추가. 버전 8.17.0.4에서 변경됨: #:properties 추가.

syntax struct-field-index

(struct-field-index field-id)

이 형식은 struct 형식 안에서 표현식으로만 나타날 수 있어요. 보통 #:property와 함께 사용되며, 특히 prop:procedure 같은 속성에 사용돼요. struct-field-index 표현식의 결과는 field-id가 이름붙인 필드의 구조체 선언 안에서의 위치에 대응하는 정확한(exact) 음이 아닌 정수예요.

예시:

(struct mood-procedure (base rating)
  #:property prop:procedure (struct-field-index base))

(define happy+ (mood-procedure add1 10))
> (happy+ 2)
3
> (mood-procedure-rating happy+)
10

syntax define-struct

(define-struct id-maybe-super (field ...)
               struct-option ...)
  • `id-maybe-super = `id` `|` (id super-id)

struct와 같지만, super-id를 공급하는 문법이 다르고, #:extra-constructor-name#:constructor-name도 제공되지 않으면 idmake- 접두사가 붙은 생성자 id가 #:extra-constructor-name으로 암시적으로 공급돼요.

이 형식은 하위 호환성을 위해 제공되며, struct가 선호돼요.

예시:

(define-struct posn (x y [z #:auto])
  #:auto-value 0
  #:transparent)
> (make-posn 1 2)
(posn 1 2 0)
> (posn? (make-posn 1 2))
#t
> (posn-y (make-posn 1 2))
2

syntax struct/derived

(struct/derived (id . rest-form)
id (field ...) struct-option ...)
(struct/derived (id . rest-form)
id super-id (field ...) struct-option ...)

struct와 같지만, 추가로 (id . rest-form) 하위 형식이 있어서 구문 오류 보고를 위한 전체 형식으로 취급되고 그 외에는 무시돼요. 오류 보고를 위한 하위 형식의 유일한 제약은 id로 시작해야 한다는 것이에요. struct/derived 형식은 struct로 확장되는 매크로가 사용하도록 의도된 것이에요.

예시:

(define-syntax (fruit-struct stx)
  (syntax-case stx ()
   [(ds name . rest)
    (with-syntax ([orig stx])
      #'(struct/derived orig name (seeds color) . rest))]))

> (fruit-struct apple)
> (apple-seeds (apple 12 "red"))
12
> (fruit-struct apple #:mutable)
> (set-apple-seeds! (apple 12 "red") 8)

; this next line will cause an error due to a bad keyword
> (fruit-struct apple #:bad-option)
eval:54:0: fruit-struct: unrecognized struct-specification
keyword
  at: #:bad-option
  in: (fruit-struct apple #:bad-option)

package base 버전 7.5.0.16에서 추가됨.

syntax define-struct/derived

(define-struct/derived (id . rest-form)
  id-maybe-super (field ...) struct-option ...)

struct/derived와 같지만, super-id를 공급하는 문법이 다르고, #:extra-constructor-name#:constructor-name도 제공되지 않으면 idmake- 접두사가 붙은 생성자 id가 #:extra-constructor-name으로 암시적으로 공급돼요. define-struct/derived 형식은 define-struct로 확장되는 매크로가 사용하도록 의도된 것이에요.

예시:

(define-syntax (define-xy-struct stx)
  (syntax-case stx ()
   [(ds name . rest)
    (with-syntax ([orig stx])
      #'(define-struct/derived orig name (x y) . rest))]))

> (define-xy-struct posn)
> (posn-x (make-posn 1 2))
1
> (define-xy-struct posn #:mutable)
> (set-posn-x! (make-posn 1 2) 0)

; this next line will cause an error due to a bad keyword
> (define-xy-struct posn #:bad-option)
eval:60:0: define-xy-struct: unrecognized
struct-specification keyword
  at: #:bad-option
  in: (define-xy-struct posn #:bad-option)

package base 버전 7.5.0.16에서 변경됨: 주요 설명을 struct/derived로 옮기고 차이점으로 대체했어요.

더 알아보기