문서의 표기법

문서의 표기법 (Notation for Documentation)

이 장은 Racket 문서 전체에서 쓰이는 필수 용어와 표기법을 소개해요. 모듈·문법 형식·함수·구조체 타입·파라미터가 각각 어떻게 문서화되는지를 미리 알면, 레퍼런스를 읽을 때 눈에 잘 들어옵니다.

출처: Racket Reference - Notation for Documentation

본문

이 장은 Racket 문서 전반에서 쓰이는 필수 용어와 표기법을 소개합니다.

모듈 문서의 표기법 (Notation for Module Documentation)

Racket 프로그램은 모듈로 조직되기 때문에, 문서도 그 조직을 반영해 특정 모듈이 제공하는 바인딩을 절 또는 하위 절의 시작 부분에 표기(annotation)로 달아 둡니다.

예를 들어 racket/list가 제공하는 기능을 설명하는 절은 이렇게 시작해요:

(require racket/list)

require 대신 #lang으로 도입되는 모듈도 있습니다:

#lang racket/base

#lang을 쓴다는 것은 그 모듈이 보통 모듈 전체의 언어로 쓰인다는 뜻이에요. 즉 #lang 뒤에 그 언어를 붙여 시작하는 모듈에서 쓰이지, require로 가져오는 게 아니죠. 다만 달리 명시하지 않으면 #lang으로 문서화된 모듈 이름도 require와 함께 써서 그 언어의 바인딩을 얻을 수 있습니다.

모듈 표기에는 오른쪽에 그 모듈이 속한 패키지(package)도 함께 보여 줍니다. 패키지에 대한 자세한 내용은 Package Management in Racket에서 볼 수 있어요.

때로는 모듈 명세가 문서의 시작이나 많은 하위 절을 가진 절의 시작에 나타나기도 합니다. 문서의 절 또는 절의 하위 절들은 바깥 문서·절의 모듈 선언을 "상속"받는 셈이에요. 따라서 The Racket Reference에 문서화된 바인딩은 절이나 하위 절에서 달리 지정하지 않는 한 racketracket/base에서 사용할 수 있습니다.

문법 형식 문서의 표기법 (Notation for Syntactic Form Documentation)

Racket Guide의 Notation은 이 문법 형식 표기법을 소개합니다.

문법 형식은 문법(grammar)으로 명세됩니다. 보통 문법은 여는 괄호 다음에 문법 형식의 이름이 오는 형태로 시작하며, if의 문법이 그 예예요:

문법

(if test-expr then-expr else-expr)

모든 형식은 문법 객체(syntax object)로 표현되므로, 문법 명세의 괄호는 리스트를 감싼 문법 객체를 나타내고, 맨 앞의 if는 그 리스트를 시작하는 식별자로, 그 바인딩은 문서화 중인 모듈(이 경우 racket/base)의 if 바인딩이에요. 문법의 대괄호는 괄호와 같은 방식으로 문법 객체 리스트를 나타내지만, 프로그램 소스에서 관례상 대괄호를 쓰는 자리에 사용됩니다.

문법의 이탤릭체 식별자는 다른 문법 생성(production)에 대응하는 *메타변수(metavariable)*예요. 특정 메타변수 이름에는 암묵적인 문법 생성이 있습니다:

  • id로 끝나는 메타변수는 식별자를 나타냅니다.
  • keyword로 끝나는 메타변수는 문법 객체 키워드를 나타냅니다.
  • expr로 끝나는 메타변수는 어떤 형식이든 나타내며, 그 형식은 표현식으로 파싱됩니다.
  • body로 끝나는 메타변수는 어떤 형식이든 나타내며, 그 형식은 지역 정의 또는 표현식으로 파싱돼요. body는 앞에 어떤 표현식도 오지 않을 때만 정의로 파싱될 수 있고, 마지막 body는 반드시 표현식이어야 합니다(Internal Definitions 참고).
  • datum으로 끝나는 메타변수는 어떤 형식이든 나타내며, 그 형식은 보통 해석되지 않습니다(예: quote됨).
  • numberboolean로 끝나는 메타변수는 각각 어떤 문법 객체(즉 리터럴) 숫자나 불리언을 나타냅니다.

문법에서 form ...form에 맞는 임의 개수(0개도 가능)의 형식을, form ...+form에 맞는 하나 이상의 형식을 나타냅니다.

암묵적인 문법이 없는 메타변수는 문법 형식 전체 문법 옆에 생성으로 정의돼요. 예를 들어:

문법

(lambda formals body ...+)

에서 formals 메타변수는 식별자 하나, 문법 객체 리스트 안의 식별자 0개 이상, 또는 빈 리스트 대신 식별자로 끝나는 하나 이상의 페어(pair) 체인에 해당하는 문법 객체 중 하나를 나타냅니다.

어떤 문법 형식은 여러 최상위 문법을 갖기도 하는데, 그 경우 그 문법 형식의 문서에 여러 문법이 표시됩니다. 예를 들어:

문법

(init-rest id)

init-rest가 문법 객체 리스트에 혼자 있거나, 단일 식별자가 뒤따라 올 수 있음을 나타냅니다.

마지막으로 expr 메타변수를 포함한 문법 명세는 일부 메타변수에 런타임 계약(contract)을 붙여 보강할 수 있는데, 이는 그 표현식의 결과가 런타임에 만족해야 하는 술어를 나타냅니다. 예를 들어:

문법

(parameterize ([parameter-expr value-expr] ...) body ...+)
parameter-expr : parameter?

은 각 parameter-expr의 결과가 (parameter? v)가 참을 반환하는 값 v여야 함을 나타냅니다.

