구조체 타입 정의하기: 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) — 구조체 타입 선언에 대한 정보를 담는 트랜스포머 바인딩. 이 바인딩은 하위 타입을 정의하는 데 사용되며,shared및match형식에서도 동작해요.name-id바인딩에 대한 자세한 내용은 「Structure Type Transformer Binding」을 참고해요.constructor-id와name-id는 같을 수 있으며, 그 경우name-id가 두 역할을 모두 수행해요. 그 경우 표현식으로서의name-id의 확장은 그렇지 않으면 접근할 수 없는(accessor) 식별자를 만들어내고 그 식별자가 생성자 프로시저에 바인딩돼요. 확장된 식별자는'constructor-for속성을 갖는데, 그 값은name-id에free-identifier=?인 식별자이며,name-id에free-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-id와 id 둘 다 구조체 타입에 대한 정보에 바인딩돼요. 하나의 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-out은 constructor-id를 내보내기로 포함해요. constructor-id가 #:extra-constructor-name으로 제공되고 id가 아니라면, 생성자에 object-name을 적용하면 constructor-id가 아니라 id의 기호 형태가 나와요. constructor-id가 #:constructor-name으로 제공되고 name-id와 같지 않다면, name-id는 생성자 역할을 하지 않고, 생성자에 대한 object-name은 constructor-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-expr은 struct-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-id는 define-generics가 만들어낸 제네릭 인터페이스의 정적 정보에 대한 트랜스포머 바인딩이어야 해요. method-defs는 gen: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도 제공되지 않으면 id에 make- 접두사가 붙은 생성자 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도 제공되지 않으면 id에 make- 접두사가 붙은 생성자 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로 옮기고 차이점으로 대체했어요.