프로시저 표현식: lambda와 case-lambda
프로시저 표현식: lambda와 case-lambda
lambda는 익명 프로시저를 만드는 Racket의 핵심 표현식이에요. 키워드 인자와 기본값까지 다루는 형태를 차근차근 살펴볼게요.
출처: Racket Reference
본문
프로시저 표현식에 대한 소개는 The Racket Guide의 Functions: lambda에서 다룹니다.
lambda
syntax
(lambda kw-formals body ...+)
(λ kw-formals body ...+)
kw-formals = (arg ...)
| (arg ...+ . rest-id)
| rest-id
arg = id
| [id default-expr]
| keyword id
| keyword [id default-expr]
프로시저를 만들어냅니다. kw-formals는 프로시저가 받는 인자의 개수와 어떤 키워드 인자를 받는지를 결정합니다.
첫 번째 arg 형태만 고려하면, 단순한 kw-formals는 다음 세 가지 형태 중 하나입니다:
-
(id ...)— 프로시저는id의 개수만큼의 비키워드 인자 값을 받습니다. 각id는 위치에 따라 인자 값에 연관됩니다. -
(id ...+ . rest-id)— 프로시저는id의 개수보다 크거나 같은 임의의 개수의 비키워드 인자를 받습니다. 프로시저가 적용되면id들은 위치에 따라 인자 값에 연관되고, 남은 모든 인자는rest-id에 연관된 리스트에 담깁니다. -
rest-id— 프로시저는 임의의 개수의 비키워드 인자를 받습니다. 모든 인자는rest-id에 연관된 리스트에 담깁니다.
더 일반적으로, arg는 키워드 및/또는 기본값을 포함할 수 있습니다. 따라서 위의 처음 두 경우는 다음과 같이 더 완전하게 명시됩니다:
-
(arg ...)— 각arg는 다음 네 가지 형태를 갖습니다:id— 프로시저가 받는 비키워드 인자의 최소·최대 개수에 모두 1을 더합니다.id는 위치에 따라 실제 인자에 연관됩니다.[id default-expr]— 프로시저가 받는 비키워드 인자의 최대 개수에 1을 더합니다.id는 위치에 따라 실제 인자에 연관되고, 그런 인자가 제공되지 않으면default-expr가 평가되어id에 연관된 값을 만들어냅니다.default-expr를 가진arg는default-expr도 키워드도 없는id보다 앞에 올 수 없습니다.keyword id— 프로시저는keyword를 쓰는 키워드 기반 인자를 요구합니다.id는keyword를 쓰는 키워드 기반 실제 인자에 연관됩니다.keyword [id default-expr]— 프로시저는keyword를 쓰는 키워드 기반 인자를 받습니다.id는 적용(application)에서 제공된다면keyword를 쓰는 키워드 기반 실제 인자에 연관되고, 그렇지 않으면default-expr가 평가되어id에 연관시킬 값을 얻습니다.
kw-formals에서 키워드 인자의 위치는 중요하지 않지만, 각각 지정된 키워드는 서로 달라야 합니다. -
(arg ...+ . rest-id)— 이전 경우와 같지만, 프로시저는 최소 인자 개수를 넘는 임의의 개수의 비키워드 인자를 받습니다.args중 비키워드 인자보다 더 많은 인자가 제공되면, 추가 인자는rest-id에 연관된 리스트에 담깁니다.
다시 말해, 기본값 표현식이 있는 인자 바인딩은 let*와 유사하게 평가됩니다.
kw-formals의 식별자들은 body 안에 바인딩됩니다. 프로시저가 적용되면 각 식별자마다 새 위치(location)가 생성되고, 그 위치는 연관된 인자 값으로 채워집니다. 위치들은 순서대로 생성되고 채워지며, 위치를 채우는 데 필요한 만큼 default-expr가 평가됩니다.
body에 kw-formals의 식별자가 아닌 식별자가 나타나면, 그것은 lambda 표현식 자리에 나타났을 때와 같은 위치를 가리킵니다. (즉, 변수 참조는 어휘적으로 스코프됩니다.)
kw-formals에 여러 식별자가 나타나면 그것들은 bound-identifier=?에 따라 서로 달라야 합니다.
lambda가 만들어낸 프로시저가 받아들이는 것보다 적거나 많은 위치 기반·키워드 기반 인자로 적용되거나, 받아들이지 않는 키워드 기반 인자로 적용되거나, 요구되는 키워드 기반 인자 없이 적용되면 exn:fail:contract 예외가 발생합니다.
마지막 body 표현식은 프로시저 본문에 대해 꼬리 위치에 있습니다.
예시:
> ((lambda (x) x) 10)
10
> ((lambda (x y) (list y x)) 1 2)
'(2 1)
> ((lambda (x [y 5]) (list y x)) 1 2)
'(2 1)
> (let ([f (lambda (x #:arg y) (list y x))])
(list (f 1 #:arg 2)
(f #:arg 2 1)))
'((2 1) (2 1))
lambda나 case-lambda 표현식을 컴파일할 때 Racket은 표현식에 붙은 'method-arity-error 속성(Syntax Object Properties 참고)을 찾습니다. 그것이 참 값으로 존재하고 프로시저의 어떤 경우도 인자 0개를 받아들이지 않는다면, 프로시저는 그 프로시저와 관련된 exn:fail:contract:arity 예외가 (제공되었다면) 첫 번째 인자를 숨기도록 표시됩니다. (첫 번째 인자를 숨기는 것은 프로시저가 메서드를 구현할 때 유용한데, 메서드에서는 첫 번째 인자가 원래 소스에서 암묵적이기 때문입니다.) 이 속성은 exn:fail:contract:arity 예외의 형식에만 영향을 주지, procedure-arity의 결과에는 영향을 주지 않습니다.
비슷한 맥락으로, Racket은 lambda나 case-lambda 표현식을 컴파일할 때 'body-as-unsafe 속성도 찾습니다. 그것이 참 값으로 존재하면 프로시저 본문은 (#%declare #:unsafe)와 같은 의미로 unsafe 모드로 컴파일될 수 있습니다. 'body-as-unsafe 속성은 컴파일 시점의 현재 코드 검사자(inspector)가 초기 검사자일 때만 허용됩니다.
키워드를 받는 프로시저가 어떤 방식으로든 식별자에 바인딩되고, 그 식별자가 적용 폼의 함수 위치에서 사용되면, 적용 폼은 원래 바인딩이 적용의 대상으로서 가려지는 방식으로 확장될 수 있습니다. 함수 적용과 함수 선언 사이의 연결을 드러내기 위해, 함수 적용 확장 안의 식별자가 원래 식별자의 실질적인 별칭이라면 그 식별자에 syntax-procedure-alias-property로 접근 가능한 문법 속성이 붙습니다. 인자가 평탄화(flattened)된 형태로 변환된 것만 빼고 원래 식별자와 같은 확장 안의 식별자에는 syntax-procedure-converted-arguments-property로 접근 가능한 문법 속성이 붙습니다. 이 평탄화된 형태란 키워드 인자, 요구되는 위치 기반 인자, 위치 기반 선택 인자, 나머지 인자를 모두 요구되는 위치 기반 인자로 바꾼 것입니다. 키워드 인자는 키워드 이름으로 정렬되고, 각 선택 키워드 인자 뒤에는 값이 제공되었는지 나타내는 불리언이 따라오며, 값이 제공되지 않은 선택 키워드 인자에는 #f가 사용됩니다. 선택 위치 기반 인자에는 각각 제공되지 않은 인자마다 #f가 포함되고, 그 선택 인자 값들의 시퀀스 뒤에는 각 선택 인자 값이 제공되었는지 나타내는 불리언의 평행 시퀀스가 따라옵니다.
package base의 8.13.0.5 버전에서 변경: (free-identifier=? #'λ #'lambda)가 #t를 만들어내도록 바인딩이 조정되었습니다.
package base의 8.15.0.12 버전에서 변경: 'body-as-unsafe 속성이 추가되었습니다.
case-lambda
syntax
(case-lambda [formals body ...+] ...)
(case-λ [formals body ...+] ...)
formals = (id ...)
| (id ...+ . rest-id)
| rest-id
프로시저를 만들어냅니다. 각 [formals body ...+] 절은 단일 lambda 프로시저와 유사합니다. case-lambda로 생성된 프로시저를 적용하는 것은 절 중 하나에 해당하는 프로시저—주어진 인자 개수를 받아들이는 첫 번째 프로시저—를 적용하는 것과 같습니다. 어떤 해당 프로시저도 주어진 인자 개수를 받아들이지 않으면 exn:fail:contract 예외가 발생합니다.
case-lambda 절은 lambda의 더 일반적인 kw-formals가 아니라 formals만 지원한다는 점에 주의하세요. 즉, case-lambda는 키워드와 선택 인자를 직접 지원하지 않습니다.
예시:
> (let ([f (case-lambda
[() 10]
[(x) x]
[(x y) (list y x)]
[r r])])
(list (f)
(f 1)
(f 1 2)
(f 1 2 3)))
'(10 1 (2 1) (1 2 3))
package base의 8.13.0.5 버전에서 변경: case-λ가 추가되었습니다.
#%plain-lambda
syntax
(#%plain-lambda formals body ...+)
lambda와 같지만, 키워드나 선택 인자를 지원하지 않습니다.