함수 문서의 표기법 (Notation for Function Documentation)

프로시저와 다른 값들은 계약(contract)에 기반한 표기법으로 설명됩니다. 본질적으로 이 계약들은 Racket 술어와 표현식을 사용해 문서화된 라이브러리의 인터페이스를 기술하죠.

예를 들어 다음은 전형적인 프로시저 정의의 머리(head)입니다:

프로시저

(char->integer char) → exact-integer?

정의되는 함수 char->integer는 적용되는 것처럼 조판됩니다. 함수 이름 뒤에 오는 메타변수들은 인자를 대신합니다. 모서리의 흰 글씨는 문서화되는 값의 종류를 식별해 줘요.

각 메타변수는 계약으로 설명됩니다. 앞의 예에서 메타변수 char는 계약 char?를 가져요. 이 계약은 char? 술어에 참으로 답하는 어떤 인자 char든 유효하다고 명시합니다. 문서화된 함수가 실제로 이 속성을 검사할 수도 있고 아닐 수도 있지만, 계약은 구현자의 의도를 알려 주죠.

화살표 오른쪽의 계약(이 경우 exact-integer?)은 함수가 만들어 내는 기대 결과를 명시합니다.

계약 명세는 술어 이름보다 더 표현력이 풍부할 수 있어요. argmax의 다음 머리를 생각해 보죠:

프로시저

(argmax proc lst) → any

계약 (-> any/c real?)은 함수 계약을 나타내는데, proc의 인자는 어떤 값 하나여도 되고 결과는 실수여야 한다고 명시해요. lst에 대한 계약 (and/c pair? list?)lstpair?list?를 모두 통과해야 하며(즉 비어 있지 않은 리스트), 그래야 한다고 명시합니다.

->and/c는 모두 계약 조합자(contract combinator)의 예예요. or/c, cons/c, listof 같은 계약 조합자들은 문서 곳곳에서 쓰입니다. 하이퍼링크된 조합자 이름을 클릭하면 그 의미에 대한 더 많은 정보를 볼 수 있어요.

Racket 함수는 하나 이상의 선택적 인자를 가진 것으로 문서화될 수 있습니다. read 함수가 그런 예시죠:

프로시저

(read [in]) → any

적용 문법에서 in 인자를 감싼 대괄호는 그것이 선택적 인자임을 나타냅니다.

read의 머리는 평소처럼 파라미터 in에 대한 계약을 명시해요. 계약 오른쪽에는 read를 인자 없이 호출할 때 사용되는 기본값 (current-input-port)도 명시합니다.

함수는 필수 또는 선택적 키워드 인자를 받는 것으로도 문서화될 수 있어요. 예를 들어 sort 함수는 두 개의 선택적 키워드 인자를 가집니다:

프로시저

(sort lst less-than? [#:key extract-key #:cache-keys? cache-keys?]) → list?
lst : list?
less-than? : (any/c any/c . -> . any/c)
extract-key : (any/c . -> . any/c) = (lambda (x) x)
cache-keys? : boolean? = #f

extract-keycache-keys? 인자를 감싼 대괄호는 앞과 마찬가지로 그것들이 선택적임을 나타냅니다. 머리의 계약 부분은 이 키워드 인자들에 제공되는 기본값을 보여 줘요.

구조체 타입 문서의 표기법 (Notation for Structure Type Documentation)

구조체 타입도 계약 표기법으로 문서화됩니다:

구조체

(struct color (red green blue alpha))

구조체 타입은 프로그램 소스에서 struct 형식으로 선언된 것처럼 조판됩니다. 구조체의 각 필드는 그 필드에 허용되는 값을 명시하는 해당 계약으로 문서화돼요.

앞의 예에서 구조체 타입 colorred, green, blue, alpha 네 개의 필드를 가집니다. 구조체 타입의 생성자(constructor)는 (and/c natural-number/c (<=/c 255)), 즉 최대 255까지의 음이 아닌 정확한 정수를 만족하는 필드 값을 받아들여요.

구조체 타입 문서에서 필드 이름 뒤에는 추가 키워드가 나타날 수 있습니다:

구조체

(struct data-source (connector args extensions) #:mutable)
connector : (or/c 'postgresql 'mysql 'sqlite3 'odbc)
args : list?
extensions : (listof (list/c symbol? any/c))

여기서 #:mutable 키워드는 data-source 구조체 타입 인스턴스의 필드들을 각각의 setter 함수로 변이할 수 있음을 나타냅니다.

파라미터 문서의 표기법 (Notation for Parameter Documentation)

파라미터는 함수와 같은 방식으로 문서화돼요:

파라미터

(current-command-line-arguments) → (vectorof string?)

파라미터는 참조하거나 설정할 수 있으므로 위 머리에 두 개 항목이 있어요. current-command-line-arguments를 인자 없이 호출하면 파라미터 값을 접근하는데, 그 값은 요소가 string?immutable?을 모두 통과하는 벡터여야 합니다. current-command-line-arguments를 인자 하나로 호출하면 파라미터 값을 설정하는데, 그 값은 요소가 string?을 통과하는 벡터여야 해요(필요하면 파라미터의 가드가 문자열을 불변 형태로 강제 변환합니다).

기타 문서의 표기법 (Notation for Other Documentation)

어떤 라이브러리는 상수 값에 대한 바인딩을 제공합니다. 이 값들은 별도의 머리로 문서화돼요:

object% : class?

racket/class 라이브러리는 Racket 클래스 계층의 뿌리인 object% 값을 제공해요. 그 문서 머리는 단지 그것이 class? 술어를 만족하는 값임을 나타낼 뿐입니다.

더 알아보기